feat(openstack-sync): full CRUD plugin for Neutron router flavors - #2217
feat(openstack-sync): full CRUD plugin for Neutron router flavors#2217haseebsyed12 wants to merge 7 commits into
Conversation
5e1c34c to
6485b41
Compare
c030b31 to
fa81b55
Compare
Code reviewFound 6 issues. The theme: a lot of machinery is built and wired into the chart, but never called. 1. Four functions have no non-test callers, so three chart env knobs are inert. understack/python/openstack-sync/openstack_sync/hooks/router_flavors.py Lines 218 to 224 in fa81b55 2. Disabling the plugin no longer disables the hook. 3. Abort-on-first-failure is back. understack/python/openstack-sync/openstack_sync/hooks/common.py Lines 218 to 222 in fa81b55 4. The hourly sync never dispatches. understack/python/openstack-sync/openstack_sync/hooks/router_flavors.py Lines 102 to 108 in fa81b55 5. Replacement service profiles are created without ownership markers. 6. understack/python/openstack-sync/openstack_sync/plugins/common.py Lines 160 to 172 in fa81b55 Checked and clear: the empty-desired-set prune guard from #2205 is present; 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
Follow-up: use stdlib
|
b732f09 to
ed4c01a
Compare
ed4c01a to
5871160
Compare
d4b163b to
57b3fca
Compare
|
|
||
| for credentials, credential_resources in grouped_resources.items(): | ||
| conn = connections[credentials] | ||
| prune_removed_flavors( |
There was a problem hiding this comment.
Prune computes its desired set only from this pod's POD_NAMESPACE snapshot but prune_removed_flavors operates on the shared Neutron cloud. With PRUNE on and two namespaces managing CRs against the same (secretName, cloudName), this deletes flavors owned by the other namespace's CRs — and the authoritative-empty path at line 408 (authoritative_empty_desired=True) wipes all managed L3_ROUTER_NAT flavors in the cloud. We only run in the openstack cloud for now so it's not a live risk, but noting it: we need to come back and scope prune to marker + namespace ownership, not just the marker.
There was a problem hiding this comment.
Well my skill went sideways here. This is merely a comment so that we don't forget about this if we start using multiple namespaces. So it'd be good to capture this as an issue and not something to fix before merging.
| # Router-flavor CRD identity | ||
| # --------------------------------------------------------------------------- | ||
| # The chart injects these from the rendered CRD when the hook has an envPrefix. | ||
| CRD_API_VERSION = os.environ["NEUTRON_ROUTER_FLAVOR_CRD_API_VERSION"] |
There was a problem hiding this comment.
These module-level env reads (the os.environ[...] hard lookups here plus the env_bool/int/float parsing further down) run at import, and hooks/router_flavors.py imports this module at load. A missing or malformed var therefore raises before the hook can emit its config — including the --config path shell-operator needs just to register the hook. A bad value should degrade, not stop the hook from loading. Defer these reads into the config-building/run path.
There was a problem hiding this comment.
This comment was supposed to be a question if this is a problem with the design of the shell-operator or not.
| LOG = logging.getLogger(__name__) | ||
|
|
||
|
|
||
| def ensure_flavor(conn: Any, name: str, service_type: str, description: str) -> Any: |
There was a problem hiding this comment.
ensure_flavor only reconciles description drift. find_flavor matches on name alone, so a pre-existing flavor with a different service_type or is_enabled=False is returned unchanged, then gets stamped with the operator marker + profile and reported Synced (and becomes prune-eligible) while still mismatched/disabled. Flagging for a follow-up — should reconcile service_type/is_enabled, or refuse to adopt a mismatched flavor.
There was a problem hiding this comment.
This is one to identify as an issue and not a blocker to merge.
| ) | ||
| for credentials in sorted(deleted_only_credentials): | ||
| secret_name, cloud_name = credentials | ||
| conn = get_openstack_connection(secret_name, cloud_name) |
There was a problem hiding this comment.
The reconcile loop at line 332 isolates connection failures per credential group, but this deleted-only prune calls get_openstack_connection/wait_for_openstack_network bare. A transient Keystone/Neutron hiccup here raises after the current resources were already patched Synced, propagates to main()'s broad except → return 1 → shell-operator re-runs the whole hook. Wrap it like the reconcile loop.
There was a problem hiding this comment.
This seems like it should be fixed. I'm also okay as a fast follow up.
| return False | ||
|
|
||
|
|
||
| def service_profile_attached_to_any_flavor(conn: Any, profile_id: str) -> bool: |
There was a problem hiding this comment.
This prune is O(profiles × flavors) in Neutron calls: service_profile_attached_to_any_flavor re-lists every L3 flavor per candidate profile, and prune_orphaned_service_profiles scans all profiles every reconcile. On a busy cloud the hourly sync hammers the API. This needs an immediate follow-up to this PR — build the flavor→profile map once per prune pass.
There was a problem hiding this comment.
This is a follow up fix up to me.
| ) | ||
|
|
||
|
|
||
| def is_prunable_flavor(conn: Any, flavor: Any) -> bool: |
There was a problem hiding this comment.
Minor: is_prunable_flavor takes conn but never uses it, so callers thread a connection through for nothing and it misleadingly implies API calls. Not worried about it — drop the param when convenient.
Implements full CRUD for Neutron router flavors in the openstack-sync operator.
Extracts shared hook/plugin logic into common modules, fixes a status patch feedback loop
Module structure
Data flow
Reconciliation Engine and Neutron Router Flavor Process