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',
|
||||
help=_("The driver to determine OpenStack project for pod ports"),
|
||||
default='default'),
|
||||
cfg.StrOpt('pod_subnets_driver',
|
||||
help=_("The driver to determine Neutron subnets for pod ports"),
|
||||
default='default'),
|
||||
]
|
||||
|
||||
neutron_defaults = [
|
||||
cfg.StrOpt('project',
|
||||
help=_("Default OpenStack project ID for Kubernetes resources")),
|
||||
cfg.StrOpt('pod_subnet',
|
||||
help=_("Default Neutron subnet ID for Kubernetes pods")),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
@ -93,3 +93,23 @@ class PodProjectDriver(DriverBase):
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
from kuryr.lib._i18n import _LI, _LE
|
||||
import os_vif
|
||||
from oslo_log import log as logging
|
||||
from oslo_service import service
|
||||
|
||||
@ -101,5 +102,6 @@ def start():
|
||||
config.init(sys.argv[1:])
|
||||
config.setup_logging()
|
||||
clients.setup_clients()
|
||||
os_vif.initialize()
|
||||
kuryrk8s_launcher = service.launch(config.CONF, KuryrK8sService())
|
||||
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(
|
||||
'kuryr_kubernetes.clients.get_kubernetes_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.service>=1.10.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
|
||||
stevedore>=1.17.1 # Apache-2.0
|
||||
|
@ -29,6 +29,9 @@ console_scripts =
|
||||
kuryr_kubernetes.controller.drivers.pod_project =
|
||||
default = kuryr_kubernetes.controller.drivers.default_project:DefaultPodProjectDriver
|
||||
|
||||
kuryr_kubernetes.controller.drivers.pod_subnets =
|
||||
default = kuryr_kubernetes.controller.drivers.default_subnet:DefaultPodSubnetDriver
|
||||
|
||||
[files]
|
||||
packages =
|
||||
kuryr_kubernetes
|
||||
|
Loading…
Reference in New Issue
Block a user