Skip to content

Commit d330bb4

Browse files
authored
Merge branch 'main' into add-network-device-credentials-config
2 parents 8122093 + eb7e2b5 commit d330bb4

11 files changed

Lines changed: 1378 additions & 0 deletions

File tree

Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
---
2+
apiVersion: apiextensions.k8s.io/v1
3+
kind: CustomResourceDefinition
4+
metadata:
5+
name: neutronsegmentranges.neutron.understack.rackspace.net
6+
spec:
7+
group: neutron.understack.rackspace.net
8+
names:
9+
kind: NeutronSegmentRange
10+
listKind: NeutronSegmentRangeList
11+
plural: neutronsegmentranges
12+
shortNames:
13+
- nsr
14+
singular: neutronsegmentrange
15+
scope: Namespaced
16+
versions:
17+
- name: v1alpha1
18+
served: true
19+
storage: true
20+
additionalPrinterColumns:
21+
- name: Name
22+
type: string
23+
jsonPath: .spec.name
24+
- name: Type
25+
type: string
26+
jsonPath: .spec.network_type
27+
- name: Physical
28+
type: string
29+
jsonPath: .spec.physical_network
30+
- name: Min
31+
type: integer
32+
jsonPath: .spec.minimum
33+
- name: Max
34+
type: integer
35+
jsonPath: .spec.maximum
36+
- name: Shared
37+
type: boolean
38+
jsonPath: .spec.shared
39+
- name: SyncStatus
40+
type: string
41+
jsonPath: .status.syncStatus
42+
- name: Age
43+
type: date
44+
jsonPath: .metadata.creationTimestamp
45+
schema:
46+
openAPIV3Schema:
47+
description: >-
48+
NeutronSegmentRange defines one Neutron network segment range
49+
(``openstack network segment range``). The operator finds or creates
50+
the operator-owned range identified by ``spec.name`` and reconciles
51+
its minimum/maximum segmentation IDs toward the spec. Creating a CR
52+
claims ownership of a range the operator creates under an ownership
53+
prefix; a range created out-of-band is not adopted. Neutron only
54+
allows updating minimum and maximum on an existing range, so a CR
55+
that disagrees on network_type, physical_network, shared or
56+
project_id fails loudly rather than silently diverging.
57+
type: object
58+
required:
59+
- spec
60+
properties:
61+
apiVersion:
62+
type: string
63+
kind:
64+
type: string
65+
metadata:
66+
type: object
67+
spec:
68+
type: object
69+
required:
70+
- name
71+
- network_type
72+
- minimum
73+
- maximum
74+
- cloudCredentialsRef
75+
properties:
76+
cloudCredentialsRef:
77+
description: >-
78+
cloudCredentialsRef points to a Kubernetes Secret containing
79+
an OpenStack clouds.yaml file. The operator reads this secret
80+
directly at reconcile time; no volume mount is required.
81+
type: object
82+
required:
83+
- secretName
84+
- cloudName
85+
properties:
86+
secretName:
87+
description: >-
88+
Name of a Secret in the same namespace as this resource.
89+
The Secret must contain a key named clouds.yaml holding
90+
an OpenStack clouds.yaml file.
91+
type: string
92+
minLength: 1
93+
maxLength: 253
94+
cloudName:
95+
description: >-
96+
Name of the cloud entry within the clouds.yaml to
97+
authenticate as.
98+
type: string
99+
minLength: 1
100+
maxLength: 256
101+
name:
102+
description: >-
103+
Neutron network segment range name. This is the identity the
104+
operator uses to find, create, and prune the range, so
105+
it must be unique per cloud. The operator prepends a 14-char
106+
ownership prefix (``understack-sr:``) before storing the name
107+
in Neutron, whose name column holds 255 chars, so the logical
108+
name is capped at 241.
109+
type: string
110+
minLength: 1
111+
maxLength: 241
112+
pattern: ^[A-Za-z0-9._/-]+$
113+
network_type:
114+
description: >-
115+
The network type of the segment range. VLAN IDs apply to the
116+
``vlan`` type; tunnel IDs apply to ``geneve``, ``gre`` and
117+
``vxlan``.
118+
type: string
119+
enum:
120+
- vlan
121+
- vxlan
122+
- gre
123+
- geneve
124+
minLength: 1
125+
maxLength: 32
126+
physical_network:
127+
description: >-
128+
The physical network the segment range is bound to. Required
129+
for ``vlan`` ranges and must be omitted for the tunnelled
130+
types (``vxlan``, ``gre``, ``geneve``); the operator enforces
131+
this at reconcile time. Neutron stores this in a 64-char
132+
column.
133+
type: string
134+
minLength: 1
135+
maxLength: 64
136+
minimum:
137+
description: >-
138+
The minimum segmentation ID in the range (inclusive). Must be
139+
less than or equal to ``maximum``.
140+
type: integer
141+
format: int64
142+
minimum: 1
143+
maximum:
144+
description: >-
145+
The maximum segmentation ID in the range (inclusive). Must be
146+
greater than or equal to ``minimum``.
147+
type: integer
148+
format: int64
149+
minimum: 1
150+
shared:
151+
description: >-
152+
Whether the segment range is shared with all projects. When
153+
false the range is scoped to ``project_id``, which then
154+
becomes required.
155+
type: boolean
156+
default: true
157+
project_id:
158+
description: >-
159+
The project the range is scoped to when ``shared`` is false.
160+
Ignored for shared ranges.
161+
type: string
162+
minLength: 1
163+
maxLength: 255
164+
x-kubernetes-validations:
165+
- rule: self.maximum >= self.minimum
166+
message: maximum must be greater than or equal to minimum
167+
- rule: "self.shared || has(self.project_id)"
168+
message: project_id is required when shared is false
169+
status:
170+
description: NeutronSegmentRangeStatus defines the observed sync state.
171+
type: object
172+
properties:
173+
syncStatus:
174+
description: SyncStatus indicates the synchronization state with Neutron.
175+
type: string
176+
enum:
177+
- Synced
178+
- Failed
179+
- Unknown
180+
lastSyncTime:
181+
description: LastSyncTime is the last time the operator attempted to sync the range.
182+
type: string
183+
format: date-time
184+
observedGeneration:
185+
description: ObservedGeneration is the metadata generation last processed by the operator.
186+
type: integer
187+
format: int64
188+
message:
189+
description: Message provides details about the last sync attempt.
190+
type: string
191+
maxLength: 2048
192+
conditions:
193+
description: Conditions describe current observed state.
194+
type: array
195+
items:
196+
type: object
197+
required:
198+
- type
199+
- status
200+
properties:
201+
type:
202+
type: string
203+
status:
204+
type: string
205+
enum:
206+
- "True"
207+
- "False"
208+
- Unknown
209+
reason:
210+
type: string
211+
message:
212+
type: string
213+
maxLength: 2048
214+
lastTransitionTime:
215+
type: string
216+
format: date-time
217+
subresources:
218+
status: {}

