diff --git a/.github/workflows/containers.yaml b/.github/workflows/containers.yaml index 6d7e8fc2a..a2e9ff638 100644 --- a/.github/workflows/containers.yaml +++ b/.github/workflows/containers.yaml @@ -19,7 +19,7 @@ jobs: strategy: matrix: - project: [ironic] + project: [ironic, neutron] openstack: [2023.1, 2024.1] steps: @@ -44,8 +44,7 @@ jobs: - name: build and deploy container image to registry uses: docker/build-push-action@v5 with: - context: "{{defaultContext}}:containers" - file: Dockerfile.${{ matrix.project }} + file: containers/Dockerfile.${{ matrix.project }} build-args: OPENSTACK_VERSION=${{ matrix.openstack }} pull: true # ensure we always have an up to date source push: ${{ github.event_name != 'pull_request' }} diff --git a/.gitignore b/.gitignore index 940112336..26c04ece0 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ components/01-secrets/kustomization.yaml # python virtualenv .venv/ venv/ +.python-version diff --git a/.mailmap b/.mailmap index 7f7277d4f..7b3d23fd4 100644 --- a/.mailmap +++ b/.mailmap @@ -1 +1,2 @@ Nicholas Kuechler +Micah Culpepper diff --git a/components/neutron/aio-values.yaml b/components/neutron/aio-values.yaml index 1f288e8b6..274a7b157 100644 --- a/components/neutron/aio-values.yaml +++ b/components/neutron/aio-values.yaml @@ -26,8 +26,8 @@ conf: plugins: ml2_conf: ml2: - # set the default ml2 backend as the logger for testing, actual deployment should override - mechanism_drivers: logger + # set the default ml2 backend to our plugin, neutron_understack + mechanism_drivers: understack neutron: DEFAULT: # we aren't using neutron routers diff --git a/components/openstack-2023.1-jammy.yaml b/components/openstack-2023.1-jammy.yaml index 5a7e26983..de625e3ac 100644 --- a/components/openstack-2023.1-jammy.yaml +++ b/components/openstack-2023.1-jammy.yaml @@ -40,7 +40,7 @@ images: neutron_metadata: "docker.io/openstackhelm/neutron:2023.1-ubuntu_jammy" neutron_ovn_metadata: "docker.io/openstackhelm/neutron:2023.1-ubuntu_jammy" neutron_openvswitch_agent: "docker.io/openstackhelm/neutron:2023.1-ubuntu_jammy" - neutron_server: "docker.io/openstackhelm/neutron:2023.1-ubuntu_jammy" + neutron_server: "ghcr.io/rackerlabs/understack/neutron:2023.1-ubuntu_jammy" neutron_rpc_server: "docker.io/openstackhelm/neutron:2023.1-ubuntu_jammy" neutron_bagpipe_bgp: "docker.io/openstackhelm/neutron:2023.1-ubuntu_jammy" neutron_netns_cleanup_cron: "docker.io/openstackhelm/neutron:2023.1-ubuntu_jammy" diff --git a/components/openstack-2024.1-jammy.yaml b/components/openstack-2024.1-jammy.yaml index 30dc36122..5b05c6bc9 100644 --- a/components/openstack-2024.1-jammy.yaml +++ b/components/openstack-2024.1-jammy.yaml @@ -32,18 +32,18 @@ images: ironic_retrive_swift_config: "docker.io/openstackhelm/heat:2024.1-ubuntu_jammy" # neutron - neutron_db_sync: "docker.io/openstackhelm/neutron:2024.1-ubuntu_jammy" - neutron_dhcp: "docker.io/openstackhelm/neutron:2024.1-ubuntu_jammy" - neutron_l3: "docker.io/openstackhelm/neutron:2024.1-ubuntu_jammy" - neutron_l2gw: "docker.io/openstackhelm/neutron:2024.1-ubuntu_jammy" - neutron_linuxbridge_agent: "docker.io/openstackhelm/neutron:2024.1-ubuntu_jammy" - neutron_metadata: "docker.io/openstackhelm/neutron:2024.1-ubuntu_jammy" - neutron_ovn_metadata: "docker.io/openstackhelm/neutron:2024.1-ubuntu_jammy" - neutron_openvswitch_agent: "docker.io/openstackhelm/neutron:2024.1-ubuntu_jammy" - neutron_server: "docker.io/openstackhelm/neutron:2024.1-ubuntu_jammy" - neutron_rpc_server: "docker.io/openstackhelm/neutron:2024.1-ubuntu_jammy" - neutron_bagpipe_bgp: "docker.io/openstackhelm/neutron:2024.1-ubuntu_jammy" - neutron_netns_cleanup_cron: "docker.io/openstackhelm/neutron:2024.1-ubuntu_jammy" + neutron_db_sync: "ghcr.io/rackerlabs/understack/neutron:2024.1-ubuntu_jammy" + neutron_dhcp: "ghcr.io/rackerlabs/understack/neutron:2024.1-ubuntu_jammy" + neutron_l3: "ghcr.io/rackerlabs/understack/neutron:2024.1-ubuntu_jammy" + neutron_l2gw: "ghcr.io/rackerlabs/understack/neutron:2024.1-ubuntu_jammy" + neutron_linuxbridge_agent: "ghcr.io/rackerlabs/understack/neutron:2024.1-ubuntu_jammy" + neutron_metadata: "ghcr.io/rackerlabs/understack/neutron:2024.1-ubuntu_jammy" + neutron_ovn_metadata: "ghcr.io/rackerlabs/understack/neutron:2024.1-ubuntu_jammy" + neutron_openvswitch_agent: "ghcr.io/rackerlabs/understack/neutron:2024.1-ubuntu_jammy" + neutron_server: "ghcr.io/rackerlabs/understack/neutron:2024.1-ubuntu_jammy" + neutron_rpc_server: "ghcr.io/rackerlabs/understack/neutron:2024.1-ubuntu_jammy" + neutron_bagpipe_bgp: "ghcr.io/rackerlabs/understack/neutron:2024.1-ubuntu_jammy" + neutron_netns_cleanup_cron: "ghcr.io/rackerlabs/understack/neutron:2024.1-ubuntu_jammy" # nova nova_api: "docker.io/openstackhelm/nova:2024.1-ubuntu_jammy" diff --git a/containers/Dockerfile.neutron b/containers/Dockerfile.neutron new file mode 100644 index 000000000..e318d9958 --- /dev/null +++ b/containers/Dockerfile.neutron @@ -0,0 +1,7 @@ +# syntax=docker/dockerfile:1 + +ARG OPENSTACK_VERSION +FROM docker.io/openstackhelm/neutron:${OPENSTACK_VERSION}-ubuntu_jammy + +COPY python/neutron-understack /tmp/neutron-understack +RUN /var/lib/openstack/bin/python -m pip install --no-input --no-index /tmp/neutron-understack diff --git a/python/neutron-understack/README.rst b/python/neutron-understack/README.rst new file mode 100644 index 000000000..d3cd6e77e --- /dev/null +++ b/python/neutron-understack/README.rst @@ -0,0 +1 @@ +Understack neutron plugin diff --git a/python/neutron-understack/neutron_understack/__init__.py b/python/neutron-understack/neutron_understack/__init__.py new file mode 100644 index 000000000..a4e2017f0 --- /dev/null +++ b/python/neutron-understack/neutron_understack/__init__.py @@ -0,0 +1 @@ +__version__ = "0.1" diff --git a/python/neutron-understack/neutron_understack/neutron_understack_mech.py b/python/neutron-understack/neutron_understack/neutron_understack_mech.py new file mode 100644 index 000000000..a3abb082f --- /dev/null +++ b/python/neutron-understack/neutron_understack/neutron_understack_mech.py @@ -0,0 +1,146 @@ +import json +import logging +import typing +import uuid + +from neutron_lib.plugins.ml2.api import MechanismDriver, NetworkContext, PortContext, SubnetContext + +LOG = logging.getLogger(__name__) + + +def dump_context(context: typing.Union[NetworkContext, SubnetContext, PortContext]) -> dict: + # RESOURCE_ATTRIBUTE_MAP + # from neutron_lib.api.definitions import network, subnet, port, portbindings + # The properties of a NetworkContext.current are defined in network.RESOURCE_ATTRIBUTE_MAP + # The properties of a SubnetContext.current are defined in subnet.RESOURCE_ATTRIBUTE_MAP + # The properties of a PortContext.current are defined in port.RESOURCE_ATTRIBUTE_MAP + attr_map = { + NetworkContext: ("current", "original", "network_segments"), + SubnetContext: ("current", "original"), + PortContext: ( + "current", + "original", + "status", + "original_status", + "network", + "binding_levels", + "original_binding_levels", + "top_bound_segment", + "original_top_bound_segment", + "bottom_bound_segment", + "original_bottom_bound_segment", + "host", + "original_host", + "vif_type", + "original_vif_type", + "vif_details", + "original_vif_details", + "segments_to_bind", + ), + } + retval = {"errors": [], "other_attrs": []} + if isinstance(context, NetworkContext): + attrs_to_dump = attr_map[NetworkContext] + elif isinstance(context, SubnetContext): + attrs_to_dump = attr_map[SubnetContext] + elif isinstance(context, PortContext): + attrs_to_dump = attr_map[PortContext] + else: + retval["errors"].append(f"Error: unknown object type {type(context)}") + return retval + + attrs = vars(context) + for attr in attrs: + if attr in attrs_to_dump: + try: + val = getattr(context, attr) + retval.update({attr: val}) + except Exception as e: + retval["errors"].append(f"Error dumping {attr}: {str(e)}") + else: + retval["other_attrs"].append(attr) + return retval + + +def log_call(method: str, context: typing.Union[NetworkContext, SubnetContext, PortContext]) -> None: + data = dump_context(context) + data.update({"method": method}) + try: + jsondata = json.dumps(data) + except Exception as e: + LOG.error( + "failed to dump %s object to JSON on %s call: %s", + str(context), + method, + str(e), + ) + return + LOG.info("%s method called with data: %s", method, jsondata) + + +class UnderstackDriver(MechanismDriver): + # See MechanismDriver docs for resource_provider_uuid5_namespace + resource_provider_uuid5_namespace = uuid.UUID("6eae3046-4072-11ef-9bcf-d6be6370a162") + + def initialize(self): + pass + + def create_network_precommit(self, context): + log_call("create_network_precommit", context) + + def create_network_postcommit(self, context): + log_call("create_network_postcommit", context) + + def update_network_precommit(self, context): + log_call("update_network_precommit", context) + + def update_network_postcommit(self, context): + log_call("update_network_postcommit", context) + + def delete_network_precommit(self, context): + log_call("delete_network_precommit", context) + + def delete_network_postcommit(self, context): + log_call("delete_network_postcommit", context) + + def create_subnet_precommit(self, context): + log_call("create_subnet_precommit", context) + + def create_subnet_postcommit(self, context): + log_call("create_subnet_postcommit", context) + + def update_subnet_precommit(self, context): + log_call("update_subnet_precommit", context) + + def update_subnet_postcommit(self, context): + log_call("update_subnet_postcommit", context) + + def delete_subnet_precommit(self, context): + log_call("delete_subnet_precommit", context) + + def delete_subnet_postcommit(self, context): + log_call("delete_subnet_postcommit", context) + + def create_port_precommit(self, context): + log_call("create_port_precommit", context) + + def create_port_postcommit(self, context): + log_call("create_port_postcommit", context) + + def update_port_precommit(self, context): + log_call("update_port_precommit", context) + + def update_port_postcommit(self, context): + log_call("update_port_postcommit", context) + + def delete_port_precommit(self, context): + log_call("delete_port_precommit", context) + + def delete_port_postcommit(self, context): + log_call("delete_port_postcommit", context) + + def bind_port(self, context): + log_call("bind_port", context) + + def check_vlan_transparency(self, context): + log_call("check_vlan_transparency", context) diff --git a/python/neutron-understack/setup.cfg b/python/neutron-understack/setup.cfg new file mode 100644 index 000000000..f6bc2dc76 --- /dev/null +++ b/python/neutron-understack/setup.cfg @@ -0,0 +1,34 @@ +[metadata] +name = neutron-understack +author = Rackspace Technology +author_email = micah.culpepper@rackspace.com +home_page = https://github.com/rackerlabs/understack +summary = Understack ML2 Neutron Driver +python_requires = >=3.10 +classifier = + Development Status :: 3 - Alpha + Environment :: OpenStack + Intended Audience :: System Administrators + Intended Audience :: Information Technology + License :: OSI Approved :: Apache Software License + Operating System :: OS Independent + Programming Language :: Python + Programming Language :: Python :: Implementation :: CPython + Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: 3 + Programming Language :: Python :: 3.10 + + +description_file = + README.rst + +[options] +install_requires = + neutron-lib==3.* + +packages = find: + + +[options.entry_points] +neutron.ml2.mechanism_drivers = + understack = neutron_understack.neutron_understack_mech:UnderstackDriver diff --git a/python/neutron-understack/setup.py b/python/neutron-understack/setup.py new file mode 100644 index 000000000..d09211f3b --- /dev/null +++ b/python/neutron-understack/setup.py @@ -0,0 +1,28 @@ +from setuptools import setup +import codecs +import os.path + +# Obtaining version number using recommended approach from +# https://packaging.python.org/guides/single-sourcing-package-version/ +# This avoids problems that may arise from trying to import neutron_understack while it is not installed. + + +def read(rel_path): + here = os.path.abspath(os.path.dirname(__file__)) + with codecs.open(os.path.join(here, rel_path), "r") as fp: + return fp.read() + + +def get_version(rel_path): + for line in read(rel_path).splitlines(): + if line.startswith("__version__"): + delim = '"' if '"' in line else "'" + return line.split(delim)[1] + else: + raise RuntimeError("Unable to find version string.") + + +setup( + name="neutron_understack", + version=get_version("neutron_understack/__init__.py"), +)