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:
Ilya Chukhnakov 2017-01-31 23:19:32 +03:00
parent f3887af958
commit a715f9edb9
14 changed files with 717 additions and 15 deletions

View File

@ -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")),
]

View File

@ -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"

View File

@ -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."""

View File

@ -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

View File

@ -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'))

View File

@ -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)}

View 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

View File

@ -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__)

View File

@ -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()

View File

@ -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)

View File

@ -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)

View File

@ -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()

View 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")

View File

@ -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