diff --git a/devstack/lib/kuryr_kubernetes b/devstack/lib/kuryr_kubernetes index 5b19e0a17..d78b5ff25 100644 --- a/devstack/lib/kuryr_kubernetes +++ b/devstack/lib/kuryr_kubernetes @@ -28,8 +28,8 @@ function ovs_bind_for_kubelet() { project_id="$1" port_number="$2" security_group=$(openstack security group list \ - --project "$project_id" -f value | \ - awk '/default/ {print $1}') + --project "$project_id" -c ID -c Name -f value | \ + awk '{if ($2=="default") print $1}') port_id=$(openstack port create \ --device-owner compute:kuryr \ --project "$project_id" \ @@ -49,6 +49,11 @@ function ovs_bind_for_kubelet() { "$KURYR_K8S_OCTAVIA_MEMBER_MODE" == "L2" ]]; then openstack port set "$port_id" --security-group octavia_pod_access fi + if [[ "$KURYR_SG_DRIVER" == "namespace" ]]; then + openstack port set "$port_id" --security-group allow_from_namespace + openstack port set "$port_id" --security-group allow_from_default + fi + ifname="kubelet${port_id}" ifname="${ifname:0:14}" service_subnet_cidr=$(openstack --os-cloud devstack-admin \ @@ -73,9 +78,16 @@ function ovs_bind_for_kubelet() { sudo ip link set dev "$ifname" address "$port_mac" sudo ip link set dev "$ifname" up for ((i=0; i < ${#port_ips[@]}; i++)); do - prefix=$(openstack subnet show "${port_subnets[$i]}" \ - -c cidr -f value | \ - cut -f2 -d/) + if [[ "$KURYR_SG_DRIVER" == "namespace" ]]; then + subnetpool_id=$(openstack subnet show "${port_subnets[$i]}" \ + -c subnetpool_id -f value | cut -f2) + prefix=$(openstack subnet pool show "${subnetpool_id}" \ + -c prefixes -f value | cut -f2 -d/) + else + prefix=$(openstack subnet show "${port_subnets[$i]}" \ + -c cidr -f value | \ + cut -f2 -d/) + fi sudo ip addr add "${port_ips[$i]}/${prefix}" dev "$ifname" done sudo ip route add "$service_subnet_cidr" via "$pod_subnet_gw" dev "$ifname" diff --git a/devstack/plugin.sh b/devstack/plugin.sh index e5dd5631d..90674c1ac 100644 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -71,6 +71,7 @@ function configure_kuryr { iniset "$KURYR_CONFIG" kubernetes port_debug "$KURYR_PORT_DEBUG" iniset "$KURYR_CONFIG" kubernetes pod_subnets_driver "$KURYR_SUBNET_DRIVER" + iniset "$KURYR_CONFIG" kubernetes pod_security_groups_driver "$KURYR_SG_DRIVER" iniset "$KURYR_CONFIG" kubernetes enabled_handlers "$KURYR_ENABLED_HANDLERS" # Let Kuryr retry connections to K8s API for 20 minutes. @@ -278,6 +279,8 @@ function configure_neutron_defaults { local subnetpool_id local router local router_id + local ext_svc_net_id + local ext_svc_subnet_id # If a subnetpool is not passed, we get the one created in devstack's # Neutron module @@ -307,8 +310,10 @@ function configure_neutron_defaults { service_subnet_id="$(openstack subnet show -c id -f value \ "${KURYR_NEUTRON_DEFAULT_SERVICE_SUBNET}")" - sg_ids=$(echo $(openstack security group list \ - --project "$project_id" -c ID -f value) | tr ' ' ',') + if [ "$KURYR_SG_DRIVER" != "namespace" ]; then + sg_ids=$(echo $(openstack security group list \ + --project "$project_id" -c ID -f value) | tr ' ' ',') + fi ext_svc_net_id="$(openstack network show -c id -f value \ "${KURYR_NEUTRON_DEFAULT_EXT_SVC_NET}")" @@ -334,7 +339,11 @@ function configure_neutron_defaults { --description "k8s service subnet allowed" \ --remote-ip "$service_cidr" --ethertype IPv4 --protocol tcp \ "$service_pod_access_sg_id" - sg_ids+=",${service_pod_access_sg_id}" + if [ -n "$sg_ids" ]; then + sg_ids+=",${service_pod_access_sg_id}" + else + sg_ids="${service_pod_access_sg_id}" + fi elif [[ "$use_octavia" == "True" && \ "$KURYR_K8S_OCTAVIA_MEMBER_MODE" == "L2" ]]; then # In case the member connectivity is L2, Octavia by default uses the @@ -357,7 +366,11 @@ function configure_neutron_defaults { --description "k8s pod subnet allowed from k8s-pod-subnet" \ --remote-ip "$pod_cidr" --ethertype IPv4 --protocol tcp \ "$octavia_pod_access_sg_id" - sg_ids+=",${octavia_pod_access_sg_id}" + if [ -n "$sg_ids" ]; then + sg_ids+=",${octavia_pod_access_sg_id}" + else + sg_ids="${octavia_pod_access_sg_id}" + fi fi KURYR_K8S_CONTAINERIZED_DEPLOYMENT=$(trueorfalse False KURYR_K8S_CONTAINERIZED_DEPLOYMENT) @@ -384,6 +397,31 @@ function configure_neutron_defaults { iniset "$KURYR_CONFIG" namespace_subnet pod_subnet_pool "$subnetpool_id" iniset "$KURYR_CONFIG" namespace_subnet pod_router "$router_id" fi + if [ "$KURYR_SG_DRIVER" == "namespace" ]; then + local allow_namespace_sg_id + local allow_default_sg_id + allow_namespace_sg_id=$(openstack --os-cloud devstack-admin \ + --os-region "$REGION_NAME" \ + security group create --project "$project_id" \ + allow_from_namespace -f value -c id) + allow_default_sg_id=$(openstack --os-cloud devstack-admin \ + --os-region "$REGION_NAME" \ + security group create --project "$project_id" \ + allow_from_default -f value -c id) + openstack --os-cloud devstack-admin --os-region "$REGION_NAME" \ + security group rule create --project "$project_id" \ + --description "allow traffic from default namespace" \ + --remote-group "$allow_namespace_sg_id" --ethertype IPv4 --protocol tcp \ + "$allow_default_sg_id" + openstack --os-cloud devstack-admin --os-region "$REGION_NAME" \ + security group rule create --project "$project_id" \ + --description "allow traffic from namespaces at default namespace" \ + --remote-group "$allow_default_sg_id" --ethertype IPv4 --protocol tcp \ + "$allow_namespace_sg_id" + + iniset "$KURYR_CONFIG" namespace_sg sg_allow_from_namespaces "$allow_namespace_sg_id" + iniset "$KURYR_CONFIG" namespace_sg sg_allow_from_default "$allow_default_sg_id" + fi if [ -n "$OVS_BRIDGE" ]; then iniset "$KURYR_CONFIG" neutron_defaults ovs_bridge "$OVS_BRIDGE" fi @@ -409,7 +447,7 @@ function configure_k8s_pod_sg_rules { --os-region "$REGION_NAME" \ security group list \ --project "$project_id" -c ID -c Name -f value | \ - awk '/default/ {print $1}') + awk '{if ($2=="default") print $1}') create_k8s_icmp_sg_rules "$sg_id" ingress } diff --git a/devstack/settings b/devstack/settings index ba99efbc4..766acd7b2 100644 --- a/devstack/settings +++ b/devstack/settings @@ -48,6 +48,7 @@ KURYR_K8S_API_CACERT=${KURYR_K8S_API_CACERT:-"${KURYR_HYPERKUBE_DATA_DIR}/kuryr- KURYR_K8S_API_LB_PORT=${KURYR_K8S_API_LB_PORT:-443} KURYR_PORT_DEBUG=${KURYR_PORT_DEBUG:-True} KURYR_SUBNET_DRIVER=${KURYR_SUBNET_DRIVER:-default} +KURYR_SG_DRIVER=${KURYR_SG_DRIVER:-default} KURYR_ENABLED_HANDLERS=${KURYR_ENABLED_HANDLERS:-vif,lb,lbaasspec} # OpenShift diff --git a/doc/source/installation/network_namespace.rst b/doc/source/installation/network_namespace.rst index 72a3d1831..f2309979c 100644 --- a/doc/source/installation/network_namespace.rst +++ b/doc/source/installation/network_namespace.rst @@ -20,6 +20,15 @@ the next steps are needed: pod_subnets_driver = namespace + In addition, to ensure that pods at one given namespace cannot reach (or be + reached by) the ones at another namespace, except the pods at the default + namespace that can reach (and be reached by) any pod at a different + namespace, the next security group driver needs to be set too:: + + [kubernetes] + pod_security_groups_driver = namespace + + 3. Select (and create if needed) the subnet pool from where the new subnets will get their CIDR (e.g., the default on devstack deployment is shared-default-subnetpool-v4):: @@ -40,6 +49,15 @@ the next steps are needed: the default subnet driver. +5. Select (and create if needed) the security groups to be attached to the + pods at the default namespace and to the others, enabling the cross access + between them:: + + [namespace_sg] + sg_allow_from_namespaces = SG_ID_1 # Makes SG_ID_1 allow traffic from the sg sg_allow_from_default + sg_allow_from_default = SG_ID_2 # Makes SG_ID_2 allow traffic from the sg sg_allow_from_namespaces + + Note you need to restart the kuryr controller after applying the above detailed steps. For devstack non-containerized deployments:: @@ -56,6 +74,7 @@ For directly enabling the driver when deploying with devstack, you just need to add the namespace handler and state the namespace subnet driver with:: KURYR_SUBNET_DRIVER=namespace + KURYR_SG_DRIVER=namespace KURYR_ENABLED_HANDLERS=vif,lb,lbaasspec,namespace diff --git a/kuryr_kubernetes/controller/drivers/base.py b/kuryr_kubernetes/controller/drivers/base.py index dd2eaa401..c1dac22c3 100644 --- a/kuryr_kubernetes/controller/drivers/base.py +++ b/kuryr_kubernetes/controller/drivers/base.py @@ -150,11 +150,13 @@ class PodSubnetsDriver(DriverBase): """ raise NotImplementedError() - def create_namespace_network(self, namespace): + def create_namespace_network(self, namespace, project_id): """Create network resources for a namespace. :param namespace: string with the namespace name - :return: CRD KuryrNet dict + :param project_id: OpenStack project ID + :return: dict with the keys and values for the CRD spec, such as + routerId or subnetId """ raise NotImplementedError() @@ -166,13 +168,11 @@ class PodSubnetsDriver(DriverBase): """ raise NotImplementedError() - def rollback_network_resources(self, router_id, net_id, subnet_id, - namespace): + def rollback_network_resources(self, crd_spec, namespace): """Rollback created network resources for a namespace. - :param router_id: OpenStack router ID where the network is connected - :param net_id: OpenStack network ID - :param subnet_id: OpenStack subnet ID + :param crd_spec: dict with the keys and values for the CRD spec, such + as routerId or subnetId :param namespace: name of the Kubernetes namespace object """ raise NotImplementedError() @@ -214,6 +214,22 @@ class PodSecurityGroupsDriver(DriverBase): """ raise NotImplementedError() + def create_namespace_sg(self, namespace, project_id): + """Create security group resources for a namespace. + + :param namespace: string with the namespace name + :param project_id: OpenStack project ID + :return: dict with the keys and values for the CRD spec, such as sgId + """ + raise NotImplementedError() + + def delete_sg(self, sg_id): + """Delete security group associated to a namespace. + + :param sg_id: OpenStack security group ID + """ + raise NotImplementedError() + @six.add_metaclass(abc.ABCMeta) class ServiceSecurityGroupsDriver(DriverBase): diff --git a/kuryr_kubernetes/controller/drivers/namespace_security_groups.py b/kuryr_kubernetes/controller/drivers/namespace_security_groups.py new file mode 100644 index 000000000..b28257ca5 --- /dev/null +++ b/kuryr_kubernetes/controller/drivers/namespace_security_groups.py @@ -0,0 +1,130 @@ +# Copyright (c) 2018 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from kuryr.lib._i18n import _ +from oslo_config import cfg +from oslo_log import log as logging + +from kuryr_kubernetes import clients +from kuryr_kubernetes import config +from kuryr_kubernetes import constants +from kuryr_kubernetes.controller.drivers import base +from kuryr_kubernetes import exceptions + +from neutronclient.common import exceptions as n_exc + +LOG = logging.getLogger(__name__) + +namespace_sg_driver_opts = [ + cfg.StrOpt('sg_allow_from_namespaces', + help=_("Default security group to allow traffic from the " + "namespaces into the default namespace.")), + cfg.StrOpt('sg_allow_from_default', + help=_("Default security group to allow traffic from the " + "default namespaces into the other namespaces.")) +] + +cfg.CONF.register_opts(namespace_sg_driver_opts, "namespace_sg") + +DEFAULT_NAMESPACE = 'default' + + +class NamespacePodSecurityGroupsDriver(base.PodSecurityGroupsDriver): + """Provides security groups for Pod based on a configuration option.""" + + def get_security_groups(self, pod, project_id): + namespace = pod['metadata']['namespace'] + net_crd = self._get_net_crd(namespace) + + sg_list = config.CONF.neutron_defaults.pod_security_groups + sg_list.append(str(net_crd['spec']['sgId'])) + + extra_sgs = self._get_extra_sg(namespace) + for sg in extra_sgs: + sg_list.append(str(sg)) + + return sg_list[:] + + def _get_net_crd(self, namespace): + kubernetes = clients.get_kubernetes_client() + + try: + ns = kubernetes.get('%s/namespaces/%s' % (constants.K8S_API_BASE, + namespace)) + except exceptions.K8sClientException: + LOG.exception("Kubernetes Client Exception.") + raise exceptions.ResourceNotReady(namespace) + try: + annotations = ns['metadata']['annotations'] + net_crd_name = annotations[constants.K8S_ANNOTATION_NET_CRD] + except KeyError: + LOG.exception("Namespace missing CRD annotations for selecting " + "the corresponding security group.") + raise exceptions.ResourceNotReady(namespace) + try: + net_crd = kubernetes.get('%s/kuryrnets/%s' % ( + constants.K8S_API_CRD, net_crd_name)) + except exceptions.K8sClientException: + LOG.exception("Kubernetes Client Exception.") + raise + + return net_crd + + def _get_extra_sg(self, namespace): + # Differentiates between default namespace and the rest + if namespace == DEFAULT_NAMESPACE: + return [cfg.CONF.namespace_sg.sg_allow_from_namespaces] + else: + return [cfg.CONF.namespace_sg.sg_allow_from_default] + + def create_namespace_sg(self, namespace, project_id): + neutron = clients.get_neutron_client() + + sg_name = "ns/" + namespace + "-sg" + # create the associated SG for the namespace + try: + # default namespace is different from the rest + # Default allows traffic from everywhere + # The rest can be accessed from the default one + sg = neutron.create_security_group( + { + "security_group": { + "name": sg_name, + "project_id": project_id + } + }).get('security_group') + neutron.create_security_group_rule( + { + "security_group_rule": { + "direction": "ingress", + "remote_group_id": sg['id'], + "security_group_id": sg['id'] + } + }) + except n_exc.NeutronClientException as ex: + LOG.error("Error creating security group for the namespace " + "%s: %s", namespace, ex) + raise ex + return {'sgId': sg['id']} + + def delete_sg(self, sg_id): + neutron = clients.get_neutron_client() + try: + neutron.delete_security_group(sg_id) + except n_exc.NotFound: + LOG.debug("Security Group not found: %s", sg_id) + except n_exc.NeutronClientException: + LOG.exception("Error deleting security group %s.", sg_id) + raise diff --git a/kuryr_kubernetes/controller/drivers/namespace_subnet.py b/kuryr_kubernetes/controller/drivers/namespace_subnet.py index 7b553c371..3ba664006 100644 --- a/kuryr_kubernetes/controller/drivers/namespace_subnet.py +++ b/kuryr_kubernetes/controller/drivers/namespace_subnet.py @@ -72,18 +72,10 @@ class NamespacePodSubnetDriver(default_subnet.DefaultPodSubnetDriver): return net_crd['spec']['subnetId'] - def delete_namespace_subnet(self, net_crd_name): - router_id = oslo_cfg.CONF.namespace_subnet.pod_router - + def delete_namespace_subnet(self, net_crd): neutron = clients.get_neutron_client() - kubernetes = clients.get_kubernetes_client() - try: - net_crd = kubernetes.get('%s/kuryrnets/%s' % ( - constants.K8S_API_CRD, net_crd_name)) - except exceptions.K8sClientException: - LOG.exception("Kubernetes Client Exception.") - raise + router_id = oslo_cfg.CONF.namespace_subnet.pod_router subnet_id = net_crd['spec']['subnetId'] net_id = net_crd['spec']['netId'] @@ -111,8 +103,6 @@ class NamespacePodSubnetDriver(default_subnet.DefaultPodSubnetDriver): LOG.exception("Error deleting network %s.", net_id) raise - self._del_kuryrnet_crd(net_crd_name) - def create_namespace_network(self, namespace, project_id): neutron = clients.get_neutron_client() @@ -151,64 +141,19 @@ class NamespacePodSubnetDriver(default_subnet.DefaultPodSubnetDriver): LOG.error("Error creating neutron resources for the namespace " "%s: %s", namespace, ex) raise ex + return {'netId': neutron_net['id'], + 'routerId': router_id, + 'subnetId': neutron_subnet['id']} - # create CRD resource for the network - try: - net_crd = self._add_kuryrnet_crd(namespace, neutron_net['id'], - router_id, neutron_subnet['id']) - except exceptions.K8sClientException: - LOG.exception("Kuryrnet CRD could not be added. Rolling back " - "network resources created for the namespace.") - self.rollback_network_resources(router_id, neutron_net['id'], - neutron_subnet['id'], namespace) - raise - return net_crd - - def rollback_network_resources(self, router_id, net_id, subnet_id, - namespace): + def rollback_network_resources(self, net_crd_spec, namespace): neutron = clients.get_neutron_client() try: - neutron.remove_interface_router(router_id, - {'subnet_id': subnet_id}) - neutron.delete_network(net_id) + neutron.remove_interface_router(net_crd_spec['routerId'], + {'subnet_id': + net_crd_spec['subnetId']}) + neutron.delete_network(net_crd_spec['netId']) except n_exc.NeutronClientException: LOG.exception("Failed to clean up network resources associated to " "%(net_id)s, created for the namespace: " - "%(namespace)s." % {'net_id': net_id, + "%(namespace)s." % {'net_id': net_crd_spec['netId'], 'namespace': namespace}) - - def _add_kuryrnet_crd(self, namespace, net_id, router_id, subnet_id): - kubernetes = clients.get_kubernetes_client() - net_crd_name = "ns-" + namespace - net_crd = { - 'apiVersion': 'openstack.org/v1', - 'kind': 'KuryrNet', - 'metadata': { - 'name': net_crd_name, - 'annotations': { - 'namespaceName': namespace, - } - }, - 'spec': { - 'netId': net_id, - 'routerId': router_id, - 'subnetId': subnet_id, - }, - } - try: - kubernetes.post('%s/kuryrnets' % constants.K8S_API_CRD, net_crd) - except exceptions.K8sClientException: - LOG.exception("Kubernetes Client Exception creating kuryrnet " - "CRD.") - raise - return net_crd - - def _del_kuryrnet_crd(self, net_crd_name): - kubernetes = clients.get_kubernetes_client() - try: - kubernetes.delete('%s/kuryrnets/%s' % (constants.K8S_API_CRD, - net_crd_name)) - except exceptions.K8sClientException: - LOG.exception("Kubernetes Client Exception deleting kuryrnet " - "CRD.") - raise diff --git a/kuryr_kubernetes/controller/handlers/namespace.py b/kuryr_kubernetes/controller/handlers/namespace.py index be4870e36..c5ce29126 100644 --- a/kuryr_kubernetes/controller/handlers/namespace.py +++ b/kuryr_kubernetes/controller/handlers/namespace.py @@ -20,6 +20,8 @@ from kuryr_kubernetes.controller.drivers import base as drivers from kuryr_kubernetes import exceptions from kuryr_kubernetes.handlers import k8s_base +from neutronclient.common import exceptions as n_exc + LOG = logging.getLogger(__name__) @@ -31,6 +33,7 @@ class NamespaceHandler(k8s_base.ResourceEventHandler): super(NamespaceHandler, self).__init__() self._drv_project = drivers.NamespaceProjectDriver.get_instance() self._drv_subnets = drivers.PodSubnetsDriver.get_instance() + self._drv_sg = drivers.PodSecurityGroupsDriver.get_instance() self._drv_vif_pool = drivers.VIFPoolDriver.get_instance( driver_alias='multi_pool') self._drv_vif_pool.set_vif_driver() @@ -38,42 +41,65 @@ class NamespaceHandler(k8s_base.ResourceEventHandler): def on_present(self, namespace): ns_name = namespace['metadata']['name'] project_id = self._drv_project.get_project(namespace) - net_crd = self._get_net_crd(namespace) - if net_crd: + net_crd_id = self._get_net_crd_id(namespace) + if net_crd_id: LOG.debug("CRD existing at the new namespace") return LOG.debug("Creating network resources for namespace: %s", ns_name) - net_crd = self._drv_subnets.create_namespace_network(ns_name, - project_id) + net_crd_spec = self._drv_subnets.create_namespace_network(ns_name, + project_id) try: + net_crd_sg = self._drv_sg.create_namespace_sg(ns_name, project_id) + except n_exc.NeutronClientException: + LOG.exception("Error creating security group for the namespace. " + "Rolling back created network resources.") + self._drv_subnets.rollback_network_resources(net_crd_spec, ns_name) + raise + net_crd_spec.update(net_crd_sg) + + # create CRD resource for the network + try: + net_crd = self._add_kuryrnet_crd(ns_name, net_crd_spec) self._set_net_crd(namespace, net_crd) except exceptions.K8sClientException: - LOG.exception("Failed to set annotation") - crd_spec = net_crd['spec'] - self._drv_subnets.rollback_network_resources( - crd_spec['routerId'], crd_spec['netId'], crd_spec['subnetId'], - ns_name) + LOG.exception("Kuryrnet CRD could not be added. Rolling back " + "network resources created for the namespace.") + self._drv_subnets.rollback_network_resources(net_crd_spec, ns_name) + self._drv_sg.delete_sg(net_crd_sg['sgId']) def on_deleted(self, namespace): LOG.debug("Deleting namespace: %s", namespace) - net_crd = self._get_net_crd(namespace) - if not net_crd: + net_crd_id = self._get_net_crd_id(namespace) + if not net_crd_id: LOG.warning("There is no CRD annotated at the namespace %s", namespace) return + net_crd = self._get_net_crd(net_crd_id) - net_id = self._get_net_id_from_net_crd(net_crd) - self._drv_vif_pool.delete_network_pools(net_id) + self._drv_vif_pool.delete_network_pools(net_crd['spec']['netId']) self._drv_subnets.delete_namespace_subnet(net_crd) + self._drv_sg.delete_sg(net_crd['spec']['sgId']) - def _get_net_crd(self, namespace): + self._del_kuryrnet_crd(net_crd_id) + + def _get_net_crd_id(self, namespace): try: annotations = namespace['metadata']['annotations'] - net_crd = annotations[constants.K8S_ANNOTATION_NET_CRD] + net_crd_id = annotations[constants.K8S_ANNOTATION_NET_CRD] except KeyError: return None - return net_crd + return net_crd_id + + def _get_net_crd(self, net_crd_id): + k8s = clients.get_kubernetes_client() + try: + kuryrnet_crd = k8s.get('%s/kuryrnets/%s' % (constants.K8S_API_CRD, + net_crd_id)) + except exceptions.K8sClientException: + LOG.exception("Kubernetes Client Exception.") + raise + return kuryrnet_crd def _set_net_crd(self, namespace, net_crd): LOG.debug("Setting CRD annotations: %s", net_crd) @@ -84,12 +110,35 @@ class NamespaceHandler(k8s_base.ResourceEventHandler): net_crd['metadata']['name']}, resource_version=namespace['metadata']['resourceVersion']) - def _get_net_id_from_net_crd(self, net_crd): - k8s = clients.get_kubernetes_client() + def _add_kuryrnet_crd(self, namespace, net_crd_spec): + kubernetes = clients.get_kubernetes_client() + net_crd_name = "ns-" + namespace + spec = {k: v for k, v in net_crd_spec.items()} + net_crd = { + 'apiVersion': 'openstack.org/v1', + 'kind': 'KuryrNet', + 'metadata': { + 'name': net_crd_name, + 'annotations': { + 'namespaceName': namespace, + } + }, + 'spec': spec, + } try: - kuryrnet_crd = k8s.get('%s/kuryrnets/%s' % (constants.K8S_API_CRD, - net_crd)) + kubernetes.post('%s/kuryrnets' % constants.K8S_API_CRD, net_crd) except exceptions.K8sClientException: - LOG.exception("Kubernetes Client Exception.") + LOG.exception("Kubernetes Client Exception creating kuryrnet " + "CRD.") + raise + return net_crd + + def _del_kuryrnet_crd(self, net_crd_name): + kubernetes = clients.get_kubernetes_client() + try: + kubernetes.delete('%s/kuryrnets/%s' % (constants.K8S_API_CRD, + net_crd_name)) + except exceptions.K8sClientException: + LOG.exception("Kubernetes Client Exception deleting kuryrnet " + "CRD.") raise - return kuryrnet_crd['spec']['netId'] diff --git a/kuryr_kubernetes/opts.py b/kuryr_kubernetes/opts.py index 52a2b2f44..019be4c49 100644 --- a/kuryr_kubernetes/opts.py +++ b/kuryr_kubernetes/opts.py @@ -17,6 +17,7 @@ from kuryr.lib import opts as lib_opts from kuryr_kubernetes.cni import health as cni_health from kuryr_kubernetes import config from kuryr_kubernetes.controller.drivers import default_subnet +from kuryr_kubernetes.controller.drivers import namespace_security_groups from kuryr_kubernetes.controller.drivers import namespace_subnet from kuryr_kubernetes.controller.drivers import nested_vif from kuryr_kubernetes.controller.drivers import vif_pool @@ -38,6 +39,7 @@ _kuryr_k8s_opts = [ ('health_server', health.health_server_opts), ('cni_health_server', cni_health.cni_health_server_opts), ('namespace_subnet', namespace_subnet.namespace_subnet_driver_opts), + ('namespace_sg', namespace_security_groups.namespace_sg_driver_opts), ('ingress', config.ingress), ] diff --git a/kuryr_kubernetes/tests/unit/controller/drivers/test_namespace_security_groups.py b/kuryr_kubernetes/tests/unit/controller/drivers/test_namespace_security_groups.py new file mode 100644 index 000000000..41a6ec9bd --- /dev/null +++ b/kuryr_kubernetes/tests/unit/controller/drivers/test_namespace_security_groups.py @@ -0,0 +1,181 @@ +# Copyright (c) 2018 Red Hat, Inc. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import mock + +from kuryr_kubernetes import constants +from kuryr_kubernetes.controller.drivers import namespace_security_groups +from kuryr_kubernetes.tests import base as test_base +from kuryr_kubernetes.tests.unit import kuryr_fixtures as k_fix + +from neutronclient.common import exceptions as n_exc + + +def get_pod_obj(): + return { + 'status': { + 'qosClass': 'BestEffort', + 'hostIP': '192.168.1.2', + }, + 'kind': 'Pod', + 'spec': { + 'schedulerName': 'default-scheduler', + 'containers': [{ + 'name': 'busybox', + 'image': 'busybox', + 'resources': {} + }], + 'nodeName': 'kuryr-devstack' + }, + 'metadata': { + 'name': 'busybox-sleep1', + 'namespace': 'default', + 'resourceVersion': '53808', + 'selfLink': '/api/v1/namespaces/default/pods/busybox-sleep1', + 'uid': '452176db-4a85-11e7-80bd-fa163e29dbbb', + 'annotations': { + 'openstack.org/kuryr-vif': {} + } + }} + + +def get_namespace_obj(): + return { + 'metadata': { + 'annotations': { + constants.K8S_ANNOTATION_NET_CRD: 'net_crd_url_sample' + } + } + } + + +class TestNamespacePodSecurityGroupsDriver(test_base.TestCase): + + @mock.patch('kuryr_kubernetes.config.CONF') + def test_get_security_groups(self, m_cfg): + cls = namespace_security_groups.NamespacePodSecurityGroupsDriver + m_driver = mock.MagicMock(spec=cls) + + pod = get_pod_obj() + project_id = mock.sentinel.project_id + sg_list = [mock.sentinel.sg_id] + m_cfg.neutron_defaults.pod_security_groups = sg_list + sg_id = mock.sentinel.sg_id + extra_sg = mock.sentinel.extra_sg + net_crd = { + 'spec': { + 'sgId': sg_id + } + } + m_driver._get_net_crd.return_value = net_crd + m_driver._get_extra_sg.return_value = [extra_sg] + + ret = cls.get_security_groups(m_driver, pod, project_id) + expected_sg = [sg_list[0], str(sg_id), str(extra_sg)] + + self.assertEqual(ret, expected_sg) + + def test__get_net_crd(self): + cls = namespace_security_groups.NamespacePodSecurityGroupsDriver + m_driver = mock.MagicMock(spec=cls) + + namespace = mock.sentinel.namespace + subnet_id = mock.sentinel.subnet_id + net_id = mock.sentinel.net_id + sg_id = mock.sentinel.sg_id + ns = get_namespace_obj() + + crd = { + 'spec': { + 'netId': net_id, + 'subnetId': subnet_id, + 'sgId': sg_id + } + } + + kubernetes = self.useFixture(k_fix.MockK8sClient()).client + kubernetes.get.side_effect = [ns, crd] + + ret = cls._get_net_crd(m_driver, namespace) + + self.assertEqual(ret, crd) + + def test_create_namespace_sg(self): + cls = namespace_security_groups.NamespacePodSecurityGroupsDriver + m_driver = mock.MagicMock(spec=cls) + + namespace = 'test' + project_id = mock.sentinel.project_id + sg = {'id': mock.sentinel.sg} + neutron = self.useFixture(k_fix.MockNeutronClient()).client + neutron.create_security_group.return_value = {'security_group': sg} + + create_sg_resp = cls.create_namespace_sg(m_driver, namespace, + project_id) + + self.assertEqual(create_sg_resp, {'sgId': sg['id']}) + neutron.create_security_group.assert_called_once() + neutron.create_security_group_rule.assert_called_once() + + def test_create_namespace_sg_exception(self): + cls = namespace_security_groups.NamespacePodSecurityGroupsDriver + m_driver = mock.MagicMock(spec=cls) + + namespace = 'test' + project_id = mock.sentinel.project_id + neutron = self.useFixture(k_fix.MockNeutronClient()).client + neutron.create_security_group.side_effect = ( + n_exc.NeutronClientException) + + self.assertRaises(n_exc.NeutronClientException, + cls.create_namespace_sg, m_driver, + namespace, project_id) + + neutron.create_security_group.assert_called_once() + neutron.create_security_group_rule.assert_not_called() + + def test_delete_sg(self): + cls = namespace_security_groups.NamespacePodSecurityGroupsDriver + m_driver = mock.MagicMock(spec=cls) + neutron = self.useFixture(k_fix.MockNeutronClient()).client + + sg_id = mock.sentinel.sg_id + + cls.delete_sg(m_driver, sg_id) + neutron.delete_security_group.assert_called_once_with(sg_id) + + def test_delete_sg_exception(self): + cls = namespace_security_groups.NamespacePodSecurityGroupsDriver + m_driver = mock.MagicMock(spec=cls) + neutron = self.useFixture(k_fix.MockNeutronClient()).client + + sg_id = mock.sentinel.sg_id + neutron.delete_security_group.side_effect = ( + n_exc.NeutronClientException) + + self.assertRaises(n_exc.NeutronClientException, cls.delete_sg, + m_driver, sg_id) + neutron.delete_security_group.assert_called_once_with(sg_id) + + def test_delete_sg_not_found(self): + cls = namespace_security_groups.NamespacePodSecurityGroupsDriver + m_driver = mock.MagicMock(spec=cls) + neutron = self.useFixture(k_fix.MockNeutronClient()).client + + sg_id = mock.sentinel.sg_id + neutron.delete_security_group.side_effect = n_exc.NotFound + + cls.delete_sg(m_driver, sg_id) + neutron.delete_security_group.assert_called_once_with(sg_id) diff --git a/kuryr_kubernetes/tests/unit/controller/drivers/test_namespace_subnet.py b/kuryr_kubernetes/tests/unit/controller/drivers/test_namespace_subnet.py index 1911b2142..3db8350cd 100644 --- a/kuryr_kubernetes/tests/unit/controller/drivers/test_namespace_subnet.py +++ b/kuryr_kubernetes/tests/unit/controller/drivers/test_namespace_subnet.py @@ -22,6 +22,7 @@ from kuryr_kubernetes.tests import base as test_base from kuryr_kubernetes.tests.unit import kuryr_fixtures as k_fix from neutronclient.common import exceptions as n_exc +from oslo_config import cfg as oslo_cfg def get_pod_obj(): @@ -176,69 +177,20 @@ class TestNamespacePodSubnetDriver(test_base.TestCase): cls = subnet_drv.NamespacePodSubnetDriver m_driver = mock.MagicMock(spec=cls) - net_crd_name = 'test' net_id = mock.sentinel.net_id subnet_id = mock.sentinel.subnet_id + sg_id = mock.sentinel.sg_id crd = { 'spec': { 'subnetId': subnet_id, - 'netId': net_id + 'netId': net_id, + 'sgId': sg_id } } neutron = self.useFixture(k_fix.MockNeutronClient()).client - kubernetes = self.useFixture(k_fix.MockK8sClient()).client - kubernetes.get.return_value = crd + cls.delete_namespace_subnet(m_driver, crd) - cls.delete_namespace_subnet(m_driver, net_crd_name) - - kubernetes.get.assert_called_once() - m_driver._del_kuryrnet_crd.assert_called_once_with(net_crd_name) - neutron.remove_interface_router.assert_called_once() - neutron.delete_network.assert_called_once_with(net_id) - - def test_delete_namespace_subnet_k8s_get_exception(self): - cls = subnet_drv.NamespacePodSubnetDriver - m_driver = mock.MagicMock(spec=cls) - - net_crd_name = 'test' - neutron = self.useFixture(k_fix.MockNeutronClient()).client - kubernetes = self.useFixture(k_fix.MockK8sClient()).client - - kubernetes.get.side_effect = k_exc.K8sClientException - - self.assertRaises(k_exc.K8sClientException, - cls.delete_namespace_subnet, m_driver, net_crd_name) - - kubernetes.get.assert_called_once() - m_driver._del_kuryrnet_crd.assert_not_called() - neutron.remove_interface_router.assert_not_called() - neutron.delete_network.assert_not_called() - - def test_delete_namespace_subnet_k8s_del_crd_exception(self): - cls = subnet_drv.NamespacePodSubnetDriver - m_driver = mock.MagicMock(spec=cls) - - net_crd_name = 'test' - net_id = mock.sentinel.net_id - subnet_id = mock.sentinel.subnet_id - crd = { - 'spec': { - 'subnetId': subnet_id, - 'netId': net_id - } - } - neutron = self.useFixture(k_fix.MockNeutronClient()).client - kubernetes = self.useFixture(k_fix.MockK8sClient()).client - - kubernetes.get.return_value = crd - m_driver._del_kuryrnet_crd.side_effect = k_exc.K8sClientException - - self.assertRaises(k_exc.K8sClientException, - cls.delete_namespace_subnet, m_driver, net_crd_name) - - kubernetes.get.assert_called_once() - m_driver._del_kuryrnet_crd.assert_called_once_with(net_crd_name) neutron.remove_interface_router.assert_called_once() neutron.delete_network.assert_called_once_with(net_id) @@ -246,28 +198,24 @@ class TestNamespacePodSubnetDriver(test_base.TestCase): cls = subnet_drv.NamespacePodSubnetDriver m_driver = mock.MagicMock(spec=cls) - net_crd_name = 'test' net_id = mock.sentinel.net_id subnet_id = mock.sentinel.subnet_id + sg_id = mock.sentinel.sg_id crd = { 'spec': { 'subnetId': subnet_id, - 'netId': net_id + 'netId': net_id, + 'sgId': sg_id } } neutron = self.useFixture(k_fix.MockNeutronClient()).client - kubernetes = self.useFixture(k_fix.MockK8sClient()).client - - kubernetes.get.return_value = crd neutron.delete_network.side_effect = n_exc.NetworkInUseClient self.assertRaises(k_exc.ResourceNotReady, - cls.delete_namespace_subnet, m_driver, net_crd_name) + cls.delete_namespace_subnet, m_driver, crd) - kubernetes.get.assert_called_once() neutron.remove_interface_router.assert_called_once() neutron.delete_network.assert_called_once_with(net_id) - m_driver._del_kuryrnet_crd.assert_not_called() def test_create_namespace_network(self): cls = subnet_drv.NamespacePodSubnetDriver @@ -280,8 +228,13 @@ class TestNamespacePodSubnetDriver(test_base.TestCase): neutron.create_network.return_value = {'network': net} subnet = {'id': mock.sentinel.subnet} neutron.create_subnet.return_value = {'subnet': subnet} - net_crd = mock.sentinel.net_crd - m_driver._add_kuryrnet_crd.return_value = net_crd + router_id = 'router1' + oslo_cfg.CONF.set_override('pod_router', + router_id, + group='namespace_subnet') + net_crd = {'netId': net['id'], + 'routerId': router_id, + 'subnetId': subnet['id']} net_crd_resp = cls.create_namespace_network(m_driver, namespace, project_id) @@ -290,7 +243,6 @@ class TestNamespacePodSubnetDriver(test_base.TestCase): neutron.create_network.assert_called_once() neutron.create_subnet.assert_called_once() neutron.add_interface_router.assert_called_once() - m_driver._add_kuryrnet_crd.assert_called_once() def test_create_namespace_network_net_exception(self): cls = subnet_drv.NamespacePodSubnetDriver @@ -309,7 +261,6 @@ class TestNamespacePodSubnetDriver(test_base.TestCase): neutron.create_network.assert_called_once() neutron.create_subnet.assert_not_called() neutron.add_interface_router.assert_not_called() - m_driver._add_kuryrnet_crd.assert_not_called() def test_create_namespace_network_subnet_exception(self): cls = subnet_drv.NamespacePodSubnetDriver @@ -329,10 +280,8 @@ class TestNamespacePodSubnetDriver(test_base.TestCase): neutron.create_network.assert_called_once() neutron.create_subnet.assert_called_once() neutron.add_interface_router.assert_not_called() - m_driver._add_kuryrnet_crd.assert_not_called() def test_create_namespace_network_router_exception(self): - pass cls = subnet_drv.NamespacePodSubnetDriver m_driver = mock.MagicMock(spec=cls) @@ -353,30 +302,6 @@ class TestNamespacePodSubnetDriver(test_base.TestCase): neutron.create_network.assert_called_once() neutron.create_subnet.assert_called_once() neutron.add_interface_router.assert_called_once() - m_driver._add_kuryrnet_crd.assert_not_called() - - def test_create_namespace_network_crd_exception(self): - cls = subnet_drv.NamespacePodSubnetDriver - m_driver = mock.MagicMock(spec=cls) - - namespace = 'test' - project_id = mock.sentinel.project_id - neutron = self.useFixture(k_fix.MockNeutronClient()).client - net = {'id': mock.sentinel.net} - neutron.create_network.return_value = {'network': net} - subnet = {'id': mock.sentinel.subnet} - neutron.create_subnet.return_value = {'subnet': subnet} - m_driver._add_kuryrnet_crd.side_effect = k_exc.K8sClientException - - self.assertRaises(k_exc.K8sClientException, - cls.create_namespace_network, m_driver, namespace, - project_id) - - neutron.create_network.assert_called_once() - neutron.create_subnet.assert_called_once() - neutron.add_interface_router.assert_called_once() - m_driver._add_kuryrnet_crd.assert_called_once() - m_driver.rollback_network_resources.assert_called_once() def test_rollback_network_resources(self): cls = subnet_drv.NamespacePodSubnetDriver @@ -385,12 +310,18 @@ class TestNamespacePodSubnetDriver(test_base.TestCase): router_id = mock.sentinel.router_id net_id = mock.sentinel.net_id subnet_id = mock.sentinel.subnet_id + sg_id = mock.sentinel.sg_id + crd_spec = { + 'subnetId': subnet_id, + 'routerId': router_id, + 'netId': net_id, + 'sgId': sg_id + } namespace = mock.sentinel.namespace neutron = self.useFixture(k_fix.MockNeutronClient()).client - cls.rollback_network_resources(m_driver, router_id, net_id, - subnet_id, namespace) + cls.rollback_network_resources(m_driver, crd_spec, namespace) neutron.remove_interface_router.assert_called_with( router_id, {'subnet_id': subnet_id}) neutron.delete_network.assert_called_with(net_id) @@ -402,14 +333,20 @@ class TestNamespacePodSubnetDriver(test_base.TestCase): router_id = mock.sentinel.router_id net_id = mock.sentinel.net_id subnet_id = mock.sentinel.subnet_id + sg_id = mock.sentinel.sg_id + crd_spec = { + 'subnetId': subnet_id, + 'routerId': router_id, + 'netId': net_id, + 'sgId': sg_id + } namespace = mock.sentinel.namespace neutron = self.useFixture(k_fix.MockNeutronClient()).client neutron.remove_interface_router.side_effect = ( n_exc.NeutronClientException) - cls.rollback_network_resources(m_driver, router_id, net_id, - subnet_id, namespace) + cls.rollback_network_resources(m_driver, crd_spec, namespace) neutron.remove_interface_router.assert_called_with( router_id, {'subnet_id': subnet_id}) neutron.delete_network.assert_not_called() diff --git a/kuryr_kubernetes/tests/unit/controller/handlers/test_namespace.py b/kuryr_kubernetes/tests/unit/controller/handlers/test_namespace.py index cb61cea73..60a7716b1 100644 --- a/kuryr_kubernetes/tests/unit/controller/handlers/test_namespace.py +++ b/kuryr_kubernetes/tests/unit/controller/handlers/test_namespace.py @@ -13,7 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -import ddt import mock from neutronclient.common import exceptions as n_exc @@ -25,7 +24,6 @@ from kuryr_kubernetes import exceptions as k_exc from kuryr_kubernetes.tests import base as test_base -@ddt.ddt class TestNamespaceHandler(test_base.TestCase): def setUp(self): @@ -50,6 +48,7 @@ class TestNamespaceHandler(test_base.TestCase): self._handler._drv_project = mock.Mock( spec=drivers.NamespaceProjectDriver) self._handler._drv_subnets = mock.Mock(spec=drivers.PodSubnetsDriver) + self._handler._drv_sg = mock.Mock(spec=drivers.PodSecurityGroupsDriver) self._handler._drv_vif_pool = mock.MagicMock( spec=vif_pool.MultiVIFPool) @@ -60,10 +59,15 @@ class TestNamespaceHandler(test_base.TestCase): self._handler._drv_subnets.create_namespace_network) self._delete_namespace_subnet = ( self._handler._drv_subnets.delete_namespace_subnet) + self._create_namespace_sg = ( + self._handler._drv_sg.create_namespace_sg) + self._delete_sg = ( + self._handler._drv_sg.delete_sg) self._get_net_crd = self._handler._get_net_crd self._set_net_crd = self._handler._set_net_crd - self._get_net_id_from_net_crd = ( - self._handler._get_net_id_from_net_crd) + self._get_net_crd_id = self._handler._get_net_crd_id + self._add_kuryrnet_crd = self._handler._add_kuryrnet_crd + self._del_kuryrnet_crd = self._handler._del_kuryrnet_crd self._rollback_network_resources = ( self._handler._drv_subnets.rollback_network_resources) self._delete_network_pools = ( @@ -79,89 +83,144 @@ class TestNamespaceHandler(test_base.TestCase): 'routerId': mock.sentinel.router_id, 'netId': mock.sentinel.net_id, 'subnetId': mock.sentinel.subnet_id, + 'sgId': mock.sentinel.sg_id, } } return crd @mock.patch.object(drivers.VIFPoolDriver, 'get_instance') + @mock.patch.object(drivers.PodSecurityGroupsDriver, 'get_instance') @mock.patch.object(drivers.PodSubnetsDriver, 'get_instance') @mock.patch.object(drivers.NamespaceProjectDriver, 'get_instance') def test_init(self, m_get_project_driver, m_get_subnets_driver, - m_get_vif_pool_driver): + m_get_sg_driver, m_get_vif_pool_driver): project_driver = mock.sentinel.project_driver subnets_driver = mock.sentinel.subnets_driver + sg_driver = mock.sentinel.sg_driver vif_pool_driver = mock.Mock(spec=vif_pool.MultiVIFPool) m_get_project_driver.return_value = project_driver m_get_subnets_driver.return_value = subnets_driver + m_get_sg_driver.return_value = sg_driver m_get_vif_pool_driver.return_value = vif_pool_driver handler = namespace.NamespaceHandler() self.assertEqual(project_driver, handler._drv_project) self.assertEqual(subnets_driver, handler._drv_subnets) + self.assertEqual(sg_driver, handler._drv_sg) self.assertEqual(vif_pool_driver, handler._drv_vif_pool) def test_on_present(self): net_crd = self._get_crd() - - self._get_net_crd.return_value = None - self._create_namespace_network.return_value = net_crd + self._get_net_crd_id.return_value = None + self._create_namespace_network.return_value = {'test_net': 'uuid'} + self._create_namespace_sg.return_value = {'test_sg': 'uuid'} + net_crd_spec = {'test_net': 'uuid', 'test_sg': 'uuid'} + self._add_kuryrnet_crd.return_value = net_crd namespace.NamespaceHandler.on_present(self._handler, self._namespace) - self._get_net_crd.assert_called_once_with(self._namespace) + self._get_net_crd_id.assert_called_once_with(self._namespace) self._create_namespace_network.assert_called_once_with( self._namespace_name, self._project_id) + self._create_namespace_sg.assert_called_once_with( + self._namespace_name, self._project_id) + self._add_kuryrnet_crd.assert_called_once_with(self._namespace_name, + net_crd_spec) self._set_net_crd.assert_called_once_with(self._namespace, net_crd) self._rollback_network_resources.assert_not_called() def test_on_present_existing(self): - net_crd = self._get_crd() - - self._get_net_crd.return_value = net_crd + net_crd_id = mock.sentinel.net_crd_id + self._get_net_crd_id.return_value = net_crd_id namespace.NamespaceHandler.on_present(self._handler, self._namespace) - self._get_net_crd.assert_called_once_with(self._namespace) + self._get_net_crd_id.assert_called_once_with(self._namespace) self._create_namespace_network.assert_not_called() - self._set_net_crd.assert_not_called() - self._rollback_network_resources.assert_not_called() - @ddt.data((n_exc.NeutronClientException), (k_exc.K8sClientException)) - def test_on_present_create_exception(self, m_create_net): - self._get_net_crd.return_value = None - self._create_namespace_network.side_effect = m_create_net + def test_on_present_create_network_exception(self): + self._get_net_crd_id.return_value = None + self._create_namespace_network.side_effect = ( + n_exc.NeutronClientException) - self.assertRaises(m_create_net, namespace.NamespaceHandler.on_present, + self.assertRaises(n_exc.NeutronClientException, + namespace.NamespaceHandler.on_present, self._handler, self._namespace) - self._get_net_crd.assert_called_once_with(self._namespace) + self._get_net_crd_id.assert_called_once_with(self._namespace) self._create_namespace_network.assert_called_once_with( self._namespace_name, self._project_id) + self._create_namespace_sg.assert_not_called() self._set_net_crd.assert_not_called() - self._rollback_network_resources.assert_not_called() + + def test_on_present_create_sg_exception(self): + self._get_net_crd_id.return_value = None + self._create_namespace_network.return_value = {'test_net': 'uuid'} + self._create_namespace_sg.side_effect = ( + n_exc.NeutronClientException) + + self.assertRaises(n_exc.NeutronClientException, + namespace.NamespaceHandler.on_present, + self._handler, self._namespace) + + self._get_net_crd_id.assert_called_once_with(self._namespace) + self._create_namespace_network.assert_called_once_with( + self._namespace_name, self._project_id) + self._create_namespace_sg.assert_called_once_with( + self._namespace_name, self._project_id) + self._set_net_crd.assert_not_called() + + def test_on_present_add_kuryrnet_crd_exception(self): + self._get_net_crd_id.return_value = None + self._create_namespace_network.return_value = {'test_net': 'uuid'} + self._create_namespace_sg.return_value = {'sgId': 'uuid'} + net_crd_spec = {'test_net': 'uuid', 'sgId': 'uuid'} + self._add_kuryrnet_crd.side_effect = k_exc.K8sClientException + + namespace.NamespaceHandler.on_present(self._handler, self._namespace) + + self._get_net_crd_id.assert_called_once_with(self._namespace) + self._create_namespace_network.assert_called_once_with( + self._namespace_name, self._project_id) + self._create_namespace_sg.assert_called_once_with( + self._namespace_name, self._project_id) + self._add_kuryrnet_crd.assert_called_once_with(self._namespace_name, + net_crd_spec) + self._set_net_crd.assert_not_called() + self._rollback_network_resources.assert_called_once() def test_on_present_set_crd_exception(self): net_crd = self._get_crd() - self._get_net_crd.return_value = None - self._create_namespace_network.return_value = net_crd + self._get_net_crd_id.return_value = None + self._create_namespace_network.return_value = {'test_net': 'uuid'} + self._create_namespace_sg.return_value = {'sgId': 'uuid'} + net_crd_spec = {'test_net': 'uuid', 'sgId': 'uuid'} + self._add_kuryrnet_crd.return_value = net_crd self._set_net_crd.side_effect = k_exc.K8sClientException namespace.NamespaceHandler.on_present(self._handler, self._namespace) - self._get_net_crd.assert_called_once_with(self._namespace) + self._get_net_crd_id.assert_called_once_with(self._namespace) self._create_namespace_network.assert_called_once_with( self._namespace_name, self._project_id) + self._create_namespace_sg.assert_called_once_with( + self._namespace_name, self._project_id) + self._add_kuryrnet_crd.assert_called_once_with(self._namespace_name, + net_crd_spec) self._set_net_crd.assert_called_once_with(self._namespace, net_crd) self._rollback_network_resources.assert_called_once() def test_on_present_rollback_exception(self): net_crd = self._get_crd() - self._get_net_crd.return_value = None - self._create_namespace_network.return_value = net_crd + self._get_net_crd_id.return_value = None + self._create_namespace_network.return_value = {'test_net': 'uuid'} + self._create_namespace_sg.return_value = {'sgId': 'uuid'} + net_crd_spec = {'test_net': 'uuid', 'sgId': 'uuid'} + self._add_kuryrnet_crd.return_value = net_crd self._set_net_crd.side_effect = k_exc.K8sClientException self._rollback_network_resources.side_effect = ( n_exc.NeutronClientException) @@ -170,47 +229,56 @@ class TestNamespaceHandler(test_base.TestCase): namespace.NamespaceHandler.on_present, self._handler, self._namespace) - self._get_net_crd.assert_called_once_with(self._namespace) + self._get_net_crd_id.assert_called_once_with(self._namespace) self._create_namespace_network.assert_called_once_with( self._namespace_name, self._project_id) + self._create_namespace_sg.assert_called_once_with( + self._namespace_name, self._project_id) + self._add_kuryrnet_crd.assert_called_once_with(self._namespace_name, + net_crd_spec) self._set_net_crd.assert_called_once_with(self._namespace, net_crd) self._rollback_network_resources.assert_called_once() def test_on_deleted(self): + net_crd_id = mock.sentinel.net_crd_id net_crd = self._get_crd() - net_id = mock.sentinel.net_id + self._get_net_crd_id.return_value = net_crd_id self._get_net_crd.return_value = net_crd - self._get_net_id_from_net_crd.return_value = net_id namespace.NamespaceHandler.on_deleted(self._handler, self._namespace) - self._get_net_crd.assert_called_once_with(self._namespace) - self._get_net_id_from_net_crd.assert_called_once_with(net_crd) - self._delete_network_pools.assert_called_once_with(net_id) + self._get_net_crd_id.assert_called_once_with(self._namespace) + self._get_net_crd.assert_called_once_with(net_crd_id) + self._delete_network_pools.assert_called_once_with( + net_crd['spec']['netId']) self._delete_namespace_subnet.assert_called_once_with(net_crd) + self._delete_sg.assert_called_once_with(net_crd['spec']['sgId']) + self._del_kuryrnet_crd.assert_called_once_with(net_crd_id) def test_on_deleted_missing_crd_annotation(self): - self._get_net_crd.return_value = None + self._get_net_crd_id.return_value = None namespace.NamespaceHandler.on_deleted(self._handler, self._namespace) - self._get_net_crd.assert_called_once_with(self._namespace) - self._get_net_id_from_net_crd.assert_not_called() + self._get_net_crd_id.assert_called_once_with(self._namespace) + self._get_net_crd.assert_not_called() self._delete_network_pools.assert_not_called() self._delete_namespace_subnet.assert_not_called() + self._delete_sg.assert_not_called() + self._del_kuryrnet_crd.assert_not_called() def test_on_deleted_k8s_exception(self): - net_crd = self._get_crd() + net_crd_id = mock.sentinel.net_crd_id - self._get_net_crd.return_value = net_crd - self._get_net_id_from_net_crd.side_effect = k_exc.K8sClientException + self._get_net_crd_id.return_value = net_crd_id + self._get_net_crd.side_effect = k_exc.K8sClientException self.assertRaises(k_exc.K8sClientException, namespace.NamespaceHandler.on_deleted, self._handler, self._namespace) - self._get_net_crd.assert_called_once_with(self._namespace) - self._get_net_id_from_net_crd.assert_called_once_with(net_crd) + self._get_net_crd_id.assert_called_once_with(self._namespace) + self._get_net_crd.assert_called_once_with(net_crd_id) self._delete_network_pools.assert_not_called() self._delete_namespace_subnet.assert_not_called() diff --git a/setup.cfg b/setup.cfg index 79f84fb21..319f7fc01 100644 --- a/setup.cfg +++ b/setup.cfg @@ -61,6 +61,7 @@ kuryr_kubernetes.controller.drivers.service_subnets = kuryr_kubernetes.controller.drivers.pod_security_groups = default = kuryr_kubernetes.controller.drivers.default_security_groups:DefaultPodSecurityGroupsDriver + namespace = kuryr_kubernetes.controller.drivers.namespace_security_groups:NamespacePodSecurityGroupsDriver kuryr_kubernetes.controller.drivers.service_security_groups = default = kuryr_kubernetes.controller.drivers.default_security_groups:DefaultServiceSecurityGroupsDriver