Add Neutron single network plugin
For share drivers that do support handling of share servers may be useful case when used different share servers but with shared network. This new plugin allows to use some specific network and subnet from Neutron for all share servers within some back-end. Add two configuration options ('neutron_net_id', 'neutron_subnet_id'), that can be defined as all other opts for network plugins. For each back-end can be defined it's own pair of these two options if this new plugin is enabled. New configuration opts accept only IDs and provided subnet should be assigned to provided network else error will be raised on start of manila-share service. If provided share-network has values for net and subnet, they will be redefined. Implements blueprint neutron-single-network-plugin Change-Id: Ia803339078dd25e75848c41d3007a166d94fc309
This commit is contained in:
parent
13176ec1c9
commit
1d87257f18
@ -85,10 +85,9 @@ class API(base.Base):
|
||||
|
||||
def __init__(self, config_group_name=None):
|
||||
super(API, self).__init__()
|
||||
if config_group_name is None:
|
||||
config_group_name = 'DEFAULT'
|
||||
CONF.register_opts(neutron_opts, group=config_group_name)
|
||||
self.configuration = getattr(CONF, config_group_name, CONF)
|
||||
self.config_group_name = config_group_name or 'DEFAULT'
|
||||
CONF.register_opts(neutron_opts, group=self.config_group_name)
|
||||
self.configuration = getattr(CONF, self.config_group_name, CONF)
|
||||
self.last_neutron_extension_sync = None
|
||||
self.extensions = {}
|
||||
self.client = self.get_client(context.get_admin_context())
|
||||
|
@ -1,4 +1,5 @@
|
||||
# Copyright 2013 Openstack Foundation
|
||||
# Copyright 2015 Mirantis, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
@ -13,6 +14,8 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from oslo_config import cfg
|
||||
|
||||
from manila.common import constants
|
||||
from manila import exception
|
||||
from manila import network
|
||||
@ -20,6 +23,23 @@ from manila.network.neutron import api as neutron_api
|
||||
from manila.network.neutron import constants as neutron_constants
|
||||
from manila.openstack.common import log as logging
|
||||
|
||||
neutron_single_network_plugin_opts = [
|
||||
cfg.StrOpt(
|
||||
'neutron_net_id',
|
||||
help="Default Neutron network that will be used for share server "
|
||||
"creation in case it is not provided.",
|
||||
deprecated_group='DEFAULT',
|
||||
default=None),
|
||||
cfg.StrOpt(
|
||||
'neutron_subnet_id',
|
||||
help="Default Neutron subnet that will be used for share server "
|
||||
"creation in case it is not provided. Should be assigned to "
|
||||
"network defined in opt 'neutron_net_id' if set.",
|
||||
deprecated_group='DEFAULT',
|
||||
default=None),
|
||||
]
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -131,3 +151,45 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI):
|
||||
self.db.share_network_update(context,
|
||||
share_network['id'],
|
||||
subnet_values)
|
||||
|
||||
|
||||
class NeutronSingleNetworkPlugin(NeutronNetworkPlugin):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(NeutronSingleNetworkPlugin, self).__init__(*args, **kwargs)
|
||||
CONF.register_opts(
|
||||
neutron_single_network_plugin_opts,
|
||||
group=self.neutron_api.config_group_name)
|
||||
self.net = self.neutron_api.configuration.neutron_net_id
|
||||
self.subnet = self.neutron_api.configuration.neutron_subnet_id
|
||||
self._verify_net_and_subnet()
|
||||
|
||||
def allocate_network(self, context, share_server, share_network, **kwargs):
|
||||
share_network = self._update_share_network_net_data(
|
||||
context, share_network)
|
||||
super(NeutronSingleNetworkPlugin, self).allocate_network(
|
||||
context, share_server, share_network, **kwargs)
|
||||
|
||||
def _verify_net_and_subnet(self):
|
||||
data = dict(net=self.net, subnet=self.subnet)
|
||||
if self.net and self.subnet:
|
||||
net = self.neutron_api.get_network(self.net)
|
||||
if not (net.get('subnets') and data['subnet'] in net['subnets']):
|
||||
raise exception.NetworkBadConfigurationException(
|
||||
"Subnet '%(subnet)s' does not belong to "
|
||||
"network '%(net)s'." % data)
|
||||
else:
|
||||
raise exception.NetworkBadConfigurationException(
|
||||
"Neutron net and subnet are expected to be both set. "
|
||||
"Got: net=%(net)s and subnet=%(subnet)s." % data)
|
||||
|
||||
def _update_share_network_net_data(self, context, share_network):
|
||||
upd = dict()
|
||||
if not share_network.get('neutron_net_id') == self.net:
|
||||
upd['neutron_net_id'] = self.net
|
||||
if not share_network.get('neutron_subnet_id') == self.subnet:
|
||||
upd['neutron_subnet_id'] = self.subnet
|
||||
if upd:
|
||||
share_network = self.db.share_network_update(
|
||||
context, share_network['id'], upd)
|
||||
return share_network
|
||||
|
@ -33,6 +33,7 @@ import manila.exception
|
||||
import manila.network
|
||||
import manila.network.linux.interface
|
||||
import manila.network.neutron.api
|
||||
import manila.network.neutron.neutron_network_plugin
|
||||
import manila.openstack.common.eventlet_backdoor
|
||||
import manila.openstack.common.log
|
||||
import manila.openstack.common.policy
|
||||
@ -78,6 +79,8 @@ _global_opt_lists = [
|
||||
manila.network.linux.interface.OPTS,
|
||||
manila.network.network_opts,
|
||||
manila.network.neutron.api.neutron_opts,
|
||||
manila.network.neutron.neutron_network_plugin.
|
||||
neutron_single_network_plugin_opts,
|
||||
manila.openstack.common.eventlet_backdoor.eventlet_backdoor_opts,
|
||||
manila.openstack.common.log.common_cli_opts,
|
||||
manila.openstack.common.log.generic_log_opts,
|
||||
|
@ -1,4 +1,5 @@
|
||||
# Copyright 2013 OpenStack Foundation
|
||||
# Copyright 2015 Mirantis, Inc.
|
||||
# All Rights Reserved
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
@ -13,15 +14,21 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
|
||||
from manila.common import constants
|
||||
from manila import context
|
||||
from manila.db import api as db_api
|
||||
from manila import exception
|
||||
from manila.network.neutron import api as neutron_api
|
||||
from manila.network.neutron import constants as neutron_constants
|
||||
from manila.network.neutron import neutron_network_plugin as plugin
|
||||
from manila import test
|
||||
from manila.tests import utils as test_utils
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
fake_neutron_port = {
|
||||
"status": "test_port_status",
|
||||
@ -34,35 +41,39 @@ fake_neutron_port = {
|
||||
"binding:capabilities": {"port_filter": True},
|
||||
"mac_address": "test_mac",
|
||||
"fixed_ips": [
|
||||
{"subnet_id": "test_subnet_id",
|
||||
"ip_address": "test_ip"}
|
||||
{"subnet_id": "test_subnet_id", "ip_address": "test_ip"},
|
||||
],
|
||||
"id": "test_port_id",
|
||||
"security_groups": ["fake_sec_group_id"],
|
||||
"device_id": "fake_device_id"
|
||||
"device_id": "fake_device_id",
|
||||
}
|
||||
|
||||
fake_share_network = {'id': 'fake nw info id',
|
||||
'neutron_subnet_id': 'fake subnet id',
|
||||
'neutron_net_id': 'fake net id',
|
||||
'project_id': 'fake project id',
|
||||
'status': 'test_subnet_status',
|
||||
'name': 'fake name',
|
||||
'description': 'fake description',
|
||||
'security_services': []}
|
||||
fake_share_network = {
|
||||
'id': 'fake nw info id',
|
||||
'neutron_subnet_id': 'fake subnet id',
|
||||
'neutron_net_id': 'fake net id',
|
||||
'project_id': 'fake project id',
|
||||
'status': 'test_subnet_status',
|
||||
'name': 'fake name',
|
||||
'description': 'fake description',
|
||||
'security_services': [],
|
||||
}
|
||||
|
||||
fake_share_server = {'id': 'fake nw info id',
|
||||
'status': 'test_server_status',
|
||||
'host': 'fake@host',
|
||||
'network_allocations': [],
|
||||
'shares': []}
|
||||
fake_share_server = {
|
||||
'id': 'fake nw info id',
|
||||
'status': 'test_server_status',
|
||||
'host': 'fake@host',
|
||||
'network_allocations': [],
|
||||
'shares': [],
|
||||
}
|
||||
|
||||
fake_network_allocation = \
|
||||
{'id': fake_neutron_port['id'],
|
||||
'share_server_id': fake_share_server['id'],
|
||||
'ip_address': fake_neutron_port['fixed_ips'][0]['ip_address'],
|
||||
'mac_address': fake_neutron_port['mac_address'],
|
||||
'status': constants.STATUS_ACTIVE}
|
||||
fake_network_allocation = {
|
||||
'id': fake_neutron_port['id'],
|
||||
'share_server_id': fake_share_server['id'],
|
||||
'ip_address': fake_neutron_port['fixed_ips'][0]['ip_address'],
|
||||
'mac_address': fake_neutron_port['mac_address'],
|
||||
'status': constants.STATUS_ACTIVE,
|
||||
}
|
||||
|
||||
|
||||
class NeutronNetworkPluginTest(test.TestCase):
|
||||
@ -284,3 +295,141 @@ class NeutronNetworkPluginTest(test.TestCase):
|
||||
|
||||
self.plugin.neutron_api.list_extensions.assert_any_call()
|
||||
self.assertFalse(result)
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class NeutronSingleNetworkPluginTest(test.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(NeutronSingleNetworkPluginTest, self).setUp()
|
||||
self.context = 'fake_context'
|
||||
|
||||
def test_init_valid(self):
|
||||
fake_net_id = 'fake_net_id'
|
||||
fake_subnet_id = 'fake_subnet_id'
|
||||
config_data = {
|
||||
'DEFAULT': {
|
||||
'neutron_net_id': fake_net_id,
|
||||
'neutron_subnet_id': fake_subnet_id,
|
||||
}
|
||||
}
|
||||
fake_net = {'subnets': ['fake1', 'fake2', fake_subnet_id]}
|
||||
self.stubs.Set(
|
||||
neutron_api.API, 'get_network', mock.Mock(return_value=fake_net))
|
||||
|
||||
with test_utils.create_temp_config_with_opts(config_data):
|
||||
instance = plugin.NeutronSingleNetworkPlugin()
|
||||
|
||||
self.assertEqual(fake_net_id, instance.net)
|
||||
self.assertEqual(fake_subnet_id, instance.subnet)
|
||||
neutron_api.API.get_network.assert_called_once_with(fake_net_id)
|
||||
|
||||
@ddt.data(
|
||||
{'net': None, 'subnet': None},
|
||||
{'net': 'fake_net_id', 'subnet': None},
|
||||
{'net': None, 'subnet': 'fake_subnet_id'})
|
||||
@ddt.unpack
|
||||
def test_init_invalid(self, net, subnet):
|
||||
config_data = dict()
|
||||
# Simulate absence of set values
|
||||
if net:
|
||||
config_data['neutron_net_id'] = net
|
||||
if subnet:
|
||||
config_data['neutron_subnet_id'] = subnet
|
||||
config_data = dict(DEFAULT=config_data)
|
||||
|
||||
with test_utils.create_temp_config_with_opts(config_data):
|
||||
self.assertRaises(
|
||||
exception.NetworkBadConfigurationException,
|
||||
plugin.NeutronSingleNetworkPlugin)
|
||||
|
||||
@ddt.data({}, {'subnets': []}, {'subnets': ['different_foo_subnet']})
|
||||
def test_init_subnet_does_not_belong_to_net(self, fake_net):
|
||||
fake_net_id = 'fake_net_id'
|
||||
config_data = {
|
||||
'DEFAULT': {
|
||||
'neutron_net_id': fake_net_id,
|
||||
'neutron_subnet_id': 'fake_subnet_id',
|
||||
}
|
||||
}
|
||||
self.stubs.Set(
|
||||
neutron_api.API, 'get_network', mock.Mock(return_value=fake_net))
|
||||
|
||||
with test_utils.create_temp_config_with_opts(config_data):
|
||||
self.assertRaises(
|
||||
exception.NetworkBadConfigurationException,
|
||||
plugin.NeutronSingleNetworkPlugin)
|
||||
neutron_api.API.get_network.assert_called_once_with(fake_net_id)
|
||||
|
||||
def _get_neutron_single_network_plugin_instance(self):
|
||||
fake_subnet_id = 'fake_subnet_id'
|
||||
config_data = {
|
||||
'DEFAULT': {
|
||||
'neutron_net_id': 'fake_net_id',
|
||||
'neutron_subnet_id': fake_subnet_id,
|
||||
}
|
||||
}
|
||||
fake_net = {'subnets': [fake_subnet_id]}
|
||||
self.stubs.Set(
|
||||
neutron_api.API, 'get_network', mock.Mock(return_value=fake_net))
|
||||
with test_utils.create_temp_config_with_opts(config_data):
|
||||
instance = plugin.NeutronSingleNetworkPlugin()
|
||||
return instance
|
||||
|
||||
def test___update_share_network_net_data_same_values(self):
|
||||
instance = self._get_neutron_single_network_plugin_instance()
|
||||
share_network = {
|
||||
'neutron_net_id': instance.net,
|
||||
'neutron_subnet_id': instance.subnet,
|
||||
}
|
||||
|
||||
result = instance._update_share_network_net_data(
|
||||
self.context, share_network)
|
||||
|
||||
self.assertEqual(share_network, result)
|
||||
|
||||
@ddt.data(
|
||||
{'n': 'foo', 's': 'bar'},
|
||||
{'n': 'fake_net_id', 's': 'bar'},
|
||||
{'n': 'foo', 's': 'fake_subnet_id'})
|
||||
@ddt.unpack
|
||||
def test___update_share_network_net_data_different_values(self, n, s):
|
||||
instance = self._get_neutron_single_network_plugin_instance()
|
||||
share_network = {
|
||||
'id': 'fake_share_network_id',
|
||||
'neutron_net_id': n,
|
||||
'neutron_subnet_id': s,
|
||||
}
|
||||
self.stubs.Set(
|
||||
instance.db, 'share_network_update',
|
||||
mock.Mock(return_value=share_network))
|
||||
|
||||
result = instance._update_share_network_net_data(
|
||||
self.context, share_network)
|
||||
|
||||
self.assertEqual(share_network, result)
|
||||
instance.db.share_network_update.assert_called_once_with(
|
||||
self.context, share_network['id'], mock.ANY)
|
||||
|
||||
@mock.patch.object(
|
||||
plugin.NeutronNetworkPlugin, "allocate_network", mock.Mock())
|
||||
def test_allocate_network(self):
|
||||
instance = self._get_neutron_single_network_plugin_instance()
|
||||
share_server = 'fake_share_server'
|
||||
share_network = 'fake_share_network'
|
||||
share_network_upd = 'updated_fake_share_network'
|
||||
count = 2
|
||||
device_owner = 'fake_device_owner'
|
||||
self.stubs.Set(
|
||||
instance, '_update_share_network_net_data',
|
||||
mock.Mock(return_value=share_network_upd))
|
||||
|
||||
instance.allocate_network(
|
||||
self.context, share_server, share_network, count=count,
|
||||
device_owner=device_owner)
|
||||
|
||||
instance._update_share_network_net_data.assert_called_once_with(
|
||||
self.context, share_network)
|
||||
plugin.NeutronNetworkPlugin.allocate_network.assert_called_once_with(
|
||||
self.context, share_server, share_network_upd, count=count,
|
||||
device_owner=device_owner)
|
||||
|
@ -15,7 +15,6 @@
|
||||
# under the License.
|
||||
"""Unit tests for the Share driver module."""
|
||||
|
||||
import os
|
||||
import time
|
||||
|
||||
import ddt
|
||||
@ -26,6 +25,7 @@ from manila import network
|
||||
from manila.share import configuration
|
||||
from manila.share import driver
|
||||
from manila import test
|
||||
from manila.tests import utils as test_utils
|
||||
from manila import utils
|
||||
|
||||
|
||||
@ -59,18 +59,8 @@ class ShareDriverTestCase(test.TestCase):
|
||||
execute_mixin._try_execute)
|
||||
|
||||
def test_verify_share_driver_mode_option_type(self):
|
||||
opt_name = 'driver_handles_share_servers'
|
||||
with utils.tempdir() as tmpdir:
|
||||
tmpfilename = os.path.join(tmpdir, '%s.conf' % opt_name)
|
||||
with open(tmpfilename, "w") as configfile:
|
||||
configfile.write("""[DEFAULT]\n%s = True""" % opt_name)
|
||||
|
||||
# Add config file with updated opt
|
||||
driver.CONF.default_config_files = [configfile.name]
|
||||
|
||||
# Reload config instance to use redefined opt
|
||||
driver.CONF.reload_config_files()
|
||||
|
||||
data = {'DEFAULT': {'driver_handles_share_servers': 'True'}}
|
||||
with test_utils.create_temp_config_with_opts(data):
|
||||
share_driver = driver.ShareDriver([True, False])
|
||||
self.assertEqual(True, share_driver.driver_handles_share_servers)
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
# Copyright 2011 OpenStack LLC
|
||||
# Copyright 2015 Mirantic, Inc.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
@ -15,11 +16,16 @@
|
||||
|
||||
import os
|
||||
|
||||
import manila.context
|
||||
from oslo_config import cfg
|
||||
|
||||
from manila import context
|
||||
from manila import utils
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
def get_test_admin_context():
|
||||
return manila.context.get_admin_context()
|
||||
return context.get_admin_context()
|
||||
|
||||
|
||||
def is_manila_installed():
|
||||
@ -27,3 +33,44 @@ def is_manila_installed():
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
class create_temp_config_with_opts(object):
|
||||
"""Creates temporary config file with provided opts and values.
|
||||
|
||||
usage:
|
||||
data = {'FOO_GROUP': {'foo_opt': 'foo_value'}}
|
||||
assert CONF.FOO_GROUP.foo_opt != 'foo_value'
|
||||
with create_temp_config_with_opts(data):
|
||||
assert CONF.FOO_GROUP.foo_opt == 'foo_value'
|
||||
assert CONF.FOO_GROUP.foo_opt != 'foo_value'
|
||||
|
||||
:param data: dict -- expected dict with two layers, first is name of
|
||||
config group and second is opts with values. Example:
|
||||
{'DEFAULT': {'foo_opt': 'foo_v'}, 'BAR_GROUP': {'bar_opt': 'bar_v'}}
|
||||
"""
|
||||
|
||||
def __init__(self, data):
|
||||
self.data = data
|
||||
|
||||
def __enter__(self):
|
||||
config_filename = 'fake_config'
|
||||
with utils.tempdir() as tmpdir:
|
||||
tmpfilename = os.path.join(tmpdir, '%s.conf' % config_filename)
|
||||
with open(tmpfilename, "w") as configfile:
|
||||
for group, opts in self.data.items():
|
||||
configfile.write("""[%s]\n""" % group)
|
||||
for opt, value in opts.items():
|
||||
configfile.write(
|
||||
"""%(k)s = %(v)s\n""" % {'k': opt, 'v': value})
|
||||
configfile.write("""\n""")
|
||||
|
||||
# Add config file with updated opts
|
||||
CONF.default_config_files = [configfile.name]
|
||||
|
||||
# Reload config instance to use redefined opts
|
||||
CONF.reload_config_files()
|
||||
return CONF
|
||||
|
||||
def __exit__(self, exc_type, exc_value, exc_traceback):
|
||||
return False # do not suppress errors
|
||||
|
Loading…
Reference in New Issue
Block a user