‎components/openstack-sync-operator/values.yaml‎

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ plugins:
3232
neutronRouterFlavors: false
3333
neutronSubnetPools: false
3434
ironicRunbooks: false
35+
neutronSegmentRanges: false
3536

3637
pluginData:
3738
openstackPlaceholder:
@@ -83,3 +84,19 @@ pluginData:
8384
# When true, removing an IronicRunbook CR also deletes its
8485
# operator-owned Ironic runbook. Enable this before removing the CR.
8586
PRUNE: false
87+
88+
neutronSegmentRanges:
89+
hook:
90+
path: /hooks/segment_ranges.py
91+
crd: crds/neutron.understack.rackspace.net_neutronsegmentranges.yaml
92+
envPrefix: NEUTRON_SEGMENT_RANGE
93+
env:
94+
SYNC_CRONTAB: "0 * * * *"
95+
# Neutron readiness wait before a segment range reconcile fails.
96+
# Total wait is READY_RETRIES * READY_DELAY seconds.
97+
READY_RETRIES: 30
98+
READY_DELAY: 10
99+
# When true, removing a NeutronSegmentRange CR also deletes its
100+
# operator-managed OpenStack segment range. Enable this before
101+
# removing the CR.
102+
PRUNE: false

‎containers/openstack-sync-operator/Dockerfile‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ COPY --chmod=755 python/openstack-sync/openstack_sync/hooks/placeholder.py /hook
2020
COPY --chmod=755 python/openstack-sync/openstack_sync/hooks/router_flavors.py /hooks/router_flavors.py
2121
COPY --chmod=755 python/openstack-sync/openstack_sync/hooks/subnet_pools.py /hooks/subnet_pools.py
2222
COPY --chmod=755 python/openstack-sync/openstack_sync/hooks/ironic_runbooks.py /hooks/ironic_runbooks.py
23+
COPY --chmod=755 python/openstack-sync/openstack_sync/hooks/segment_ranges.py /hooks/segment_ranges.py
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
#!/usr/bin/env python3
2+
"""Shell-operator hook for Neutron network segment range reconciliation."""
3+
4+
from __future__ import annotations
5+
6+
import sys
7+
from typing import Any
8+
9+
from openstack_sync.hooks.framework import HookConfig
10+
from openstack_sync.hooks.framework import ReconcileResult
11+
from openstack_sync.hooks.framework import SyncPlugin
12+
from openstack_sync.hooks.framework import build_crd_hook_config
13+
from openstack_sync.hooks.framework import hook_enabled
14+
from openstack_sync.hooks.framework import hook_inputs
15+
from openstack_sync.hooks.framework import run_hook
16+
from openstack_sync.hooks.framework import run_sync
17+
from openstack_sync.plugins.common import wait_for_openstack_network
18+
from openstack_sync.plugins.neutron.segment_ranges import prune as prune_module
19+
from openstack_sync.plugins.neutron.segment_ranges import reconcile as reconcile_module
20+
from openstack_sync.plugins.neutron.segment_ranges.config import BINDING_NAME
21+
from openstack_sync.plugins.neutron.segment_ranges.config import ENV_PREFIX
22+
23+
24+
class SegmentRangePlugin(SyncPlugin):
25+
"""Sync NeutronSegmentRange CRs into Neutron network segment ranges."""
26+
27+
noun = "segment range"
28+
29+
def wait_for_api(self, conn: Any) -> None:
30+
wait_for_openstack_network(
31+
conn,
32+
retries=self.config.ready_retries,
33+
delay=self.config.ready_delay,
34+
)
35+
36+
def new_cache(self) -> reconcile_module.RangeCache:
37+
# Keyed by managed range name and shared across every CR in one
38+
# credential group, so the managed-range listing is fetched once and
39+
# reused by each reconcile in the group. Prune does not receive this
40+
# cache (SyncPlugin.prune / PruneRequest carry no cache) and re-lists
41+
# from Neutron itself.
42+
return {}
43+
44+
def reconcile(
45+
self, conn: Any, spec: dict[str, Any], cache: reconcile_module.RangeCache
46+
) -> ReconcileResult:
47+
notes = reconcile_module.sync_segment_range(conn, spec, cache)
48+
return ReconcileResult(notes=notes)
49+
50+
def prune(
51+
self,
52+
conn: Any,
53+
desired_specs: list[dict[str, Any]],
54+
*,
55+
authoritative_empty: bool,
56+
) -> None:
57+
# The framework only calls prune under a cleanup policy that runs it,
58+
# which for this plugin means the chart's PRUNE flag is enabled (see
59+
# SyncPlugin.cleanup_policy: config.prune + a real prune step ->
60+
# FINALIZED_PRUNE). No manual gate on config.prune is needed here.
61+
prune_module.prune_removed_ranges(
62+
conn, desired_specs, authoritative_empty=authoritative_empty
63+
)
64+
65+
66+
def main() -> int:
67+
def run(contexts: list[dict[str, Any]]) -> int:
68+
if not hook_enabled(ENV_PREFIX):
69+
return 0
70+
config = HookConfig.from_env(ENV_PREFIX, binding_name=BINDING_NAME)
71+
return run_sync(SegmentRangePlugin(config), hook_inputs(contexts, config))
72+
73+
return run_hook(lambda: build_crd_hook_config(ENV_PREFIX, BINDING_NAME), run)
74+
75+
76+
if __name__ == "__main__":
77+
sys.exit(main())
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Neutron network segment range sync plugin."""
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
"""Segment-range plugin constants.
2+
3+
Runtime configuration comes from
4+
:class:`openstack_sync.hooks.framework.HookConfig`, built from the
5+
``NEUTRON_SEGMENT_RANGE`` env prefix the Helm chart injects. The values here are
6+
not configurable at runtime.
7+
"""
8+
9+
from __future__ import annotations
10+
11+
#: Env prefix the Helm chart uses for this plugin's variables.
12+
ENV_PREFIX = "NEUTRON_SEGMENT_RANGE"
13+
14+
#: shell-operator binding label for the CRD watch.
15+
BINDING_NAME = "neutron-segment-ranges"
16+
17+
#: Network types Neutron binds to a physical network. VLAN ranges require
18+
#: ``physical_network``; the tunnelled types must omit it.
19+
PHYSICAL_NETWORK_TYPES = frozenset({"vlan"})
20+
21+
#: Network types carried over a tunnel, which must not set ``physical_network``.
22+
TUNNEL_NETWORK_TYPES = frozenset({"vxlan", "gre", "geneve"})

0 commit comments

Comments
 (0)