Default pod subnet driver and os-vif utils
This patch adds a new driver type used to determine Neutron subnet that should be used for Kubernetes pods' ports. This patch also provides a default subnet driver implementation that uses a subnet set in configuration file. This patch also introduces the 'os_vif_util' module that contains functions to translate data structures returned by Neutron client to os-vif objects. Only the subnet-related functions are added in this patch. Change-Id: I643b22858239ce7f64e6ba81822b31e788fc9990 Partially-Implements: blueprint kuryr-k8s-integration
This commit is contained in:
parent
363575fe4c
commit
9e078d4a5c
@ -36,11 +36,16 @@ k8s_opts = [
|
|||||||
cfg.StrOpt('pod_project_driver',
|
cfg.StrOpt('pod_project_driver',
|
||||||
help=_("The driver to determine OpenStack project for pod ports"),
|
help=_("The driver to determine OpenStack project for pod ports"),
|
||||||
default='default'),
|
default='default'),
|
||||||
|
cfg.StrOpt('pod_subnets_driver',
|
||||||
|
help=_("The driver to determine Neutron subnets for pod ports"),
|
||||||
|
default='default'),
|
||||||
]
|
]
|
||||||
|
|
||||||
neutron_defaults = [
|
neutron_defaults = [
|
||||||
cfg.StrOpt('project',
|
cfg.StrOpt('project',
|
||||||
help=_("Default OpenStack project ID for Kubernetes resources")),
|
help=_("Default OpenStack project ID for Kubernetes resources")),
|
||||||
|
cfg.StrOpt('pod_subnet',
|
||||||
|
help=_("Default Neutron subnet ID for Kubernetes pods")),
|
||||||
]
|
]
|
||||||
|
|
||||||
CONF = cfg.CONF
|
CONF = cfg.CONF
|
||||||
|
@ -93,3 +93,23 @@ class PodProjectDriver(DriverBase):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
@six.add_metaclass(abc.ABCMeta)
|
||||||
|
class PodSubnetsDriver(DriverBase):
|
||||||
|
"""Provides subnets for Kubernetes Pods."""
|
||||||
|
|
||||||
|
ALIAS = 'pod_subnets'
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def get_subnets(self, pod, project_id):
|
||||||
|
"""Get subnets for Pod.
|
||||||
|
|
||||||
|
:param pod: 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()
|
||||||
|
52
kuryr_kubernetes/controller/drivers/default_subnet.py
Normal file
52
kuryr_kubernetes/controller/drivers/default_subnet.py
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
# 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 clients
|
||||||
|
from kuryr_kubernetes import config
|
||||||
|
from kuryr_kubernetes.controller.drivers import base
|
||||||
|
from kuryr_kubernetes import os_vif_util
|
||||||
|
|
||||||
|
|
||||||
|
def _get_subnet(subnet_id):
|
||||||
|
# TODO(ivc): add caching (e.g. oslo.cache with dict backend)
|
||||||
|
neutron = clients.get_neutron_client()
|
||||||
|
|
||||||
|
n_subnet = neutron.show_subnet(subnet_id).get('subnet')
|
||||||
|
network_id = n_subnet['network_id']
|
||||||
|
n_network = neutron.show_network(network_id).get('network')
|
||||||
|
|
||||||
|
subnet = os_vif_util.neutron_to_osvif_subnet(n_subnet)
|
||||||
|
network = os_vif_util.neutron_to_osvif_network(n_network)
|
||||||
|
network.subnets.objects.append(subnet)
|
||||||
|
|
||||||
|
return network
|
||||||
|
|
||||||
|
|
||||||
|
class DefaultPodSubnetDriver(base.PodSubnetsDriver):
|
||||||
|
"""Provides subnet for Pod port based on a configuration option."""
|
||||||
|
|
||||||
|
def get_subnets(self, pod, project_id):
|
||||||
|
subnet_id = config.CONF.neutron_defaults.pod_subnet
|
||||||
|
|
||||||
|
if not subnet_id:
|
||||||
|
# NOTE(ivc): this option is only required for
|
||||||
|
# DefaultPodSubnetDriver and its subclasses, but it may be
|
||||||
|
# optional for other drivers (e.g. when each namespace has own
|
||||||
|
# subnet)
|
||||||
|
raise cfg.RequiredOptError('pod_subnet', 'neutron_defaults')
|
||||||
|
|
||||||
|
return {subnet_id: _get_subnet(subnet_id)}
|
@ -16,6 +16,7 @@
|
|||||||
import sys
|
import sys
|
||||||
|
|
||||||
from kuryr.lib._i18n import _LI, _LE
|
from kuryr.lib._i18n import _LI, _LE
|
||||||
|
import os_vif
|
||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_service import service
|
from oslo_service import service
|
||||||
|
|
||||||
@ -101,5 +102,6 @@ def start():
|
|||||||
config.init(sys.argv[1:])
|
config.init(sys.argv[1:])
|
||||||
config.setup_logging()
|
config.setup_logging()
|
||||||
clients.setup_clients()
|
clients.setup_clients()
|
||||||
|
os_vif.initialize()
|
||||||
kuryrk8s_launcher = service.launch(config.CONF, KuryrK8sService())
|
kuryrk8s_launcher = service.launch(config.CONF, KuryrK8sService())
|
||||||
kuryrk8s_launcher.wait()
|
kuryrk8s_launcher.wait()
|
||||||
|
53
kuryr_kubernetes/os_vif_util.py
Normal file
53
kuryr_kubernetes/os_vif_util.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
# 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 os_vif.objects import network as osv_network
|
||||||
|
from os_vif.objects import route as osv_route
|
||||||
|
from os_vif.objects import subnet as osv_subnet
|
||||||
|
|
||||||
|
|
||||||
|
# REVISIT(ivc): consider making this module part of kuryr-lib
|
||||||
|
|
||||||
|
|
||||||
|
def neutron_to_osvif_network(neutron_network):
|
||||||
|
obj = osv_network.Network(id=neutron_network['id'])
|
||||||
|
|
||||||
|
if neutron_network.get('name') is not None:
|
||||||
|
obj.label = neutron_network['name']
|
||||||
|
|
||||||
|
if neutron_network.get('mtu') is not None:
|
||||||
|
obj.mtu = neutron_network['mtu']
|
||||||
|
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
def neutron_to_osvif_subnet(neutron_subnet):
|
||||||
|
obj = osv_subnet.Subnet(
|
||||||
|
cidr=neutron_subnet['cidr'],
|
||||||
|
dns=neutron_subnet['dns_nameservers'],
|
||||||
|
routes=_neutron_to_osvif_routes(neutron_subnet['host_routes']))
|
||||||
|
|
||||||
|
if neutron_subnet.get('gateway_ip') is not None:
|
||||||
|
obj.gateway = neutron_subnet['gateway_ip']
|
||||||
|
|
||||||
|
return obj
|
||||||
|
|
||||||
|
|
||||||
|
def _neutron_to_osvif_routes(neutron_routes):
|
||||||
|
obj_list = [osv_route.Route(cidr=route['destination'],
|
||||||
|
gateway=route['nexthop'])
|
||||||
|
for route in neutron_routes]
|
||||||
|
|
||||||
|
return osv_route.RouteList(objects=obj_list)
|
@ -0,0 +1,84 @@
|
|||||||
|
# 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_subnet
|
||||||
|
from kuryr_kubernetes.tests import base as test_base
|
||||||
|
from kuryr_kubernetes.tests.unit import kuryr_fixtures as k_fix
|
||||||
|
|
||||||
|
|
||||||
|
class TestDefaultPodSubnetDriver(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
|
||||||
|
pod = mock.sentinel.pod
|
||||||
|
project_id = mock.sentinel.project_id
|
||||||
|
m_cfg.neutron_defaults.pod_subnet = subnet_id
|
||||||
|
m_get_subnet.return_value = subnet
|
||||||
|
driver = default_subnet.DefaultPodSubnetDriver()
|
||||||
|
|
||||||
|
subnets = driver.get_subnets(pod, 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):
|
||||||
|
pod = mock.sentinel.pod
|
||||||
|
project_id = mock.sentinel.project_id
|
||||||
|
driver = default_subnet.DefaultPodSubnetDriver()
|
||||||
|
|
||||||
|
self.assertRaises(cfg.RequiredOptError, driver.get_subnets, pod,
|
||||||
|
project_id)
|
||||||
|
m_get_subnet.assert_not_called()
|
||||||
|
|
||||||
|
|
||||||
|
class TestGetSubnet(test_base.TestCase):
|
||||||
|
|
||||||
|
@mock.patch('kuryr_kubernetes.os_vif_util.neutron_to_osvif_network')
|
||||||
|
@mock.patch('kuryr_kubernetes.os_vif_util.neutron_to_osvif_subnet')
|
||||||
|
def test_get_subnet(self, m_osv_subnet, m_osv_network):
|
||||||
|
neutron = self.useFixture(k_fix.MockNeutronClient()).client
|
||||||
|
|
||||||
|
subnet = mock.MagicMock()
|
||||||
|
network = mock.MagicMock()
|
||||||
|
subnet_id = mock.sentinel.subnet_id
|
||||||
|
network_id = mock.sentinel.network_id
|
||||||
|
|
||||||
|
neutron_subnet = {'network_id': network_id}
|
||||||
|
neutron_network = mock.sentinel.neutron_network
|
||||||
|
|
||||||
|
neutron.show_subnet.return_value = {'subnet': neutron_subnet}
|
||||||
|
neutron.show_network.return_value = {'network': neutron_network}
|
||||||
|
|
||||||
|
m_osv_subnet.return_value = subnet
|
||||||
|
m_osv_network.return_value = network
|
||||||
|
|
||||||
|
ret = default_subnet._get_subnet(subnet_id)
|
||||||
|
|
||||||
|
self.assertEqual(network, ret)
|
||||||
|
neutron.show_subnet.assert_called_once_with(subnet_id)
|
||||||
|
neutron.show_network.assert_called_once_with(network_id)
|
||||||
|
m_osv_subnet.assert_called_once_with(neutron_subnet)
|
||||||
|
m_osv_network.assert_called_once_with(neutron_network)
|
||||||
|
network.subnets.objects.append.assert_called_once_with(subnet)
|
@ -25,3 +25,11 @@ class MockK8sClient(fixtures.Fixture):
|
|||||||
self.useFixture(fixtures.MockPatch(
|
self.useFixture(fixtures.MockPatch(
|
||||||
'kuryr_kubernetes.clients.get_kubernetes_client',
|
'kuryr_kubernetes.clients.get_kubernetes_client',
|
||||||
lambda: self.client))
|
lambda: self.client))
|
||||||
|
|
||||||
|
|
||||||
|
class MockNeutronClient(fixtures.Fixture):
|
||||||
|
def _setUp(self):
|
||||||
|
self.client = mock.Mock()
|
||||||
|
self.useFixture(fixtures.MockPatch(
|
||||||
|
'kuryr_kubernetes.clients.get_neutron_client',
|
||||||
|
lambda: self.client))
|
||||||
|
117
kuryr_kubernetes/tests/unit/test_os_vif_util.py
Normal file
117
kuryr_kubernetes/tests/unit/test_os_vif_util.py
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
# 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 os_vif.objects import route as osv_route
|
||||||
|
from oslo_utils import uuidutils
|
||||||
|
|
||||||
|
from kuryr_kubernetes import os_vif_util as ovu
|
||||||
|
from kuryr_kubernetes.tests import base as test_base
|
||||||
|
|
||||||
|
|
||||||
|
# REVISIT(ivc): move to kuryr-lib along with 'os_vif_util'
|
||||||
|
|
||||||
|
|
||||||
|
class TestOSVIFUtils(test_base.TestCase):
|
||||||
|
def test_neutron_to_osvif_network(self):
|
||||||
|
network_id = uuidutils.generate_uuid()
|
||||||
|
network_name = 'test-net'
|
||||||
|
network_mtu = 1500
|
||||||
|
neutron_network = {
|
||||||
|
'id': network_id,
|
||||||
|
'name': network_name,
|
||||||
|
'mtu': network_mtu,
|
||||||
|
}
|
||||||
|
|
||||||
|
network = ovu.neutron_to_osvif_network(neutron_network)
|
||||||
|
|
||||||
|
self.assertEqual(network_id, network.id)
|
||||||
|
self.assertEqual(network_name, network.label)
|
||||||
|
self.assertEqual(network_mtu, network.mtu)
|
||||||
|
|
||||||
|
def test_neutron_to_osvif_network_no_name(self):
|
||||||
|
network_id = uuidutils.generate_uuid()
|
||||||
|
network_mtu = 1500
|
||||||
|
neutron_network = {
|
||||||
|
'id': network_id,
|
||||||
|
'mtu': network_mtu,
|
||||||
|
}
|
||||||
|
|
||||||
|
network = ovu.neutron_to_osvif_network(neutron_network)
|
||||||
|
|
||||||
|
self.assertFalse(network.obj_attr_is_set('label'))
|
||||||
|
|
||||||
|
def test_neutron_to_osvif_network_no_mtu(self):
|
||||||
|
network_id = uuidutils.generate_uuid()
|
||||||
|
network_name = 'test-net'
|
||||||
|
neutron_network = {
|
||||||
|
'id': network_id,
|
||||||
|
'name': network_name,
|
||||||
|
}
|
||||||
|
|
||||||
|
network = ovu.neutron_to_osvif_network(neutron_network)
|
||||||
|
|
||||||
|
self.assertEqual(None, network.mtu)
|
||||||
|
|
||||||
|
@mock.patch('kuryr_kubernetes.os_vif_util._neutron_to_osvif_routes')
|
||||||
|
def test_neutron_to_osvif_subnet(self, m_conv_routes):
|
||||||
|
gateway = '1.1.1.1'
|
||||||
|
cidr = '1.1.1.1/8'
|
||||||
|
dns = ['2.2.2.2', '3.3.3.3']
|
||||||
|
host_routes = mock.sentinel.host_routes
|
||||||
|
route_list = osv_route.RouteList(objects=[
|
||||||
|
osv_route.Route(cidr='4.4.4.4/8', gateway='5.5.5.5')])
|
||||||
|
m_conv_routes.return_value = route_list
|
||||||
|
neutron_subnet = {
|
||||||
|
'cidr': cidr,
|
||||||
|
'dns_nameservers': dns,
|
||||||
|
'host_routes': host_routes,
|
||||||
|
'gateway_ip': gateway,
|
||||||
|
}
|
||||||
|
|
||||||
|
subnet = ovu.neutron_to_osvif_subnet(neutron_subnet)
|
||||||
|
|
||||||
|
self.assertEqual(cidr, str(subnet.cidr))
|
||||||
|
self.assertEqual(route_list, subnet.routes)
|
||||||
|
self.assertEqual(set(dns), set([str(addr) for addr in subnet.dns]))
|
||||||
|
self.assertEqual(gateway, str(subnet.gateway))
|
||||||
|
m_conv_routes.assert_called_once_with(host_routes)
|
||||||
|
|
||||||
|
@mock.patch('kuryr_kubernetes.os_vif_util._neutron_to_osvif_routes')
|
||||||
|
def test_neutron_to_osvif_subnet_no_gateway(self, m_conv_routes):
|
||||||
|
cidr = '1.1.1.1/8'
|
||||||
|
route_list = osv_route.RouteList()
|
||||||
|
m_conv_routes.return_value = route_list
|
||||||
|
neutron_subnet = {
|
||||||
|
'cidr': cidr,
|
||||||
|
'dns_nameservers': [],
|
||||||
|
'host_routes': [],
|
||||||
|
}
|
||||||
|
|
||||||
|
subnet = ovu.neutron_to_osvif_subnet(neutron_subnet)
|
||||||
|
|
||||||
|
self.assertFalse(subnet.obj_attr_is_set('gateway'))
|
||||||
|
|
||||||
|
def test_neutron_to_osvif_routes(self):
|
||||||
|
routes_map = {'%s.0.0.0/8' % i: '10.0.0.%s' % i for i in range(3)}
|
||||||
|
routes = [{'destination': k, 'nexthop': v}
|
||||||
|
for k, v in routes_map.items()]
|
||||||
|
|
||||||
|
route_list = ovu._neutron_to_osvif_routes(routes)
|
||||||
|
|
||||||
|
self.assertEqual(len(routes), len(route_list.objects))
|
||||||
|
for route in route_list.objects:
|
||||||
|
self.assertEqual(routes_map[str(route.cidr)], str(route.gateway))
|
@ -12,5 +12,6 @@ oslo.log>=3.11.0 # Apache-2.0
|
|||||||
oslo.serialization>=1.10.0 # Apache-2.0
|
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
|
||||||
|
os-vif>=1.3.0 # Apache-2.0
|
||||||
six>=1.9.0 # MIT
|
six>=1.9.0 # MIT
|
||||||
stevedore>=1.17.1 # Apache-2.0
|
stevedore>=1.17.1 # Apache-2.0
|
||||||
|
@ -29,6 +29,9 @@ console_scripts =
|
|||||||
kuryr_kubernetes.controller.drivers.pod_project =
|
kuryr_kubernetes.controller.drivers.pod_project =
|
||||||
default = kuryr_kubernetes.controller.drivers.default_project:DefaultPodProjectDriver
|
default = kuryr_kubernetes.controller.drivers.default_project:DefaultPodProjectDriver
|
||||||
|
|
||||||
|
kuryr_kubernetes.controller.drivers.pod_subnets =
|
||||||
|
default = kuryr_kubernetes.controller.drivers.default_subnet:DefaultPodSubnetDriver
|
||||||
|
|
||||||
[files]
|
[files]
|
||||||
packages =
|
packages =
|
||||||
kuryr_kubernetes
|
kuryr_kubernetes
|
||||||
|
Loading…
x
Reference in New Issue
Block a user