Ensure isolation between namespaces
This patch ensures that a different security group is attached to each newly created namespace. Thus providing extra isolation between the pods allocated on the different namespaces. Implements: blueprint openshift-project-isolation-support Change-Id: Ibf63841b2a6b0c339c4c76980f1489e26af016d7
This commit is contained in:
parent
68873beaf0
commit
f02b2e99e9
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -149,11 +149,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()
|
||||
|
||||
|
@ -165,13 +167,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()
|
||||
|
@ -213,6 +213,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):
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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']
|
||||
|
|
|
@ -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),
|
||||
]
|
||||
|
||||
|
|
|
@ -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)
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue