Controller driver base and pod project driver

This patch adds stevedore-based driver support to the controller and
implements a driver to determine OpenStack project ID used to create
pod ports.

Change-Id: I0002ce1c1f7985955b7f66902dcf5386db212a1a
Partially-Implements: blueprint kuryr-k8s-integration
This commit is contained in:
Ilya Chukhnakov 2016-11-15 20:21:27 +03:00
parent f383c67b41
commit 363575fe4c
9 changed files with 264 additions and 0 deletions

View File

@ -33,12 +33,20 @@ k8s_opts = [
cfg.StrOpt('api_root', cfg.StrOpt('api_root',
help=_("The root URL of the Kubernetes API"), help=_("The root URL of the Kubernetes API"),
default=os.environ.get('K8S_API', 'http://localhost:8080')), default=os.environ.get('K8S_API', 'http://localhost:8080')),
cfg.StrOpt('pod_project_driver',
help=_("The driver to determine OpenStack project for pod ports"),
default='default'),
] ]
neutron_defaults = [
cfg.StrOpt('project',
help=_("Default OpenStack project ID for Kubernetes resources")),
]
CONF = cfg.CONF CONF = cfg.CONF
CONF.register_opts(kuryr_k8s_opts) CONF.register_opts(kuryr_k8s_opts)
CONF.register_opts(k8s_opts, group='kubernetes') CONF.register_opts(k8s_opts, group='kubernetes')
CONF.register_opts(neutron_defaults, group='neutron_defaults')
CONF.register_opts(lib_config.core_opts) CONF.register_opts(lib_config.core_opts)
CONF.register_opts(lib_config.binding_opts, 'binding') CONF.register_opts(lib_config.binding_opts, 'binding')

View File

@ -0,0 +1,95 @@
# 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 abc
import six
from kuryr.lib._i18n import _LE
from oslo_log import log as logging
from stevedore import driver as stv_driver
from kuryr_kubernetes import config
LOG = logging.getLogger(__name__)
_DRIVER_NAMESPACE_BASE = 'kuryr_kubernetes.controller.drivers'
_DRIVER_MANAGERS = {}
class DriverBase(object):
"""Base class for controller drivers.
Subclasses must define an *ALIAS* attribute that is used to find a driver
implementation by `get_instance` class method which utilises
`stevedore.driver.DriverManager` with the namespace set to
'kuryr_kubernetes.controller.drivers.*ALIAS*' and the name of
the driver determined from the '[kubernetes]/*ALIAS*_driver' configuration
parameter.
Usage example:
@six.add_metaclass(abc.ABCMeta)
class SomeDriverInterface(DriverBase):
ALIAS = 'driver_alias'
@abc.abstractmethod
def some_method(self):
pass
driver = SomeDriverInterface.get_instance()
driver.some_method()
"""
@classmethod
def get_instance(cls):
"""Get an implementing driver instance."""
alias = cls.ALIAS
try:
manager = _DRIVER_MANAGERS[alias]
except KeyError:
name = config.CONF.kubernetes[alias + '_driver']
manager = stv_driver.DriverManager(
namespace="%s.%s" % (_DRIVER_NAMESPACE_BASE, alias),
name=name,
invoke_on_load=True)
_DRIVER_MANAGERS[alias] = manager
driver = manager.driver
if not isinstance(driver, cls):
raise TypeError(_LE("Invalid %(alias)r driver type: %(driver)s, "
"must be a subclass of %(type)s") % {
'alias': alias,
'driver': driver.__class__.__name__,
'type': cls})
return driver
@six.add_metaclass(abc.ABCMeta)
class PodProjectDriver(DriverBase):
"""Provides an OpenStack project ID for Kubernetes Pod ports."""
ALIAS = 'pod_project'
@abc.abstractmethod
def get_project(self, pod):
"""Get an OpenStack project ID for Kubernetes Pod ports.
:param pod: dict containing Kubernetes Pod object
:return: project ID
"""
raise NotImplementedError()

View File

@ -0,0 +1,35 @@
# 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 oslo_config import cfg
from kuryr_kubernetes import config
from kuryr_kubernetes.controller.drivers import base
class DefaultPodProjectDriver(base.PodProjectDriver):
"""Provides project ID for Pod port based on a configuration option."""
def get_project(self, pod):
project_id = config.CONF.neutron_defaults.project
if not project_id:
# NOTE(ivc): this option is only required for
# DefaultPodProjectDriver and its subclasses, but it may be
# optional for other drivers (e.g. when each namespace has own
# project)
raise cfg.RequiredOptError('project', 'neutron_defaults')
return project_id

View File

