Skip to content

Commit 00a13b5

Browse files
authored
Add multikeylistener (#2372)
* Add MultiKeyListener CRD and generated boilerplate Introduce the MultiKeyListener custom resource type with support for priority-based routing across multiple routing keys. This commit adds: - CRD schema definition (skupper_multikeylistener_crd.yaml) - API types with Priority strategy (multikeylistener_types.go) - Generated clientset, informers, and listers Signed-off-by: Christian Kruse <christian@c-kruse.com> * Extend router bridge config for MultiKeyListener support Add ListenerAddress entity and multiAddressStrategy field to support priority-based routing. - Add ListenerAddress type for mapping priority values to routing keys - Add ListenerAddressMap to BridgeConfig alongside TcpListeners/TcpConnectors - Extend TcpEndpoint with MultiAddressStrategy field - Update AMQP management to query/create/delete listenerAddress entities - Update config marshaling/unmarshaling for listenerAddress entities - Add ListenerAddressDifference for config synchronization Signed-off-by: Christian Kruse <christian@c-kruse.com> * Implement site bindings for MultiKeyListener Add core MultiKeyListener support to the site bindings package Signed-off-by: Christian Kruse <christian@c-kruse.com> * Add MultiKeyListener watcher types and informer Add the watcher infrastructure for MultiKeyListener resources Signed-off-by: Christian Kruse <christian@c-kruse.com> * Implement ExtendedBindings support for MultiKeyListener Add MultiKeyListener handling to the kube site ExtendedBindings: - Track multiKeyListenerHosts for service exposure management - Implement updateBridgeConfigForMultiKeyListener with port mapping - Add multiKeyListenerUpdated for exposing services - Add multiKeyListenerDeleted for cleaning up exposed services - Implement UpdateMultiKeyListener/GetMultiKeyListener methods - Update bindings_test.go to include ListenerAddresses in expected configs Signed-off-by: Christian Kruse <christian@c-kruse.com> * Wire MultiKeyListener into controller and site reconciliation Signed-off-by: Christian Kruse <christian@c-kruse.com> * Implement READY condition reconciliation for MultiKeyListener Add status reconciliation that updates MultiKeyListener READY condition based on matching connectors in the network. The MultiKeyListener becomes READY when any routing key in its strategy has a reachable connector. Signed-off-by: Christian Kruse <christian@c-kruse.com> * Make connector address and multiAddressStrategy mututally exclusive Signed-off-by: Christian Kruse <christian@c-kruse.com> * Implement RequireClientCert Signed-off-by: Christian Kruse <christian@c-kruse.com> * gracefully handle multikeylistener not installed Signed-off-by: Christian Kruse <christian@c-kruse.com> * Implement MultiKeyListeners for system sites - Add MultiKeyListener to inputs - Wire up MultiKeyListener to SiteState for router config and status updates - Extend system apply and system delete commands for MultiKeyListener resources. Signed-off-by: Christian Kruse <christian@c-kruse.com> * Include sample MultiKeyListener Adds a sample for operator bundles Signed-off-by: Christian Kruse <christian@c-kruse.com> * Rename router configuration options multiAddressStrategy=priority instead of priorityFailover listenerAddress.listener instead of listenerRef Signed-off-by: Christian Kruse <christian@c-kruse.com> * prefix multikeylistener service port name Updates the multikeylistener service port logic to prefix port names. Fixes bug where Listener and MultiKeyListener with the same name got assigned the same router Port: e.g. two tcpListeners with same port. Fixes bug where Service ports (unique by name) conflicted between a Listener and MKL with the same name. Signed-off-by: Christian Kruse <christian@c-kruse.com> --------- Signed-off-by: Christian Kruse <christian@c-kruse.com>
1 parent 536d2cd commit 00a13b5

47 files changed

Lines changed: 2125 additions & 174 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
---
2+
apiVersion: apiextensions.k8s.io/v1
3+
kind: CustomResourceDefinition
4+
metadata:
5+
annotations:
6+
controller-gen.kubebuilder.io/version: v0.19.0
7+
name: multikeylisteners.skupper.io
8+
spec:
9+
group: skupper.io
10+
names:
11+
kind: MultiKeyListener
12+
listKind: MultiKeyListenerList
13+
plural: multikeylisteners
14+
singular: multikeylistener
15+
scope: Namespaced
16+
versions:
17+
- additionalPrinterColumns:
18+
- description: The status of the multikeylistener
19+
jsonPath: .status.status
20+
name: Status
21+
type: string
22+
- description: Any human reandable message relevant to the multikeylistener
23+
jsonPath: .status.message
24+
name: Message
25+
type: string
26+
- description: Whether there is at least one connector in the network matched
27+
by the strategy
28+
jsonPath: .status.hasDestination
29+
name: HasDestination
30+
type: boolean
31+
name: v2alpha1
32+
schema:
33+
openAPIV3Schema:
34+
description: |-
35+
MultiKeyListeners bind a local connection endpoint to Connectors across the
36+
Skupper network. A MultiKeyListener has a strategy that matches it to
37+
Connector routing keys.
38+
properties:
39+
apiVersion:
40+
description: |-
41+
APIVersion defines the versioned schema of this representation of an object.
42+
Servers should convert recognized schemas to the latest internal value, and
43+
may reject unrecognized values.
44+
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources
45+
type: string
46+
kind:
47+
description: |-
48+
Kind is a string value representing the REST resource this object represents.
49+
Servers may infer this from the endpoint the client submits requests to.
50+
Cannot be updated.
51+
In CamelCase.
52+
More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds
53+
type: string
54+
metadata:
55+
type: object
56+
spec:
57+
properties:
58+
host:
59+
description: |-
60+
host is the hostname or IP address of the local listener. Clients at
61+
this site use the listener host and port to establish connections to the
62+
remote service.
63+
type: string
64+
port:
65+
description: |-
66+
port of the local listener. Clients at this site use the listener host
67+
and port to establish connections to the remote service.
68+
type: integer
69+
requireClientCert:
70+
description: |-
71+
requireClientCert indicates that clients must present valid certificates
72+
to the listener to connect.
73+
type: boolean
74+
settings:
75+
additionalProperties:
76+
type: string
77+
description: |-
78+
settings is a map containing additional settings.
79+
80+
**Note:** In general, we recommend not changing settings from
81+
their default values.
82+
type: object
83+
strategy:
84+
description: |-
85+
strategy for routing traffic from the local listener endpoint to one or
86+
more connector instances by routing key.
87+
properties:
88+
priority:
89+
description: |-
90+
PriorityStrategySpec specifies an ordered set of routing keys to
91+
route traffic to.
92+
93+
With this strategy 100% of traffic will be directed to the first routing key
94+
with a reachable connector.
95+
properties:
96+
routingKeys:
97+
description: routingKeys to route traffic to in order of highest
98+
to lowest priority.
99+
items:
100+
type: string
101+
maxItems: 256
102+
minItems: 1
103+
type: array
104+
x-kubernetes-list-type: set
105+
required:
106+
- routingKeys
107+
type: object
108+
type: object
109+
x-kubernetes-validations:
110+
- message: exactly one of the fields in [priority] must be
111+
set
112+
rule: '[has(self.priority)].filter(x,x==true).size() ==
113+
1'
114+
tlsCredentials:
115+
description: tlsCredentials for client-to-listener
116+
type: string
117+
required:
118+
- host
119+
- port
120+
- strategy
121+
type: object
122+
status:
123+
properties:
124+
conditions:
125+
description: |-
126+
conditions describing the current state of the multikeylistener
127+
128+
- `Configured`: The multikeylistener configuration has been applied to the router.
129+
- `Operational`: There is at least one connector corresponding to the multikeylistener strategy.
130+
- `Ready`: The multikeylistener is ready to use. All other conditions are true..
131+
items:
132+
description: Condition contains details for one aspect of the current
133+
state of this API Resource.
134+
properties:
135+
lastTransitionTime:
136+
description: |-
137+
lastTransitionTime is the last time the condition transitioned from one status to another.
138+
This should be when the underlying condition changed. If that is not known, then using the time when the API field changed is acceptable.
139+
format: date-time
140+
type: string
141+
message:
142+
description: |-
143+
message is a human readable message indicating details about the transition.
144+
This may be an empty string.
145+
maxLength: 32768
146+
type: string
147+
observedGeneration:
148+
description: |-
149+
observedGeneration represents the .metadata.generation that the condition was set based upon.
150+
For instance, if .metadata.generation is currently 12, but the .status.conditions[x].observedGeneration is 9, the condition is out of date
151+
with respect to the current state of the instance.
152+
format: int64
153+
minimum: 0
154+
type: integer
155+
reason:
156+
description: |-
157+
reason contains a programmatic identifier indicating the reason for the condition's last transition.
158+
Producers of specific condition types may define expected values and meanings for this field,
159+
and whether the values are considered a guaranteed API.
160+
The value should be a CamelCase string.
161+
This field may not be empty.
162+
maxLength: 1024
163+
minLength: 1
164+
pattern: ^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$
165+
type: string
166+
status:
167+
description: status of the condition, one of True, False, Unknown.
168+
enum:
169+
- "True"
170+
- "False"
171+
- Unknown
172+
type: string
173+
type:
174+
description: type of condition in CamelCase or in foo.example.com/CamelCase.
175+
maxLength: 316
176+
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?(([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$
177+
type: string
178+
required:
179+
- lastTransitionTime
180+
- message
181+
- reason
182+
- status
183+
- type
184+
type: object
185+
type: array
186+
hasDestination:
187+
description: |-
188+
hasDestination is set true when there is at least one connector in the
189+
network with a routing key matched by the strategy.
190+
type: boolean
191+
message:
192+
description: A human-readable status message. Error messages are reported
193+
here.
194+
type: string
195+
status:
196+
description: |-
197+
The current state of the resource.
198+
- `Pending`: The resource is being processed.
199+
- `Error`: There was an error processing the resource. See `message` for more information.
200+
- `Ready`: The resource is ready to use.
201+
type: string
202+
strategy:
203+
properties:
204+
priority:
205+
description: priority status
206+
properties:
207+
routingKeysReachable:
208+
description: |-
209+
routingKeysReachable is a list of routingKeys with at least one
210+
reachable connector given in priority order.
211+
items:
212+
type: string
213+
type: array
214+
required:
215+
- routingKeysReachable
216+
type: object
217+
type: object
218+
x-kubernetes-validations:
219+
- message: exactly one of the fields in [priority] must be
220+
set
221+
rule: '[has(self.priority)].filter(x,x==true).size() ==
222+
1'
223+
type: object
224+
required:
225+
- spec
226+
type: object
227+
served: true
228+
storage: true
229+
subresources:
230+
status: {}

config/crd/kustomization.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ resources:
99
- bases/skupper_connector_crd.yaml
1010
- bases/skupper_link_crd.yaml
1111
- bases/skupper_listener_crd.yaml
12+
- bases/skupper_multikeylistener_crd.yaml
1213
- bases/skupper_router_access_crd.yaml
1314
- bases/skupper_secured_access_crd.yaml
1415
- bases/skupper_site_crd.yaml

config/rbac/cluster/clusterrole.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ rules:
142142
- accessgrants/status
143143
- listeners
144144
- listeners/status
145+
- multikeylisteners
146+
- multikeylisteners/status
145147
- connectors
146148
- connectors/status
147149
- attachedconnectors

config/rbac/namespace/role.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ rules:
143143
- accessgrants/status
144144
- listeners
145145
- listeners/status
146+
- multikeylisteners
147+
- multikeylisteners/status
146148
- connectors
147149
- connectors/status
148150
- attachedconnectors

config/samples/kustomization.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ resources:
88
- skupper_v2alpha1_connector.yaml
99
- skupper_v2alpha1_link.yaml
1010
- skupper_v2alpha1_listener.yaml
11+
- skupper_v2alpha1_multikeylistener.yaml
1112
- skupper_v2alpha1_router_access.yaml
1213
- skupper_v2alpha1_secured_access.yaml
1314
- skupper_v2alpha1_site.yaml
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
apiVersion: skupper.io/v2alpha1
2+
kind: MultiKeyListener
3+
metadata:
4+
name: backend
5+
spec:
6+
host: backend
7+
port: 8080
8+
strategy:
9+
priority:
10+
routingKeys:
11+
- backend-primary
12+
- backend-secondary

internal/cmd/skupper/common/constants.go

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,17 @@ var (
1111
)
1212

1313
const (
14-
Connectors string = "Connector"
15-
Listeners string = "Listener"
16-
Sites string = "Site"
17-
RouterAccesses string = "RouterAccess"
18-
Links string = "Link"
19-
AccessTokens string = "AccessToken"
20-
Secrets string = "Secret"
21-
ConfigMaps string = "ConfigMap"
22-
Certificates string = "Certificate"
23-
SecuredAccesses string = "SecuredAccess"
14+
Connectors string = "Connector"
15+
Listeners string = "Listener"
16+
Sites string = "Site"
17+
RouterAccesses string = "RouterAccess"
18+
Links string = "Link"
19+
AccessTokens string = "AccessToken"
20+
Secrets string = "Secret"
21+
ConfigMaps string = "ConfigMap"
22+
Certificates string = "Certificate"
23+
SecuredAccesses string = "SecuredAccess"
24+
MultiKeyListeners string = "MultiKeyListener"
2425
)
2526

2627
const (

internal/cmd/skupper/system/nonkube/system_apply.go

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,23 +17,24 @@ import (
1717
)
1818

1919
type CmdSystemApply struct {
20-
Client skupperv2alpha1.SkupperV2alpha1Interface
21-
KubeClient kubernetes.Interface
22-
CobraCmd *cobra.Command
23-
Namespace string
24-
Flags *common.CommandSystemApplyFlags
25-
ParseInput func(namespace string, reader *bufio.Reader, result *fs.InputFileResource) error
26-
siteHandler *fs.SiteHandler
27-
connectorHandler *fs.ConnectorHandler
28-
listenerHandler *fs.ListenerHandler
29-
linkHandler *fs.LinkHandler
30-
routerAccessHandler *fs.RouterAccessHandler
31-
accessTokenHandler *fs.AccessTokenHandler
32-
certificateHandler *fs.CertificateHandler
33-
securedAccessHandler *fs.SecuredAccessHandler
34-
secretHandler *fs.SecretHandler
35-
file string
36-
logger *slog.Logger
20+
Client skupperv2alpha1.SkupperV2alpha1Interface
21+
KubeClient kubernetes.Interface
22+
CobraCmd *cobra.Command
23+
Namespace string
24+
Flags *common.CommandSystemApplyFlags
25+
ParseInput func(namespace string, reader *bufio.Reader, result *fs.InputFileResource) error
26+
siteHandler *fs.SiteHandler
27+
connectorHandler *fs.ConnectorHandler
28+
listenerHandler *fs.ListenerHandler
29+
multiKeyListenerHandler *fs.MultiKeyListenerHandler
30+
linkHandler *fs.LinkHandler
31+
routerAccessHandler *fs.RouterAccessHandler
32+
accessTokenHandler *fs.AccessTokenHandler
33+
certificateHandler *fs.CertificateHandler
34+
securedAccessHandler *fs.SecuredAccessHandler
35+
secretHandler *fs.SecretHandler
36+
file string
37+
logger *slog.Logger
3738
}
3839

3940
func NewCmdSystemApply() *CmdSystemApply {
@@ -52,6 +53,7 @@ func (cmd *CmdSystemApply) NewClient(cobraCommand *cobra.Command, args []string)
5253

5354
cmd.connectorHandler = fs.NewConnectorHandler(cmd.Namespace)
5455
cmd.listenerHandler = fs.NewListenerHandler(cmd.Namespace)
56+
cmd.multiKeyListenerHandler = fs.NewMultiKeyListenerHandler(cmd.Namespace)
5557
cmd.linkHandler = fs.NewLinkHandler(cmd.Namespace)
5658
cmd.routerAccessHandler = fs.NewRouterAccessHandler(cmd.Namespace)
5759
cmd.accessTokenHandler = fs.NewAccessTokenHandler(cmd.Namespace)
@@ -163,6 +165,16 @@ func (cmd *CmdSystemApply) Run() error {
163165
}
164166
}
165167

168+
for _, multiKeyListener := range parsedInput.MultiKeyListener {
169+
err := cmd.multiKeyListenerHandler.Add(multiKeyListener)
170+
if err != nil {
171+
cmd.logger.Error("Error while adding multi key listener", slog.String("multikeylistener", multiKeyListener.Name), slog.Any("error", err))
172+
} else {
173+
crApplied = true
174+
fmt.Printf("MultiKeyListener %s added\n", multiKeyListener.Name)
175+
}
176+
}
177+
166178
for _, link := range parsedInput.Link {
167179
err := cmd.linkHandler.Add(link)
168180
if err != nil {

0 commit comments

Comments
 (0)