K8s Services support: LBaaSSpecHandler
This patch introduces LBaaSSpecHandler that handles K8s Service events and updates related Endpoints with LBaaSServiceSpec when necessary. Change-Id: I09a0235842edd06827437f37aeac7ca5daeb1774 Partially-Implements: blueprint kuryr-k8s-integration
This commit is contained in:
parent
f3887af958
commit
a715f9edb9
@ -36,12 +36,21 @@ k8s_opts = [
|
||||
cfg.StrOpt('pod_project_driver',
|
||||
help=_("The driver to determine OpenStack project for pod ports"),
|
||||
default='default'),
|
||||
cfg.StrOpt('service_project_driver',
|
||||
help=_("The driver to determine OpenStack project for services"),
|
||||
default='default'),
|
||||
cfg.StrOpt('pod_subnets_driver',
|
||||
help=_("The driver to determine Neutron subnets for pod ports"),
|
||||
default='default'),
|
||||
cfg.StrOpt('service_subnets_driver',
|
||||
help=_("The driver to determine Neutron subnets for services"),
|
||||
default='default'),
|
||||
cfg.StrOpt('pod_security_groups_driver',
|
||||
help=_("The driver to determine Neutron security groups for pods"),
|
||||
default='default'),
|
||||
cfg.StrOpt('service_security_groups_driver',
|
||||
help=_("The driver to determine Neutron security groups for services"),
|
||||
default='default'),
|
||||
cfg.StrOpt('pod_vif_driver',
|
||||
help=_("The driver that provides VIFs for Kubernetes Pods."),
|
||||
default='generic'),
|
||||
@ -59,6 +68,8 @@ neutron_defaults = [
|
||||
sample_default="br-int"),
|
||||
cfg.StrOpt('worker_nodes_subnet',
|
||||
help=_("Neutron subnet ID for k8s worker node vms.")),
|
||||
cfg.StrOpt('service_subnet',
|
||||
help=_("Default Neutron subnet ID for Kubernetes services")),
|
||||
]
|
||||
|
||||
|
||||
|
@ -25,6 +25,7 @@ K8S_POD_STATUS_PENDING = 'Pending'
|
||||
|
||||
K8S_ANNOTATION_PREFIX = 'openstack.org/kuryr'
|
||||
K8S_ANNOTATION_VIF = K8S_ANNOTATION_PREFIX + '-vif'
|
||||
K8S_ANNOTATION_LBAAS_SPEC = K8S_ANNOTATION_PREFIX + '-lbaas-spec'
|
||||
|
||||
K8S_OS_VIF_NOOP_PLUGIN = "noop"
|
||||
|
||||
|
@ -95,6 +95,23 @@ class PodProjectDriver(DriverBase):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class ServiceProjectDriver(DriverBase):
|
||||
"""Provides an OpenStack project ID for Kubernetes Services."""
|
||||
|
||||
ALIAS = 'service_project'
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_project(self, service):
|
||||
"""Get an OpenStack project ID for Kubernetes Service.
|
||||
|
||||
:param service: dict containing Kubernetes Service object
|
||||
:return: project ID
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class PodSubnetsDriver(DriverBase):
|
||||
"""Provides subnets for Kubernetes Pods."""
|
||||
@ -115,6 +132,26 @@ class PodSubnetsDriver(DriverBase):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class ServiceSubnetsDriver(DriverBase):
|
||||
"""Provides subnets for Kubernetes Services."""
|
||||
|
||||
ALIAS = 'service_subnets'
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_subnets(self, service, project_id):
|
||||
"""Get subnets for Service.
|
||||
|
||||
:param service: dict containing Kubernetes Pod object
|
||||
:param project_id: OpenStack project ID
|
||||
:return: dict containing the mapping 'subnet_id' -> 'network' for all
|
||||
the subnets we want to create ports on, where 'network' is an
|
||||
`os_vif.network.Network` object containing a single
|
||||
`os_vif.subnet.Subnet` object corresponding to the 'subnet_id'
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class PodSecurityGroupsDriver(DriverBase):
|
||||
"""Provides security groups for Kubernetes Pods."""
|
||||
@ -132,6 +169,23 @@ class PodSecurityGroupsDriver(DriverBase):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class ServiceSecurityGroupsDriver(DriverBase):
|
||||
"""Provides security groups for Kubernetes Services."""
|
||||
|
||||
ALIAS = 'service_security_groups'
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_security_groups(self, service, project_id):
|
||||
"""Get a list of security groups' IDs for Service.
|
||||
|
||||
:param service: dict containing Kubernetes Service object
|
||||
:param project_id: OpenStack project ID
|
||||
:return: list containing security groups' IDs
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class PodVIFDriver(DriverBase):
|
||||
"""Manages Neutron ports to provide VIFs for Kubernetes Pods."""
|
||||
|
@ -34,3 +34,20 @@ class DefaultPodProjectDriver(base.PodProjectDriver):
|
||||
cfg.OptGroup('neutron_defaults'))
|
||||
|
||||
return project_id
|
||||
|
||||
|
||||
class DefaultServiceProjectDriver(base.ServiceProjectDriver):
|
||||
"""Provides project ID for Service based on a configuration option."""
|
||||
|
||||
def get_project(self, service):
|
||||
project_id = config.CONF.neutron_defaults.project
|
||||
|
||||
if not project_id:
|
||||
# NOTE(ivc): this option is only required for
|
||||
# DefaultServiceProjectDriver and its subclasses, but it may be
|
||||
# optional for other drivers (e.g. when each namespace has own
|
||||
# project)
|
||||
raise cfg.RequiredOptError('project',
|
||||
cfg.OptGroup('neutron_defaults'))
|
||||
|
||||
return project_id
|
||||
|
@ -27,9 +27,27 @@ class DefaultPodSecurityGroupsDriver(base.PodSecurityGroupsDriver):
|
||||
|
||||
if not sg_list:
|
||||
# NOTE(ivc): this option is only required for
|
||||
# DefaultPodSecurityGroupsDriver and its subclasses, but it may be
|
||||
# optional for other drivers (e.g. when each namespace has own
|
||||
# set of security groups)
|
||||
# Default{Pod,Service}SecurityGroupsDriver and its subclasses,
|
||||
# but it may be optional for other drivers (e.g. when each
|
||||
# namespace has own set of security groups)
|
||||
raise cfg.RequiredOptError('pod_security_groups',
|
||||
cfg.OptGroup('neutron_defaults'))
|
||||
|
||||
return sg_list[:]
|
||||
|
||||
|
||||
class DefaultServiceSecurityGroupsDriver(base.ServiceSecurityGroupsDriver):
|
||||
"""Provides security groups for Service based on a configuration option."""
|
||||
|
||||
def get_security_groups(self, service, project_id):
|
||||
# NOTE(ivc): use the same option as DefaultPodSecurityGroupsDriver
|
||||
sg_list = config.CONF.neutron_defaults.pod_security_groups
|
||||
|
||||
if not sg_list:
|
||||
# NOTE(ivc): this option is only required for
|
||||
# Default{Pod,Service}SecurityGroupsDriver and its subclasses,
|
||||
# but it may be optional for other drivers (e.g. when each
|
||||
# namespace has own set of security groups)
|
||||
raise cfg.RequiredOptError('pod_security_groups',
|
||||
cfg.OptGroup('neutron_defaults'))
|
||||
|
||||
|
@ -51,3 +51,20 @@ class DefaultPodSubnetDriver(base.PodSubnetsDriver):
|
||||
cfg.OptGroup('neutron_defaults'))
|
||||
|
||||
return {subnet_id: _get_subnet(subnet_id)}
|
||||
|
||||
|
||||
class DefaultServiceSubnetDriver(base.ServiceSubnetsDriver):
|
||||
"""Provides subnet for Service's LBaaS based on a configuration option."""
|
||||
|
||||
def get_subnets(self, service, project_id):
|
||||
subnet_id = config.CONF.neutron_defaults.service_subnet
|
||||
|
||||
if not subnet_id:
|
||||
# NOTE(ivc): this option is only required for
|
||||
# DefaultServiceSubnetDriver and its subclasses, but it may be
|
||||
# optional for other drivers (e.g. when each namespace has own
|
||||
# subnet)
|
||||
raise cfg.RequiredOptError('service_subnet',
|
||||
cfg.OptGroup('neutron_defaults'))
|
||||
|
||||
return {subnet_id: _get_subnet(subnet_id)}
|
||||
|
183
kuryr_kubernetes/controller/handlers/lbaas.py
Normal file
183
kuryr_kubernetes/controller/handlers/lbaas.py
Normal file
@ -0,0 +1,183 @@
|
||||
# Copyright (c) 2016 Mirantis, 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 _LE
|
||||
from oslo_log import log as logging
|
||||
from oslo_serialization import jsonutils
|
||||
import six
|
||||
|
||||
from kuryr_kubernetes import clients
|
||||
from kuryr_kubernetes import constants as k_const
|
||||
from kuryr_kubernetes.controller.drivers import base as drv_base
|
||||
from kuryr_kubernetes import exceptions as k_exc
|
||||
from kuryr_kubernetes.handlers import k8s_base
|
||||
from kuryr_kubernetes.objects import lbaas as obj_lbaas
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class LBaaSSpecHandler(k8s_base.ResourceEventHandler):
|
||||
"""LBaaSSpecHandler handles K8s Service events.
|
||||
|
||||
LBaaSSpecHandler handles K8s Service events and updates related Endpoints
|
||||
with LBaaSServiceSpec when necessary.
|
||||
"""
|
||||
|
||||
OBJECT_KIND = k_const.K8S_OBJ_SERVICE
|
||||
|
||||
def __init__(self):
|
||||
self._drv_project = drv_base.ServiceProjectDriver.get_instance()
|
||||
self._drv_subnets = drv_base.ServiceSubnetsDriver.get_instance()
|
||||
self._drv_sg = drv_base.ServiceSecurityGroupsDriver.get_instance()
|
||||
|
||||
def on_present(self, service):
|
||||
lbaas_spec = self._get_lbaas_spec(service)
|
||||
|
||||
if self._has_lbaas_spec_changes(service, lbaas_spec):
|
||||
lbaas_spec = self._generate_lbaas_spec(service)
|
||||
self._set_lbaas_spec(service, lbaas_spec)
|
||||
|
||||
def _get_service_ip(self, service):
|
||||
spec = service['spec']
|
||||
if spec.get('type') == 'ClusterIP':
|
||||
return spec.get('clusterIP')
|
||||
return None
|
||||
|
||||
def _get_subnet_id(self, service, project_id, ip):
|
||||
subnets_mapping = self._drv_subnets.get_subnets(service, project_id)
|
||||
subnet_ids = {
|
||||
subnet_id
|
||||
for subnet_id, network in six.iteritems(subnets_mapping)
|
||||
for subnet in network.subnets.objects
|
||||
if ip in subnet.cidr}
|
||||
|
||||
if len(subnet_ids) != 1:
|
||||
raise k_exc.IntegrityError(_LE(
|
||||
"Found %(num)s subnets for service %(link)s IP %(ip)s") % {
|
||||
'link': service['metadata']['selfLink'],
|
||||
'ip': ip,
|
||||
'num': len(subnet_ids)})
|
||||
|
||||
return subnet_ids.pop()
|
||||
|
||||
def _generate_lbaas_spec(self, service):
|
||||
project_id = self._drv_project.get_project(service)
|
||||
ip = self._get_service_ip(service)
|
||||
subnet_id = self._get_subnet_id(service, project_id, ip)
|
||||
ports = self._generate_lbaas_port_specs(service)
|
||||
sg_ids = self._drv_sg.get_security_groups(service, project_id)
|
||||
|
||||
return obj_lbaas.LBaaSServiceSpec(ip=ip,
|
||||
project_id=project_id,
|
||||
subnet_id=subnet_id,
|
||||
ports=ports,
|
||||
security_groups_ids=sg_ids)
|
||||
|
||||
def _has_lbaas_spec_changes(self, service, lbaas_spec):
|
||||
return (self._has_ip_changes(service, lbaas_spec) or
|
||||
self._has_port_changes(service, lbaas_spec))
|
||||
|
||||
def _get_service_ports(self, service):
|
||||
return [{'name': port.get('name'),
|
||||
'protocol': port.get('protocol', 'TCP'),
|
||||
'port': port['port']}
|
||||
for port in service['spec']['ports']]
|
||||
|
||||
def _has_port_changes(self, service, lbaas_spec):
|
||||
link = service['metadata']['selfLink']
|
||||
|
||||
fields = obj_lbaas.LBaaSPortSpec.fields
|
||||
svc_port_set = {tuple(port[attr] for attr in fields)
|
||||
for port in self._get_service_ports(service)}
|
||||
spec_port_set = {tuple(getattr(port, attr) for attr in fields)
|
||||
for port in lbaas_spec.ports}
|
||||
|
||||
if svc_port_set != spec_port_set:
|
||||
LOG.debug("LBaaS spec ports %(spec_ports)s != %(svc_ports)s "
|
||||
"for %(link)s" % {'spec_ports': spec_port_set,
|
||||
'svc_ports': svc_port_set,
|
||||
'link': link})
|
||||
return svc_port_set != spec_port_set
|
||||
|
||||
def _has_ip_changes(self, service, lbaas_spec):
|
||||
link = service['metadata']['selfLink']
|
||||
svc_ip = self._get_service_ip(service)
|
||||
|
||||
if not lbaas_spec:
|
||||
if svc_ip:
|
||||
LOG.debug("LBaaS spec is missing for %(link)s"
|
||||
% {'link': link})
|
||||
return True
|
||||
elif str(lbaas_spec.ip) != svc_ip:
|
||||
LOG.debug("LBaaS spec IP %(spec_ip)s != %(svc_ip)s for %(link)s"
|
||||
% {'spec_ip': lbaas_spec.ip,
|
||||
'svc_ip': svc_ip,
|
||||
'link': link})
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _generate_lbaas_port_specs(self, service):
|
||||
return [obj_lbaas.LBaaSPortSpec(**port)
|
||||
for port in self._get_service_ports(service)]
|
||||
|
||||
def _get_endpoints_link(self, service):
|
||||
svc_link = service['metadata']['selfLink']
|
||||
link_parts = svc_link.split('/')
|
||||
|
||||
if link_parts[-2] != 'services':
|
||||
raise k_exc.IntegrityError(_LE(
|
||||
"Unsupported service link: %(link)s") % {
|
||||
'link': svc_link})
|
||||
link_parts[-2] = 'endpoints'
|
||||
|
||||
return "/".join(link_parts)
|
||||
|
||||
def _set_lbaas_spec(self, service, lbaas_spec):
|
||||
# TODO(ivc): extract annotation interactions
|
||||
if lbaas_spec is None:
|
||||
LOG.debug("Removing LBaaSServiceSpec annotation: %r", lbaas_spec)
|
||||
annotation = None
|
||||
else:
|
||||
lbaas_spec.obj_reset_changes(recursive=True)
|
||||
LOG.debug("Setting LBaaSServiceSpec annotation: %r", lbaas_spec)
|
||||
annotation = jsonutils.dumps(lbaas_spec.obj_to_primitive(),
|
||||
sort_keys=True)
|
||||
svc_link = service['metadata']['selfLink']
|
||||
ep_link = self._get_endpoints_link(service)
|
||||
k8s = clients.get_kubernetes_client()
|
||||
|
||||
try:
|
||||
k8s.annotate(ep_link,
|
||||
{k_const.K8S_ANNOTATION_LBAAS_SPEC: annotation})
|
||||
except k_exc.K8sClientException:
|
||||
# REVISIT(ivc): only raise ResourceNotReady for NotFound
|
||||
raise k_exc.ResourceNotReady(ep_link)
|
||||
|
||||
k8s.annotate(svc_link,
|
||||
{k_const.K8S_ANNOTATION_LBAAS_SPEC: annotation},
|
||||
resource_version=service['metadata']['resourceVersion'])
|
||||
|
||||
def _get_lbaas_spec(self, service):
|
||||
# TODO(ivc): same as '_set_lbaas_spec'
|
||||
try:
|
||||
annotations = service['metadata']['annotations']
|
||||
annotation = annotations[k_const.K8S_ANNOTATION_LBAAS_SPEC]
|
||||
except KeyError:
|
||||
return None
|
||||
obj_dict = jsonutils.loads(annotation)
|
||||
obj = obj_lbaas.LBaaSServiceSpec.obj_from_primitive(obj_dict)
|
||||
LOG.debug("Got LBaaSServiceSpec from annotation: %r", obj)
|
||||
return obj
|
@ -23,6 +23,7 @@ from oslo_service import service
|
||||
from kuryr_kubernetes import clients
|
||||
from kuryr_kubernetes import config
|
||||
from kuryr_kubernetes import constants
|
||||
from kuryr_kubernetes.controller.handlers import lbaas as h_lbaas
|
||||
from kuryr_kubernetes.controller.handlers import pipeline as h_pipeline
|
||||
from kuryr_kubernetes.controller.handlers import vif as h_vif
|
||||
from kuryr_kubernetes import objects
|
||||
@ -44,6 +45,7 @@ class KuryrK8sService(service.Service):
|
||||
for resource in ["pods", "services", "endpoints"]:
|
||||
self.watcher.add("%s/%s" % (constants.K8S_API_BASE, resource))
|
||||
pipeline.register(h_vif.VIFHandler())
|
||||
pipeline.register(h_lbaas.LBaaSSpecHandler())
|
||||
|
||||
def start(self):
|
||||
LOG.info(_LI("Service '%s' starting"), self.__class__.__name__)
|
||||
|
@ -20,10 +20,12 @@ from oslo_versionedobjects import base as obj_base
|
||||
|
||||
|
||||
@six.add_metaclass(abc.ABCMeta)
|
||||
class KuryrK8sObjectBase(obj_base.VersionedObject):
|
||||
class KuryrK8sObjectBase(obj_base.VersionedObject,
|
||||
obj_base.ComparableVersionedObject):
|
||||
|
||||
OBJ_PROJECT_NAMESPACE = 'kuryr_kubernetes'
|
||||
|
||||
def __init__(self, context=None, **kwargs):
|
||||
super(KuryrK8sObjectBase, self).__init__(context, **kwargs)
|
||||
self.obj_set_defaults()
|
||||
self.obj_reset_changes()
|
||||
|
@ -35,6 +35,21 @@ class TestDefaultPodProjectDriver(test_base.TestCase):
|
||||
def test_get_project_not_set(self):
|
||||
pod = mock.sentinel.pod
|
||||
driver = default_project.DefaultPodProjectDriver()
|
||||
msg = "value required for option project in group \[neutron_defaults\]"
|
||||
self.assertRaisesRegex(cfg.RequiredOptError, msg,
|
||||
driver.get_project, pod)
|
||||
self.assertRaises(cfg.RequiredOptError, driver.get_project, pod)
|
||||
|
||||
|
||||
class TestDefaultServiceProjectDriver(test_base.TestCase):
|
||||
|
||||
@mock.patch('kuryr_kubernetes.config.CONF')
|
||||
def test_get_project(self, m_cfg):
|
||||
project_id = mock.sentinel.project_id
|
||||
service = mock.sentinel.service
|
||||
m_cfg.neutron_defaults.project = project_id
|
||||
driver = default_project.DefaultServiceProjectDriver()
|
||||
|
||||
self.assertEqual(project_id, driver.get_project(service))
|
||||
|
||||
def test_get_project_not_set(self):
|
||||
service = mock.sentinel.service
|
||||
driver = default_project.DefaultServiceProjectDriver()
|
||||
self.assertRaises(cfg.RequiredOptError, driver.get_project, service)
|
||||
|
@ -40,8 +40,30 @@ class TestDefaultPodSecurityGroupsDriver(test_base.TestCase):
|
||||
project_id = mock.sentinel.project_id
|
||||
pod = mock.sentinel.pod
|
||||
driver = default_security_groups.DefaultPodSecurityGroupsDriver()
|
||||
msg = ("value required for option pod_security_groups in group" +
|
||||
" \[neutron_defaults\]")
|
||||
|
||||
self.assertRaisesRegex(cfg.RequiredOptError, msg,
|
||||
driver.get_security_groups, pod, project_id)
|
||||
self.assertRaises(cfg.RequiredOptError, driver.get_security_groups,
|
||||
pod, project_id)
|
||||
|
||||
|
||||
class TestDefaultServiceSecurityGroupsDriver(test_base.TestCase):
|
||||
|
||||
@mock.patch('kuryr_kubernetes.config.CONF')
|
||||
def test_get_security_groups(self, m_cfg):
|
||||
sg_list = [mock.sentinel.sg_id]
|
||||
project_id = mock.sentinel.project_id
|
||||
service = mock.sentinel.service
|
||||
m_cfg.neutron_defaults.pod_security_groups = sg_list
|
||||
driver = default_security_groups.DefaultServiceSecurityGroupsDriver()
|
||||
|
||||
ret = driver.get_security_groups(service, project_id)
|
||||
|
||||
self.assertEqual(sg_list, ret)
|
||||
self.assertIsNot(sg_list, ret)
|
||||
|
||||
def test_get_security_groups_not_set(self):
|
||||
project_id = mock.sentinel.project_id
|
||||
service = mock.sentinel.service
|
||||
driver = default_security_groups.DefaultServiceSecurityGroupsDriver()
|
||||
|
||||
self.assertRaises(cfg.RequiredOptError, driver.get_security_groups,
|
||||
service, project_id)
|
||||
|
@ -47,11 +47,39 @@ class TestDefaultPodSubnetDriver(test_base.TestCase):
|
||||
pod = mock.sentinel.pod
|
||||
project_id = mock.sentinel.project_id
|
||||
driver = default_subnet.DefaultPodSubnetDriver()
|
||||
msg = ("value required for option pod_subnet in group" +
|
||||
" \[neutron_defaults\]")
|
||||
|
||||
self.assertRaisesRegex(cfg.RequiredOptError, msg, driver.get_subnets,
|
||||
pod, project_id)
|
||||
self.assertRaises(cfg.RequiredOptError, driver.get_subnets,
|
||||
pod, project_id)
|
||||
m_get_subnet.assert_not_called()
|
||||
|
||||
|
||||
class TestDefaultServiceSubnetDriver(test_base.TestCase):
|
||||
|
||||
@mock.patch('kuryr_kubernetes.controller.drivers'
|
||||
'.default_subnet._get_subnet')
|
||||
@mock.patch('kuryr_kubernetes.config.CONF')
|
||||
def test_get_subnets(self, m_cfg, m_get_subnet):
|
||||
subnet_id = mock.sentinel.subnet_id
|
||||
subnet = mock.sentinel.subnet
|
||||
service = mock.sentinel.service
|
||||
project_id = mock.sentinel.project_id
|
||||
m_cfg.neutron_defaults.service_subnet = subnet_id
|
||||
m_get_subnet.return_value = subnet
|
||||
driver = default_subnet.DefaultServiceSubnetDriver()
|
||||
|
||||
subnets = driver.get_subnets(service, project_id)
|
||||
|
||||
self.assertEqual({subnet_id: subnet}, subnets)
|
||||
m_get_subnet.assert_called_once_with(subnet_id)
|
||||
|
||||
@mock.patch('kuryr_kubernetes.controller.drivers'
|
||||
'.default_subnet._get_subnet')
|
||||
def test_get_subnets_not_set(self, m_get_subnet):
|
||||
service = mock.sentinel.service
|
||||
project_id = mock.sentinel.project_id
|
||||
driver = default_subnet.DefaultPodSubnetDriver()
|
||||
self.assertRaises(cfg.RequiredOptError, driver.get_subnets,
|
||||
service, project_id)
|
||||
m_get_subnet.assert_not_called()
|
||||
|
||||
|
||||
|
323
kuryr_kubernetes/tests/unit/controller/handlers/test_lbaas.py
Normal file
323
kuryr_kubernetes/tests/unit/controller/handlers/test_lbaas.py
Normal file
@ -0,0 +1,323 @@
|
||||
# Copyright (c) 2016 Mirantis, 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
|
||||
import os_vif.objects.network as osv_network
|
||||
import os_vif.objects.subnet as osv_subnet
|
||||
|
||||
from kuryr_kubernetes.controller.drivers import base as drv_base
|
||||
from kuryr_kubernetes.controller.handlers import lbaas as h_lbaas
|
||||
from kuryr_kubernetes import exceptions as k_exc
|
||||
from kuryr_kubernetes.objects import lbaas as obj_lbaas
|
||||
from kuryr_kubernetes.tests import base as test_base
|
||||
|
||||
|
||||
class TestLBaaSSpecHandler(test_base.TestCase):
|
||||
|
||||
@mock.patch('kuryr_kubernetes.controller.drivers.base'
|
||||
'.ServiceSecurityGroupsDriver.get_instance')
|
||||
@mock.patch('kuryr_kubernetes.controller.drivers.base'
|
||||
'.ServiceSubnetsDriver.get_instance')
|
||||
@mock.patch('kuryr_kubernetes.controller.drivers.base'
|
||||
'.ServiceProjectDriver.get_instance')
|
||||
def test_init(self, m_get_drv_project, m_get_drv_subnets, m_get_drv_sg):
|
||||
m_get_drv_project.return_value = mock.sentinel.drv_project
|
||||
m_get_drv_subnets.return_value = mock.sentinel.drv_subnets
|
||||
m_get_drv_sg.return_value = mock.sentinel.drv_sg
|
||||
|
||||
handler = h_lbaas.LBaaSSpecHandler()
|
||||
|
||||
self.assertEqual(mock.sentinel.drv_project, handler._drv_project)
|
||||
self.assertEqual(mock.sentinel.drv_subnets, handler._drv_subnets)
|
||||
self.assertEqual(mock.sentinel.drv_sg, handler._drv_sg)
|
||||
|
||||
def test_on_present(self):
|
||||
svc_event = mock.sentinel.svc_event
|
||||
old_spec = mock.sentinel.old_spec
|
||||
new_spec = mock.sentinel.new_spec
|
||||
|
||||
m_handler = mock.Mock(spec=h_lbaas.LBaaSSpecHandler)
|
||||
m_handler._get_lbaas_spec.return_value = old_spec
|
||||
m_handler._has_lbaas_spec_changes.return_value = True
|
||||
m_handler._generate_lbaas_spec.return_value = new_spec
|
||||
|
||||
h_lbaas.LBaaSSpecHandler.on_present(m_handler, svc_event)
|
||||
|
||||
m_handler._get_lbaas_spec.assert_called_once_with(svc_event)
|
||||
m_handler._has_lbaas_spec_changes.assert_called_once_with(svc_event,
|
||||
old_spec)
|
||||
m_handler._generate_lbaas_spec.assert_called_once_with(svc_event)
|
||||
m_handler._set_lbaas_spec.assert_called_once_with(svc_event, new_spec)
|
||||
|
||||
def test_on_present_no_changes(self):
|
||||
svc_event = mock.sentinel.svc_event
|
||||
old_spec = mock.sentinel.old_spec
|
||||
|
||||
m_handler = mock.Mock(spec=h_lbaas.LBaaSSpecHandler)
|
||||
m_handler._get_lbaas_spec.return_value = old_spec
|
||||
m_handler._has_lbaas_spec_changes.return_value = False
|
||||
|
||||
h_lbaas.LBaaSSpecHandler.on_present(m_handler, svc_event)
|
||||
|
||||
m_handler._get_lbaas_spec.assert_called_once_with(svc_event)
|
||||
m_handler._has_lbaas_spec_changes.assert_called_once_with(svc_event,
|
||||
old_spec)
|
||||
m_handler._generate_lbaas_spec.assert_not_called()
|
||||
m_handler._set_lbaas_spec.assert_not_called()
|
||||
|
||||
def test_get_service_ip(self):
|
||||
svc_body = {'spec': {'type': 'ClusterIP',
|
||||
'clusterIP': mock.sentinel.cluster_ip}}
|
||||
m_handler = mock.Mock(spec=h_lbaas.LBaaSSpecHandler)
|
||||
|
||||
ret = h_lbaas.LBaaSSpecHandler._get_service_ip(m_handler, svc_body)
|
||||
self.assertEqual(mock.sentinel.cluster_ip, ret)
|
||||
|
||||
def test_get_service_ip_not_cluster_ip(self):
|
||||
svc_body = {'spec': {'type': 'notClusterIP',
|
||||
'clusterIP': mock.sentinel.cluster_ip}}
|
||||
m_handler = mock.Mock(spec=h_lbaas.LBaaSSpecHandler)
|
||||
|
||||
ret = h_lbaas.LBaaSSpecHandler._get_service_ip(m_handler, svc_body)
|
||||
self.assertEqual(None, ret)
|
||||
|
||||
def _make_test_net_obj(self, cidr_list):
|
||||
subnets = [osv_subnet.Subnet(cidr=cidr) for cidr in cidr_list]
|
||||
subnets_list = osv_subnet.SubnetList(objects=subnets)
|
||||
return osv_network.Network(subnets=subnets_list)
|
||||
|
||||
def test_get_subnet_id(self):
|
||||
test_ip = '1.2.3.4'
|
||||
test_cidr = '1.2.3.0/24'
|
||||
m_handler = mock.Mock(spec=h_lbaas.LBaaSSpecHandler)
|
||||
m_drv_subnets = mock.Mock(spec=drv_base.ServiceSubnetsDriver)
|
||||
m_handler._drv_subnets = m_drv_subnets
|
||||
m_drv_subnets.get_subnets.return_value = {
|
||||
mock.sentinel.subnet_id: self._make_test_net_obj([test_cidr])
|
||||
}
|
||||
|
||||
self.assertEqual(mock.sentinel.subnet_id,
|
||||
h_lbaas.LBaaSSpecHandler._get_subnet_id(
|
||||
m_handler,
|
||||
mock.sentinel.service,
|
||||
mock.sentinel.project_id,
|
||||
test_ip))
|
||||
m_drv_subnets.get_subnets.assert_called_once_with(
|
||||
mock.sentinel.service, mock.sentinel.project_id)
|
||||
|
||||
def test_get_subnet_id_invalid(self):
|
||||
test_ip = '1.2.3.4'
|
||||
test_cidr = '3.2.1.0/24'
|
||||
m_service = mock.MagicMock()
|
||||
m_handler = mock.Mock(spec=h_lbaas.LBaaSSpecHandler)
|
||||
m_drv_subnets = mock.Mock(spec=drv_base.ServiceSubnetsDriver)
|
||||
m_handler._drv_subnets = m_drv_subnets
|
||||
m_drv_subnets.get_subnets.return_value = {
|
||||
mock.sentinel.subnet_id: self._make_test_net_obj([test_cidr])
|
||||
}
|
||||
|
||||
self.assertRaises(k_exc.IntegrityError,
|
||||
h_lbaas.LBaaSSpecHandler._get_subnet_id,
|
||||
m_handler,
|
||||
m_service,
|
||||
mock.sentinel.project_id,
|
||||
test_ip)
|
||||
|
||||
def test_generate_lbaas_spec(self):
|
||||
m_handler = mock.Mock(spec=h_lbaas.LBaaSSpecHandler)
|
||||
|
||||
service = mock.sentinel.service
|
||||
project_id = mock.sentinel.project_id
|
||||
ip = mock.sentinel.ip
|
||||
subnet_id = mock.sentinel.subnet_id
|
||||
ports = mock.sentinel.ports
|
||||
sg_ids = mock.sentinel.sg_ids
|
||||
|
||||
m_drv_project = mock.Mock()
|
||||
m_drv_project.get_project.return_value = project_id
|
||||
m_drv_sg = mock.Mock()
|
||||
m_drv_sg.get_security_groups.return_value = sg_ids
|
||||
m_handler._drv_project = m_drv_project
|
||||
m_handler._drv_sg = m_drv_sg
|
||||
m_handler._get_service_ip.return_value = ip
|
||||
m_handler._get_subnet_id.return_value = subnet_id
|
||||
m_handler._generate_lbaas_port_specs.return_value = ports
|
||||
|
||||
spec_ctor_path = 'kuryr_kubernetes.objects.lbaas.LBaaSServiceSpec'
|
||||
with mock.patch(spec_ctor_path) as m_spec_ctor:
|
||||
m_spec_ctor.return_value = mock.sentinel.ret_obj
|
||||
ret_obj = h_lbaas.LBaaSSpecHandler._generate_lbaas_spec(
|
||||
m_handler, service)
|
||||
self.assertEqual(mock.sentinel.ret_obj, ret_obj)
|
||||
m_spec_ctor.assert_called_once_with(
|
||||
ip=ip,
|
||||
project_id=project_id,
|
||||
subnet_id=subnet_id,
|
||||
ports=ports,
|
||||
security_groups_ids=sg_ids)
|
||||
|
||||
m_drv_project.get_project.assert_called_once_with(service)
|
||||
m_handler._get_service_ip.assert_called_once_with(service)
|
||||
m_handler._get_subnet_id.assert_called_once_with(
|
||||
service, project_id, ip)
|
||||
m_handler._generate_lbaas_port_specs.assert_called_once_with(service)
|
||||
m_drv_sg.get_security_groups.assert_called_once_with(
|
||||
service, project_id)
|
||||
|
||||
def test_has_lbaas_spec_changes(self):
|
||||
m_handler = mock.Mock(spec=h_lbaas.LBaaSSpecHandler)
|
||||
service = mock.sentinel.service
|
||||
lbaas_spec = mock.sentinel.lbaas_spec
|
||||
|
||||
for has_ip_changes in (True, False):
|
||||
for has_port_changes in (True, False):
|
||||
m_handler._has_ip_changes.return_value = has_ip_changes
|
||||
m_handler._has_port_changes.return_value = has_port_changes
|
||||
ret = h_lbaas.LBaaSSpecHandler._has_lbaas_spec_changes(
|
||||
m_handler, service, lbaas_spec)
|
||||
self.assertEqual(has_ip_changes or has_port_changes, ret)
|
||||
|
||||
def test_get_service_ports(self):
|
||||
m_handler = mock.Mock(spec=h_lbaas.LBaaSSpecHandler)
|
||||
service = {'spec': {'ports': [
|
||||
{'port': 1},
|
||||
{'port': 2, 'name': 'X', 'protocol': 'UDP'}
|
||||
]}}
|
||||
expected_ret = [
|
||||
{'port': 1, 'name': None, 'protocol': 'TCP'},
|
||||
{'port': 2, 'name': 'X', 'protocol': 'UDP'}]
|
||||
|
||||
ret = h_lbaas.LBaaSSpecHandler._get_service_ports(m_handler, service)
|
||||
self.assertEqual(expected_ret, ret)
|
||||
|
||||
def test_has_port_changes(self):
|
||||
m_handler = mock.Mock(spec=h_lbaas.LBaaSSpecHandler)
|
||||
m_service = mock.MagicMock()
|
||||
m_handler._get_service_ports.return_value = [
|
||||
{'port': 1, 'name': 'X', 'protocol': 'TCP'},
|
||||
]
|
||||
|
||||
m_lbaas_spec = mock.MagicMock()
|
||||
m_lbaas_spec.ports = [
|
||||
obj_lbaas.LBaaSPortSpec(name='X', protocol='TCP', port=1),
|
||||
obj_lbaas.LBaaSPortSpec(name='Y', protocol='TCP', port=2),
|
||||
]
|
||||
|
||||
ret = h_lbaas.LBaaSSpecHandler._has_port_changes(
|
||||
m_handler, m_service, m_lbaas_spec)
|
||||
|
||||
self.assertTrue(ret)
|
||||
|
||||
def test_has_port_changes__no_changes(self):
|
||||
m_handler = mock.Mock(spec=h_lbaas.LBaaSSpecHandler)
|
||||
m_service = mock.MagicMock()
|
||||
m_handler._get_service_ports.return_value = [
|
||||
{'port': 1, 'name': 'X', 'protocol': 'TCP'},
|
||||
{'port': 2, 'name': 'Y', 'protocol': 'TCP'}
|
||||
]
|
||||
|
||||
m_lbaas_spec = mock.MagicMock()
|
||||
m_lbaas_spec.ports = [
|
||||
obj_lbaas.LBaaSPortSpec(name='X', protocol='TCP', port=1),
|
||||
obj_lbaas.LBaaSPortSpec(name='Y', protocol='TCP', port=2),
|
||||
]
|
||||
|
||||
ret = h_lbaas.LBaaSSpecHandler._has_port_changes(
|
||||
m_handler, m_service, m_lbaas_spec)
|
||||
|
||||
self.assertFalse(ret)
|
||||
|
||||
def test_has_ip_changes(self):
|
||||
m_handler = mock.Mock(spec=h_lbaas.LBaaSSpecHandler)
|
||||
m_service = mock.MagicMock()
|
||||
m_handler._get_service_ip.return_value = '1.1.1.1'
|
||||
m_lbaas_spec = mock.MagicMock()
|
||||
m_lbaas_spec.ip.__str__.return_value = '2.2.2.2'
|
||||
|
||||
ret = h_lbaas.LBaaSSpecHandler._has_ip_changes(
|
||||
m_handler, m_service, m_lbaas_spec)
|
||||
self.assertTrue(ret)
|
||||
|
||||
def test_has_ip_changes__no_changes(self):
|
||||
m_handler = mock.Mock(spec=h_lbaas.LBaaSSpecHandler)
|
||||
m_service = mock.MagicMock()
|
||||
m_handler._get_service_ip.return_value = '1.1.1.1'
|
||||
m_lbaas_spec = mock.MagicMock()
|
||||
m_lbaas_spec.ip.__str__.return_value = '1.1.1.1'
|
||||
|
||||
ret = h_lbaas.LBaaSSpecHandler._has_ip_changes(
|
||||
m_handler, m_service, m_lbaas_spec)
|
||||
self.assertFalse(ret)
|
||||
|
||||
def test_has_ip_changes__no_spec(self):
|
||||
m_handler = mock.Mock(spec=h_lbaas.LBaaSSpecHandler)
|
||||
m_service = mock.MagicMock()
|
||||
m_handler._get_service_ip.return_value = '1.1.1.1'
|
||||
m_lbaas_spec = None
|
||||
|
||||
ret = h_lbaas.LBaaSSpecHandler._has_ip_changes(
|
||||
m_handler, m_service, m_lbaas_spec)
|
||||
self.assertTrue(ret)
|
||||
|
||||
def test_has_ip_changes__no_nothing(self):
|
||||
m_handler = mock.Mock(spec=h_lbaas.LBaaSSpecHandler)
|
||||
m_service = mock.MagicMock()
|
||||
m_handler._get_service_ip.return_value = None
|
||||
m_lbaas_spec = None
|
||||
|
||||
ret = h_lbaas.LBaaSSpecHandler._has_ip_changes(
|
||||
m_handler, m_service, m_lbaas_spec)
|
||||
self.assertFalse(ret)
|
||||
|
||||
def test_generate_lbaas_port_specs(self):
|
||||
m_handler = mock.Mock(spec=h_lbaas.LBaaSSpecHandler)
|
||||
m_handler._get_service_ports.return_value = [
|
||||
{'port': 1, 'name': 'X', 'protocol': 'TCP'},
|
||||
{'port': 2, 'name': 'Y', 'protocol': 'TCP'}
|
||||
]
|
||||
expected_ports = [
|
||||
obj_lbaas.LBaaSPortSpec(name='X', protocol='TCP', port=1),
|
||||
obj_lbaas.LBaaSPortSpec(name='Y', protocol='TCP', port=2),
|
||||
]
|
||||
|
||||
ret = h_lbaas.LBaaSSpecHandler._generate_lbaas_port_specs(
|
||||
m_handler, mock.sentinel.service)
|
||||
self.assertEqual(expected_ports, ret)
|
||||
m_handler._get_service_ports.assert_called_once_with(
|
||||
mock.sentinel.service)
|
||||
|
||||
def test_get_endpoints_link(self):
|
||||
m_handler = mock.Mock(spec=h_lbaas.LBaaSSpecHandler)
|
||||
service = {'metadata': {
|
||||
'selfLink': "/api/v1/namespaces/default/services/test"}}
|
||||
ret = h_lbaas.LBaaSSpecHandler._get_endpoints_link(m_handler, service)
|
||||
expected_link = "/api/v1/namespaces/default/endpoints/test"
|
||||
self.assertEqual(expected_link, ret)
|
||||
|
||||
def test_get_endpoints_link__integrity_error(self):
|
||||
m_handler = mock.Mock(spec=h_lbaas.LBaaSSpecHandler)
|
||||
service = {'metadata': {
|
||||
'selfLink': "/api/v1/namespaces/default/not-services/test"}}
|
||||
self.assertRaises(k_exc.IntegrityError,
|
||||
h_lbaas.LBaaSSpecHandler._get_endpoints_link,
|
||||
m_handler, service)
|
||||
|
||||
def test_set_lbaas_spec(self):
|
||||
self.skipTest("skipping until generalised annotation handling is "
|
||||
"implemented")
|
||||
|
||||
def test_get_lbaas_spec(self):
|
||||
self.skipTest("skipping until generalised annotation handling is "
|
||||
"implemented")
|
@ -42,12 +42,21 @@ kuryr_kubernetes.cni.binding =
|
||||
kuryr_kubernetes.controller.drivers.pod_project =
|
||||
default = kuryr_kubernetes.controller.drivers.default_project:DefaultPodProjectDriver
|
||||
|
||||
kuryr_kubernetes.controller.drivers.service_project =
|
||||
default = kuryr_kubernetes.controller.drivers.default_project:DefaultServiceProjectDriver
|
||||
|
||||
kuryr_kubernetes.controller.drivers.pod_subnets =
|
||||
default = kuryr_kubernetes.controller.drivers.default_subnet:DefaultPodSubnetDriver
|
||||
|
||||
kuryr_kubernetes.controller.drivers.service_subnets =
|
||||
default = kuryr_kubernetes.controller.drivers.default_subnet:DefaultServiceSubnetDriver
|
||||
|
||||
kuryr_kubernetes.controller.drivers.pod_security_groups =
|
||||
default = kuryr_kubernetes.controller.drivers.default_security_groups:DefaultPodSecurityGroupsDriver
|
||||
|
||||
kuryr_kubernetes.controller.drivers.service_security_groups =
|
||||
default = kuryr_kubernetes.controller.drivers.default_security_groups:DefaultServiceSecurityGroupsDriver
|
||||
|
||||
kuryr_kubernetes.controller.drivers.pod_vif =
|
||||
generic = kuryr_kubernetes.controller.drivers.generic_vif:GenericPodVIFDriver
|
||||
nested-vlan = kuryr_kubernetes.controller.drivers.nested_vlan_vif:NestedVlanPodVIFDriver
|
||||
|
Loading…
x
Reference in New Issue
Block a user