@ -0,0 +1,83 @@
# 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 abc
import mock
import six
from kuryr_kubernetes.controller.drivers import base as d_base
from kuryr_kubernetes.tests import base as test_base
@six.add_metaclass(abc.ABCMeta)
class _TestDriver(d_base.DriverBase):
ALIAS = 'test_alias'
@abc.abstractmethod
def test(self):
raise NotImplementedError()
class TestDriverBase(test_base.TestCase):
@mock.patch.object(d_base, '_DRIVER_MANAGERS')
@mock.patch('kuryr_kubernetes.config.CONF')
@mock.patch('stevedore.driver.DriverManager')
def test_get_instance(self, m_stv_mgr, m_cfg, m_mgrs):
m_drv = mock.MagicMock(spec=_TestDriver)
m_mgr = mock.MagicMock()
m_mgr.driver = m_drv
m_mgrs.__getitem__.return_value = m_mgr
self.assertEqual(m_drv, _TestDriver.get_instance())
m_cfg.assert_not_called()
m_stv_mgr.assert_not_called()
@mock.patch.object(d_base, '_DRIVER_MANAGERS')
@mock.patch('kuryr_kubernetes.config.CONF')
@mock.patch('stevedore.driver.DriverManager')
def test_get_instance_not_loaded(self, m_stv_mgr, m_cfg, m_mgrs):
alias = _TestDriver.ALIAS
cfg_name = '%s_driver' % (alias)
drv_name = 'driver_impl'
namespace = '%s.%s' % (d_base._DRIVER_NAMESPACE_BASE, alias)
m_cfg.kubernetes.__getitem__.return_value = drv_name
m_drv = mock.MagicMock(spec=_TestDriver)
m_mgr = mock.MagicMock()
m_mgr.driver = m_drv
m_stv_mgr.return_value = m_mgr
m_mgrs.__getitem__.side_effect = KeyError
self.assertEqual(m_drv, _TestDriver.get_instance())
m_cfg.kubernetes.__getitem__.assert_called_with(cfg_name)
m_stv_mgr.assert_called_with(namespace=namespace, name=drv_name,
invoke_on_load=True)
m_mgrs.__setitem__.assert_called_once_with(alias, m_mgr)
@mock.patch.object(d_base, '_DRIVER_MANAGERS')
@mock.patch('kuryr_kubernetes.config.CONF')
@mock.patch('stevedore.driver.DriverManager')
def test_get_instance_invalid_type(self, m_stv_mgr, m_cfg, m_mgrs):
class _InvalidDriver(object):
pass
m_drv = mock.MagicMock(spec=_InvalidDriver)
m_mgr = mock.MagicMock()
m_mgr.driver = m_drv
m_mgrs.__getitem__.return_value = m_mgr
self.assertRaises(TypeError, _TestDriver.get_instance)
m_cfg.assert_not_called()
m_stv_mgr.assert_not_called()

View File

@ -0,0 +1,39 @@
# 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
from oslo_config import cfg
from kuryr_kubernetes.controller.drivers import default_project
from kuryr_kubernetes.tests import base as test_base
class TestDefaultPodProjectDriver(test_base.TestCase):
@mock.patch('kuryr_kubernetes.config.CONF')
def test_get_project(self, m_cfg):
project_id = mock.sentinel.project_id
pod = mock.sentinel.pod
m_cfg.neutron_defaults.project = project_id
driver = default_project.DefaultPodProjectDriver()
self.assertEqual(project_id, driver.get_project(pod))
def test_get_project_not_set(self):
pod = mock.sentinel.pod
driver = default_project.DefaultPodProjectDriver()
self.assertRaises(cfg.RequiredOptError, driver.get_project, pod)

View File

@ -13,3 +13,4 @@ oslo.serialization>=1.10.0 # Apache-2.0
oslo.service>=1.10.0 # Apache-2.0 oslo.service>=1.10.0 # Apache-2.0
oslo.utils>=3.16.0 # Apache-2.0 oslo.utils>=3.16.0 # Apache-2.0
six>=1.9.0 # MIT six>=1.9.0 # MIT
stevedore>=1.17.1 # Apache-2.0

View File

@ -26,6 +26,9 @@ oslo.config.opts =
console_scripts = console_scripts =
kuryr-k8s-controller = kuryr_kubernetes.cmd.eventlet.controller:start kuryr-k8s-controller = kuryr_kubernetes.cmd.eventlet.controller:start
kuryr_kubernetes.controller.drivers.pod_project =
default = kuryr_kubernetes.controller.drivers.default_project:DefaultPodProjectDriver
[files] [files]
packages = packages =
kuryr_kubernetes kuryr_kubernetes