diff --git a/devstack/plugin.sh b/devstack/plugin.sh index 6fca7ed592..fa25107afa 100755 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -75,6 +75,12 @@ function configure_default_backends { iniset $MANILA_CONF $group_name service_instance_user $MANILA_SERVICE_INSTANCE_USER iniset $MANILA_CONF $group_name driver_handles_share_servers True + if [ "$SHARE_DRIVER" == $MANILA_CONTAINER_DRIVER ]; then + iniset $MANILA_CONF $group_name network_api_class $MANILA_NETWORK_API_CLASS + iniset $MANILA_CONF $group_name neutron_host_id $(hostname) + iniset $MANILA_CONF $group_name neutron_vnic_type $MANILA_NEUTRON_VNIC_TYPE + fi + if [ $(trueorfalse False MANILA_USE_SERVICE_INSTANCE_PASSWORD) == True ]; then iniset $MANILA_CONF $group_name service_instance_password $MANILA_SERVICE_INSTANCE_PASSWORD fi @@ -522,7 +528,6 @@ function init_manila { elif [ "$SHARE_DRIVER" == $MANILA_CONTAINER_DRIVER ]; then if is_service_enabled m-shr; then SHARE_GROUP=$MANILA_CONTAINER_VOLUME_GROUP_NAME - iniset $MANILA_CONF DEFAULT neutron_host_id $(hostname) configure_backing_file fi diff --git a/devstack/settings b/devstack/settings index a534fc6dea..3de8f7f07d 100644 --- a/devstack/settings +++ b/devstack/settings @@ -166,6 +166,10 @@ MANILA_CONTAINER_VOLUME_GROUP_NAME=${MANILA_CONTAINER_VOLUME_GROUP_NAME:-"manila # permanent one as soon as possible. MANILA_DOCKER_IMAGE_URL=${MANILA_DOCKER_IMAGE_URL:-"https://github.com/a-ovchinnikov/manila-image-elements-lxd-images/releases/download/0.1.0/manila-docker-container.tar.gz"} +# Network Plugin +MANILA_NETWORK_API_CLASS=${MANILA_NETWORK_API_CLASS:-"manila.network.neutron.neutron_network_plugin.NeutronBindNetworkPlugin"} +MANILA_NEUTRON_VNIC_TYPE=${MANILA_NEUTRON_VNIC_TYPE:-"normal"} + # Enable manila services # ---------------------- # We have to add Manila to enabled services for screen_it to work diff --git a/doc/source/adminref/network_plugins.rst b/doc/source/adminref/network_plugins.rst index 43dad585c2..1898706c2d 100644 --- a/doc/source/adminref/network_plugins.rst +++ b/doc/source/adminref/network_plugins.rst @@ -77,6 +77,13 @@ There are three different network plugins and five python classes in Manila: `neutron_net_id` and `neutron_subnet_id` from the Manila configuration file and uses one network for all shares. + 1.3 `manila.network.neutron.neutron_network_plugin.NeutronBindNetworkPlugin`. + This driver waits for active binding and fails if a Neutron port can't be + bound or an error occurs. This plugin is useful for agent based binding + (like OVS with docker driver) and fabric binding where real HW + reconfiguration is taking place. The existing + `NeutronBindSingleNetworkPlugin` is a combination of 1.2 and 1.3. + When only a single network is needed, the NeutronSingleNetworkPlugin (1.2) is a simple solution. Otherwise NeutronNetworkPlugin (1.1) should be chosen. diff --git a/manila/exception.py b/manila/exception.py index 432d0926fe..7ca784ed36 100644 --- a/manila/exception.py +++ b/manila/exception.py @@ -114,6 +114,10 @@ class NetworkException(ManilaException): message = _("Exception due to network failure.") +class NetworkBindException(ManilaException): + message = _("Exception due to failed port status in binding.") + + class NetworkBadConfigurationException(NetworkException): message = _("Bad network configuration: %(reason)s.") diff --git a/manila/network/__init__.py b/manila/network/__init__.py index d618345cab..dee16b58c5 100644 --- a/manila/network/__init__.py +++ b/manila/network/__init__.py @@ -65,6 +65,17 @@ class NetworkBaseAPI(db_base.Base): "'%s'.") % share_server_id raise exception.NetworkBadConfigurationException(reason=msg) + def update_network_allocation(self, context, share_server): + """Update network allocation. + + Optional method to be called by the manager after share server creation + which can be overloaded in case the port state has to be updated. + + :param context: RequestContext object + :param share_server: share server object + :return: list of updated ports or None if nothing was updated + """ + @abc.abstractmethod def allocate_network(self, context, share_server, share_network=None, **kwargs): diff --git a/manila/network/neutron/api.py b/manila/network/neutron/api.py index 24a8e8758c..af15fb7566 100644 --- a/manila/network/neutron/api.py +++ b/manila/network/neutron/api.py @@ -164,7 +164,8 @@ class API(object): def create_port(self, tenant_id, network_id, host_id=None, subnet_id=None, fixed_ip=None, device_owner=None, device_id=None, - mac_address=None, security_group_ids=None, dhcp_opts=None): + mac_address=None, security_group_ids=None, dhcp_opts=None, + **kwargs): try: port_req_body = {'port': {}} port_req_body['port']['network_id'] = network_id @@ -187,6 +188,8 @@ class API(object): port_req_body['port']['device_owner'] = device_owner if device_id: port_req_body['port']['device_id'] = device_id + if kwargs: + port_req_body['port'].update(kwargs) port = self.client.create_port(port_req_body).get('port', {}) return port except neutron_client_exc.NeutronClientException as e: diff --git a/manila/network/neutron/constants.py b/manila/network/neutron/constants.py index 1aac2800d2..23ce4cd92a 100644 --- a/manila/network/neutron/constants.py +++ b/manila/network/neutron/constants.py @@ -15,3 +15,8 @@ PROVIDER_NW_EXT = 'Provider Network' PORTBINDING_EXT = 'Port Binding' + +PORT_STATUS_ERROR = 'ERROR' +PORT_STATUS_ACTIVE = 'ACTIVE' + +VIF_TYPE_BINDING_FAILED = 'binding_failed' diff --git a/manila/network/neutron/neutron_network_plugin.py b/manila/network/neutron/neutron_network_plugin.py index c31a664a7d..06306a8f29 100644 --- a/manila/network/neutron/neutron_network_plugin.py +++ b/manila/network/neutron/neutron_network_plugin.py @@ -14,15 +14,21 @@ # License for the specific language governing permissions and limitations # under the License. +import socket + from oslo_config import cfg +from oslo_log import log from manila.common import constants from manila import exception +from manila.i18n import _ from manila import network from manila.network.neutron import api as neutron_api from manila.network.neutron import constants as neutron_constants from manila import utils +LOG = log.getLogger(__name__) + neutron_single_network_plugin_opts = [ cfg.StrOpt( 'neutron_net_id', @@ -39,12 +45,18 @@ neutron_single_network_plugin_opts = [ deprecated_group='DEFAULT'), ] -neutron_network_plugin_opts = [ +neutron_bind_network_plugin_opts = [ + cfg.StrOpt( + 'neutron_vnic_type', + help="vNIC type used for binding.", + choices=['baremetal', 'normal', 'direct', + 'direct-physical', 'macvtap'], + default='baremetal'), cfg.StrOpt( "neutron_host_id", - help="Host ID to be used when creating neutron port. Hostname of " - "a controller running Neutron should be used in a general case.", - deprecated_group='DEFAULT'), + help="Host ID to be used when creating neutron port. If not set " + "host is set to manila-share host by default.", + default=socket.gethostname()), ] CONF = cfg.CONF @@ -59,9 +71,6 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI): self._neutron_api_args = args self._neutron_api_kwargs = kwargs self._label = kwargs.pop('label', 'user') - CONF.register_opts( - neutron_network_plugin_opts, - group=self.neutron_api.config_group_name) @property def label(self): @@ -124,13 +133,22 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI): for port in ports: self._delete_port(context, port) + def _get_port_create_args(self, share_server, share_network, + device_owner): + return { + "network_id": share_network['neutron_net_id'], + "subnet_id": share_network['neutron_subnet_id'], + "device_owner": 'manila:' + device_owner, + "device_id": share_server.get('id'), + } + def _create_port(self, context, share_server, share_network, device_owner): - host_id = self.neutron_api.configuration.neutron_host_id + create_args = self._get_port_create_args(share_server, share_network, + device_owner) + port = self.neutron_api.create_port( - share_network['project_id'], - network_id=share_network['neutron_net_id'], - subnet_id=share_network['neutron_subnet_id'], - device_owner='manila:' + device_owner, host_id=host_id) + share_network['project_id'], **create_args) + port_dict = { 'id': port['id'], 'share_server_id': share_server['id'], @@ -214,7 +232,7 @@ class NeutronSingleNetworkPlugin(NeutronNetworkPlugin): 'neutron_net_id': self.net, 'neutron_subnet_id': self.subnet, } - super(NeutronSingleNetworkPlugin, self).allocate_network( + return super(NeutronSingleNetworkPlugin, self).allocate_network( context, share_server, share_network, **kwargs) def _verify_net_and_subnet(self): @@ -261,3 +279,73 @@ class NeutronSingleNetworkPlugin(NeutronNetworkPlugin): share_network = self.db.share_network_update( context, share_network['id'], upd) return share_network + + +class NeutronBindNetworkPlugin(NeutronNetworkPlugin): + def __init__(self, *args, **kwargs): + super(NeutronBindNetworkPlugin, self).__init__(*args, **kwargs) + CONF.register_opts( + neutron_bind_network_plugin_opts, + group=self.neutron_api.config_group_name) + self.config = self.neutron_api.configuration + + def update_network_allocation(self, context, share_server): + if self.config.neutron_vnic_type == 'normal': + ports = self.db.network_allocations_get_for_share_server( + context, + share_server['id']) + self._wait_for_ports_bind(ports, share_server) + return ports + + @utils.retry(exception.NetworkBindException, retries=20) + def _wait_for_ports_bind(self, ports, share_server): + inactive_ports = [] + for port in ports: + port = self._neutron_api.show_port(port['id']) + if (port['status'] == neutron_constants.PORT_STATUS_ERROR or + ('binding:vif_type' in port and + port['binding:vif_type'] == + neutron_constants.VIF_TYPE_BINDING_FAILED)): + msg = _("Port binding %s failed.") % port['id'] + raise exception.NetworkException(msg) + elif port['status'] != neutron_constants.PORT_STATUS_ACTIVE: + LOG.debug("The port %(id)s is in state %(state)s. " + "Wait for active state.", { + "id": port['id'], + "state": port['status']}) + inactive_ports.append(port['id']) + if len(inactive_ports) == 0: + return + msg = _("Ports are not fully bound for share server " + "'%(s_id)s' (inactive ports: %(ports)s)") % { + "s_id": share_server['id'], + "ports": inactive_ports} + raise exception.NetworkBindException(msg) + + def _get_port_create_args(self, share_server, share_network, + device_owner): + arguments = super( + NeutronBindNetworkPlugin, self)._get_port_create_args( + share_network, share_network, device_owner) + arguments['host_id'] = self.config.neutron_host_id + arguments['binding:vnic_type'] = self.config.neutron_vnic_type + return arguments + + def allocate_network(self, context, share_server, share_network=None, + **kwargs): + ports = super(NeutronBindNetworkPlugin, self).allocate_network( + context, share_server, share_network, **kwargs) + # If vnic type is 'normal' we expect a neutron agent to bind the + # ports. This action requires a vnic to be spawned by the driver. + # Therefore we do not wait for the port binding here, but + # return the unbound ports and expect the share manager to call + # update_network_allocation after the share server was created, in + # order to update the ports with the correct binding. + if self.config.neutron_vnic_type != 'normal': + self._wait_for_ports_bind(ports, share_server) + return ports + + +class NeutronBindSingleNetworkPlugin(NeutronSingleNetworkPlugin, + NeutronBindNetworkPlugin): + pass diff --git a/manila/opts.py b/manila/opts.py index 7058ac2735..064bcbeb95 100644 --- a/manila/opts.py +++ b/manila/opts.py @@ -100,7 +100,7 @@ _global_opt_lists = [ manila.network.linux.interface.OPTS, manila.network.network_opts, manila.network.neutron.neutron_network_plugin. - neutron_network_plugin_opts, + neutron_bind_network_plugin_opts, manila.network.neutron.neutron_network_plugin. neutron_single_network_plugin_opts, manila.network.nova_network_plugin.nova_single_network_plugin_opts, diff --git a/manila/share/driver.py b/manila/share/driver.py index 4d1237b56d..304c4b2149 100644 --- a/manila/share/driver.py +++ b/manila/share/driver.py @@ -570,6 +570,17 @@ class ShareDriver(object): def get_admin_network_allocations_number(self): return 0 + def update_network_allocation(self, context, share_server): + """Update network allocation after share server creation.""" + self.network_api.update_network_allocation(context, share_server) + + def update_admin_network_allocation(self, context, share_server): + """Update admin network allocation after share server creation.""" + if (self.get_admin_network_allocations_number() and + self.admin_network_api): + self.admin_network_api.update_network_allocation(context, + share_server) + def allocate_network(self, context, share_server, share_network, count=None, **kwargs): """Allocate network resources using given network information.""" diff --git a/manila/share/manager.py b/manila/share/manager.py index e1ee98f0cb..1be3a3595c 100644 --- a/manila/share/manager.py +++ b/manila/share/manager.py @@ -2375,6 +2375,9 @@ class ShareManager(manager.SchedulerDependentManager): server_info = self.driver.setup_server( network_info, metadata=metadata) + self.driver.update_network_allocation(context, share_server) + self.driver.update_admin_network_allocation(context, share_server) + if server_info and isinstance(server_info, dict): self.db.share_server_backend_details_set( context, share_server['id'], server_info) diff --git a/manila/tests/network/neutron/test_neutron_api.py b/manila/tests/network/neutron/test_neutron_api.py index 6d14768b7c..ef3ace5428 100644 --- a/manila/tests/network/neutron/test_neutron_api.py +++ b/manila/tests/network/neutron/test_neutron_api.py @@ -164,6 +164,25 @@ class NeutronApiTest(test.TestCase): self.neutron_api._has_port_binding_extension.assert_called_once_with() self.assertTrue(clientv20.Client.called) + def test_create_port_with_additional_kwargs(self): + # Set up test data + self.mock_object(self.neutron_api, '_has_port_binding_extension', + mock.Mock(return_value=True)) + port_args = {'tenant_id': 'test tenant', 'network_id': 'test net', + 'binding_arg': 'foo'} + + # Execute method 'create_port' + port = self.neutron_api.create_port(**port_args) + + # Verify results + self.assertEqual(port_args['tenant_id'], port['tenant_id']) + self.assertEqual(port_args['network_id'], + port['network_id']) + self.assertEqual(port_args['binding_arg'], + port['binding_arg']) + self.neutron_api._has_port_binding_extension.assert_called_once_with() + self.assertTrue(clientv20.Client.called) + @mock.patch.object(neutron_api.LOG, 'exception', mock.Mock()) def test_create_port_exception(self): # Set up test data diff --git a/manila/tests/network/neutron/test_neutron_plugin.py b/manila/tests/network/neutron/test_neutron_plugin.py index eeb333b34d..8bfdda8a5a 100644 --- a/manila/tests/network/neutron/test_neutron_plugin.py +++ b/manila/tests/network/neutron/test_neutron_plugin.py @@ -14,8 +14,11 @@ # License for the specific language governing permissions and limitations # under the License. +import copy import ddt import mock +import time + from oslo_config import cfg from manila.common import constants @@ -31,7 +34,7 @@ from manila.tests import utils as test_utils CONF = cfg.CONF fake_neutron_port = { - "status": "test_port_status", + "status": "ACTIVE", "allowed_address_pairs": [], "admin_state_up": True, "network_id": "test_net_id", @@ -132,7 +135,8 @@ class NeutronNetworkPluginTest(test.TestCase): fake_share_network['project_id'], network_id=fake_share_network['neutron_net_id'], subnet_id=fake_share_network['neutron_subnet_id'], - device_owner='manila:share', host_id=None) + device_owner='manila:share', + device_id=fake_share_network['id']) db_api.network_allocation_create.assert_called_once_with( self.fake_context, fake_network_allocation) @@ -141,54 +145,6 @@ class NeutronNetworkPluginTest(test.TestCase): save_nw_data.stop() save_subnet_data.stop() - @mock.patch.object(db_api, 'network_allocation_create', - mock.Mock(return_values=fake_network_allocation)) - @mock.patch.object(db_api, 'share_network_get', - mock.Mock(return_value=fake_share_network)) - @mock.patch.object(db_api, 'share_server_get', - mock.Mock(return_value=fake_share_server)) - def test_allocate_network_host_id_prconfigured(self): - has_provider_nw_ext = mock.patch.object( - self.plugin, '_has_provider_network_extension').start() - has_provider_nw_ext.return_value = True - save_nw_data = mock.patch.object(self.plugin, - '_save_neutron_network_data').start() - save_subnet_data = mock.patch.object( - self.plugin, - '_save_neutron_subnet_data').start() - - config_data = { - 'DEFAULT': { - 'neutron_host_id': 'fake_host_id', - } - } - with test_utils.create_temp_config_with_opts(config_data): - with mock.patch.object(self.plugin.neutron_api, 'create_port', - mock.Mock(return_value=fake_neutron_port)): - self.plugin.allocate_network( - self.fake_context, - fake_share_server, - fake_share_network, - allocation_info={'count': 1}) - - has_provider_nw_ext.assert_any_call() - save_nw_data.assert_called_once_with(self.fake_context, - fake_share_network) - save_subnet_data.assert_called_once_with(self.fake_context, - fake_share_network) - self.plugin.neutron_api.create_port.assert_called_once_with( - fake_share_network['project_id'], - network_id=fake_share_network['neutron_net_id'], - subnet_id=fake_share_network['neutron_subnet_id'], - device_owner='manila:share', host_id='fake_host_id') - db_api.network_allocation_create.assert_called_once_with( - self.fake_context, - fake_network_allocation) - - has_provider_nw_ext.stop() - save_nw_data.stop() - save_subnet_data.stop() - @mock.patch.object(db_api, 'network_allocation_create', mock.Mock(return_values=fake_network_allocation)) @mock.patch.object(db_api, 'share_network_get', @@ -217,11 +173,13 @@ class NeutronNetworkPluginTest(test.TestCase): mock.call(fake_share_network['project_id'], network_id=fake_share_network['neutron_net_id'], subnet_id=fake_share_network['neutron_subnet_id'], - device_owner='manila:share', host_id=None), + device_owner='manila:share', + device_id=fake_share_network['id']), mock.call(fake_share_network['project_id'], network_id=fake_share_network['neutron_net_id'], subnet_id=fake_share_network['neutron_subnet_id'], - device_owner='manila:share', host_id=None), + device_owner='manila:share', + device_id=fake_share_network['id']), ] db_api_calls = [ mock.call(self.fake_context, fake_network_allocation), @@ -541,3 +499,559 @@ class NeutronSingleNetworkPluginTest(test.TestCase): plugin.NeutronNetworkPlugin.allocate_network.assert_called_once_with( self.context, share_server, share_network_upd, count=count, device_owner=device_owner) + plugin.NeutronNetworkPlugin.allocate_network.reset_mock() + + +@ddt.ddt +class NeutronBindNetworkPluginTest(test.TestCase): + def setUp(self): + super(NeutronBindNetworkPluginTest, self).setUp() + self.fake_context = context.RequestContext(user_id='fake user', + project_id='fake project', + is_admin=False) + self.has_binding_ext_mock = self.mock_object( + neutron_api.API, '_has_port_binding_extension') + self.has_binding_ext_mock.return_value = True + self.bind_plugin = plugin.NeutronBindNetworkPlugin() + self.bind_plugin.db = db_api + self.sleep_mock = self.mock_object(time, 'sleep') + + def test_wait_for_bind(self): + self.mock_object(self.bind_plugin.neutron_api, 'show_port') + self.bind_plugin.neutron_api.show_port.return_value = fake_neutron_port + + self.bind_plugin._wait_for_ports_bind([fake_neutron_port], + fake_share_server) + + self.bind_plugin.neutron_api.show_port.assert_called_once_with( + fake_neutron_port['id']) + self.sleep_mock.assert_not_called() + + def test_wait_for_bind_error(self): + fake_neut_port = copy.copy(fake_neutron_port) + fake_neut_port['status'] = 'ERROR' + self.mock_object(self.bind_plugin.neutron_api, 'show_port') + self.bind_plugin.neutron_api.show_port.return_value = fake_neut_port + + self.assertRaises(exception.ManilaException, + self.bind_plugin._wait_for_ports_bind, + [fake_neut_port, fake_neut_port], + fake_share_server) + + self.bind_plugin.neutron_api.show_port.assert_called_once_with( + fake_neutron_port['id']) + self.sleep_mock.assert_not_called() + + @ddt.data(('DOWN', 'ACTIVE'), ('DOWN', 'DOWN'), ('ACTIVE', 'DOWN')) + @mock.patch.object(time, 'time', side_effect=[1, 1, 3]) + def test_wait_for_bind_two_ports_no_bind(self, state, time_mock): + fake_neut_port1 = copy.copy(fake_neutron_port) + fake_neut_port1['status'] = state[0] + fake_neut_port2 = copy.copy(fake_neutron_port) + fake_neut_port2['status'] = state[1] + self.mock_object(self.bind_plugin.neutron_api, 'show_port') + self.bind_plugin.neutron_api.show_port.side_effect = [fake_neut_port1, + fake_neut_port2] + + self.assertRaises(exception.ManilaException, + self.bind_plugin._wait_for_ports_bind.__wrapped__, + self.bind_plugin, + [fake_neut_port1, fake_neut_port2], + fake_share_server) + + @mock.patch.object(db_api, 'network_allocation_create', + mock.Mock(return_values=fake_network_allocation)) + @mock.patch.object(db_api, 'share_network_get', + mock.Mock(return_value=fake_share_network)) + @mock.patch.object(db_api, 'share_server_get', + mock.Mock(return_value=fake_share_server)) + def test_allocate_network_one_allocation(self): + self.mock_object(self.bind_plugin, '_has_provider_network_extension') + self.bind_plugin._has_provider_network_extension.return_value = True + save_nw_data = self.mock_object(self.bind_plugin, + '_save_neutron_network_data') + save_subnet_data = self.mock_object(self.bind_plugin, + '_save_neutron_subnet_data') + self.mock_object(self.bind_plugin, '_wait_for_ports_bind') + neutron_host_id_opts = plugin.neutron_bind_network_plugin_opts[1] + self.mock_object(neutron_host_id_opts, 'default') + neutron_host_id_opts.default = 'foohost1' + self.mock_object(db_api, 'network_allocation_create') + + with mock.patch.object(self.bind_plugin.neutron_api, 'create_port', + mock.Mock(return_value=fake_neutron_port)): + self.bind_plugin.allocate_network( + self.fake_context, + fake_share_server, + fake_share_network, + allocation_info={'count': 1}) + + self.bind_plugin._has_provider_network_extension.assert_any_call() + save_nw_data.assert_called_once_with(self.fake_context, + fake_share_network) + save_subnet_data.assert_called_once_with(self.fake_context, + fake_share_network) + expected_kwargs = { + 'binding:vnic_type': 'baremetal', + 'host_id': 'foohost1', + 'network_id': fake_share_network['neutron_net_id'], + 'subnet_id': fake_share_network['neutron_subnet_id'], + 'device_owner': 'manila:share', + 'device_id': fake_share_network['id'], + } + self.bind_plugin.neutron_api.create_port.assert_called_once_with( + fake_share_network['project_id'], **expected_kwargs) + db_api.network_allocation_create.assert_called_once_with( + self.fake_context, + fake_network_allocation) + self.bind_plugin._wait_for_ports_bind.assert_called_once_with( + [db_api.network_allocation_create()], fake_share_server) + + +@ddt.ddt +class NeutronBindSingleNetworkPluginTest(test.TestCase): + def setUp(self): + super(NeutronBindSingleNetworkPluginTest, self).setUp() + self.context = 'fake_context' + self.fake_context = context.RequestContext(user_id='fake user', + project_id='fake project', + is_admin=False) + self.has_binding_ext_mock = self.mock_object( + neutron_api.API, '_has_port_binding_extension') + self.has_binding_ext_mock.return_value = True + self.bind_plugin = plugin.NeutronBindNetworkPlugin() + self.bind_plugin.db = db_api + self.sleep_mock = self.mock_object(time, 'sleep') + 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.mock_object( + neutron_api.API, 'get_network', mock.Mock(return_value=fake_net)) + + with test_utils.create_temp_config_with_opts(config_data): + self.bind_plugin = plugin.NeutronBindSingleNetworkPlugin() + self.bind_plugin.db = db_api + + 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.mock_object( + 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.mock_object( + 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.mock_object( + 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) + + def test___update_share_network_net_data_different_values_empty(self): + instance = self._get_neutron_single_network_plugin_instance() + share_network_input = { + 'id': 'fake_share_network_id', + } + share_network_result = { + 'neutron_net_id': instance.net, + 'neutron_subnet_id': instance.subnet, + } + self.mock_object( + instance.db, 'share_network_update', + mock.Mock(return_value='foo')) + + instance._update_share_network_net_data( + self.context, share_network_input) + + instance.db.share_network_update.assert_called_once_with( + self.context, share_network_input['id'], share_network_result) + + @ddt.data( + {'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.mock_object( + instance.db, 'share_network_update', + mock.Mock(return_value=share_network)) + + self.assertRaises( + exception.NetworkBadConfigurationException, + instance._update_share_network_net_data, + self.context, share_network) + self.assertFalse(instance.db.share_network_update.called) + + def test___update_share_network_net_data_nova_net_id_present(self): + instance = self._get_neutron_single_network_plugin_instance() + share_network = { + 'id': 'fake_share_network_id', + 'nova_net_id': 'foo', + } + self.mock_object( + instance.db, 'share_network_update', + mock.Mock(return_value=share_network)) + + self.assertRaises( + exception.NetworkBadConfigurationException, + instance._update_share_network_net_data, + self.context, share_network) + self.assertFalse(instance.db.share_network_update.called) + + @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.mock_object( + 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) + plugin.NeutronNetworkPlugin.allocate_network.reset_mock() + + def test_wait_for_bind(self): + self.mock_object(self.bind_plugin.neutron_api, 'show_port') + self.bind_plugin.neutron_api.show_port.return_value = fake_neutron_port + + self.bind_plugin._wait_for_ports_bind([fake_neutron_port], + fake_share_server) + + self.bind_plugin.neutron_api.show_port.assert_called_once_with( + fake_neutron_port['id']) + self.sleep_mock.assert_not_called() + + def test_wait_for_bind_error(self): + fake_neut_port = copy.copy(fake_neutron_port) + fake_neut_port['status'] = 'ERROR' + self.mock_object(self.bind_plugin.neutron_api, 'show_port') + self.bind_plugin.neutron_api.show_port.return_value = fake_neut_port + + self.assertRaises(exception.ManilaException, + self.bind_plugin._wait_for_ports_bind, + [fake_neut_port, fake_neut_port], + fake_share_server) + + self.bind_plugin.neutron_api.show_port.assert_called_once_with( + fake_neutron_port['id']) + self.sleep_mock.assert_not_called() + + @ddt.data(('DOWN', 'ACTIVE'), ('DOWN', 'DOWN'), ('ACTIVE', 'DOWN')) + @mock.patch.object(time, 'time', side_effect=[1, 1, 3]) + def test_wait_for_bind_two_ports_no_bind(self, state, time_mock): + fake_neut_port1 = copy.copy(fake_neutron_port) + fake_neut_port1['status'] = state[0] + fake_neut_port2 = copy.copy(fake_neutron_port) + fake_neut_port2['status'] = state[1] + self.mock_object(self.bind_plugin.neutron_api, 'show_port') + self.bind_plugin.neutron_api.show_port.side_effect = [fake_neut_port1, + fake_neut_port2] + + self.assertRaises(exception.NetworkBindException, + self.bind_plugin._wait_for_ports_bind.__wrapped__, + self.bind_plugin, + [fake_neut_port1, fake_neut_port2], + fake_share_server) + + @mock.patch.object(db_api, 'network_allocation_create', + mock.Mock(return_values=fake_network_allocation)) + @mock.patch.object(db_api, 'share_network_get', + mock.Mock(return_value=fake_share_network)) + @mock.patch.object(db_api, 'share_server_get', + mock.Mock(return_value=fake_share_server)) + def test_allocate_network_one_allocation(self): + self.mock_object(self.bind_plugin, '_has_provider_network_extension') + self.bind_plugin._has_provider_network_extension.return_value = True + save_nw_data = self.mock_object(self.bind_plugin, + '_save_neutron_network_data') + save_subnet_data = self.mock_object(self.bind_plugin, + '_save_neutron_subnet_data') + self.mock_object(self.bind_plugin, '_wait_for_ports_bind') + neutron_host_id_opts = plugin.neutron_bind_network_plugin_opts[1] + self.mock_object(neutron_host_id_opts, 'default') + neutron_host_id_opts.default = 'foohost1' + self.mock_object(db_api, 'network_allocation_create') + + with mock.patch.object(self.bind_plugin.neutron_api, 'create_port', + mock.Mock(return_value=fake_neutron_port)): + self.bind_plugin.allocate_network( + self.fake_context, + fake_share_server, + fake_share_network, + allocation_info={'count': 1}) + + self.bind_plugin._has_provider_network_extension.assert_any_call() + save_nw_data.assert_called_once_with(self.fake_context, + fake_share_network) + save_subnet_data.assert_called_once_with(self.fake_context, + fake_share_network) + expected_kwargs = { + 'binding:vnic_type': 'baremetal', + 'host_id': 'foohost1', + 'network_id': fake_share_network['neutron_net_id'], + 'subnet_id': fake_share_network['neutron_subnet_id'], + 'device_owner': 'manila:share', + 'device_id': fake_share_network['id'], + } + self.bind_plugin.neutron_api.create_port.assert_called_once_with( + fake_share_network['project_id'], **expected_kwargs) + db_api.network_allocation_create.assert_called_once_with( + self.fake_context, + fake_network_allocation) + self.bind_plugin._wait_for_ports_bind.assert_called_once_with( + [db_api.network_allocation_create()], fake_share_server) + + +class NeutronBindNetworkPluginWithNormalTypeTest(test.TestCase): + def setUp(self): + super(NeutronBindNetworkPluginWithNormalTypeTest, self).setUp() + config_data = { + 'DEFAULT': { + 'neutron_vnic_type': 'normal', + } + } + self.plugin = plugin.NeutronNetworkPlugin() + self.plugin.db = db_api + self.fake_context = context.RequestContext(user_id='fake user', + project_id='fake project', + is_admin=False) + + with test_utils.create_temp_config_with_opts(config_data): + self.bind_plugin = plugin.NeutronBindNetworkPlugin() + self.bind_plugin.db = db_api + + @mock.patch.object(db_api, 'network_allocation_create', + mock.Mock(return_values=fake_network_allocation)) + @mock.patch.object(db_api, 'share_network_get', + mock.Mock(return_value=fake_share_network)) + @mock.patch.object(db_api, 'share_server_get', + mock.Mock(return_value=fake_share_server)) + def test_allocate_network_one_allocation(self): + self.mock_object(self.bind_plugin, '_has_provider_network_extension') + self.bind_plugin._has_provider_network_extension.return_value = True + save_nw_data = self.mock_object(self.bind_plugin, + '_save_neutron_network_data') + save_subnet_data = self.mock_object(self.bind_plugin, + '_save_neutron_subnet_data') + self.mock_object(self.bind_plugin, '_wait_for_ports_bind') + neutron_host_id_opts = plugin.neutron_bind_network_plugin_opts[1] + self.mock_object(neutron_host_id_opts, 'default') + neutron_host_id_opts.default = 'foohost1' + self.mock_object(db_api, 'network_allocation_create') + + with mock.patch.object(self.bind_plugin.neutron_api, 'create_port', + mock.Mock(return_value=fake_neutron_port)): + self.bind_plugin.allocate_network( + self.fake_context, + fake_share_server, + fake_share_network, + allocation_info={'count': 1}) + + self.bind_plugin._has_provider_network_extension.assert_any_call() + save_nw_data.assert_called_once_with(self.fake_context, + fake_share_network) + save_subnet_data.assert_called_once_with(self.fake_context, + fake_share_network) + expected_kwargs = { + 'binding:vnic_type': 'normal', + 'host_id': 'foohost1', + 'network_id': fake_share_network['neutron_net_id'], + 'subnet_id': fake_share_network['neutron_subnet_id'], + 'device_owner': 'manila:share', + 'device_id': fake_share_network['id'], + } + self.bind_plugin.neutron_api.create_port.assert_called_once_with( + fake_share_network['project_id'], **expected_kwargs) + db_api.network_allocation_create.assert_called_once_with( + self.fake_context, + fake_network_allocation) + self.bind_plugin._wait_for_ports_bind.assert_not_called() + + def test_update_network_allocation(self): + self.mock_object(self.bind_plugin, '_wait_for_ports_bind') + self.mock_object(db_api, 'network_allocations_get_for_share_server') + db_api.network_allocations_get_for_share_server.return_value = [ + fake_neutron_port] + + self.bind_plugin.update_network_allocation(self.fake_context, + fake_share_server) + + self.bind_plugin._wait_for_ports_bind.assert_called_once_with( + [fake_neutron_port], fake_share_server) + + +class NeutronBindSingleNetworkPluginWithNormalTypeTest(test.TestCase): + def setUp(self): + super(NeutronBindSingleNetworkPluginWithNormalTypeTest, self).setUp() + 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, + 'neutron_vnic_type': 'normal', + } + } + fake_net = {'subnets': ['fake1', 'fake2', fake_subnet_id]} + self.mock_object( + neutron_api.API, 'get_network', mock.Mock(return_value=fake_net)) + self.plugin = plugin.NeutronNetworkPlugin() + self.plugin.db = db_api + self.fake_context = context.RequestContext(user_id='fake user', + project_id='fake project', + is_admin=False) + + with test_utils.create_temp_config_with_opts(config_data): + self.bind_plugin = plugin.NeutronBindSingleNetworkPlugin() + self.bind_plugin.db = db_api + + @mock.patch.object(db_api, 'network_allocation_create', + mock.Mock(return_values=fake_network_allocation)) + @mock.patch.object(db_api, 'share_network_get', + mock.Mock(return_value=fake_share_network)) + @mock.patch.object(db_api, 'share_server_get', + mock.Mock(return_value=fake_share_server)) + def test_allocate_network_one_allocation(self): + self.mock_object(self.bind_plugin, '_has_provider_network_extension') + self.bind_plugin._has_provider_network_extension.return_value = True + save_nw_data = self.mock_object(self.bind_plugin, + '_save_neutron_network_data') + save_subnet_data = self.mock_object(self.bind_plugin, + '_save_neutron_subnet_data') + self.mock_object(self.bind_plugin, '_wait_for_ports_bind') + neutron_host_id_opts = plugin.neutron_bind_network_plugin_opts[1] + self.mock_object(neutron_host_id_opts, 'default') + neutron_host_id_opts.default = 'foohost1' + self.mock_object(db_api, 'network_allocation_create') + + with mock.patch.object(self.bind_plugin.neutron_api, 'create_port', + mock.Mock(return_value=fake_neutron_port)): + self.bind_plugin.allocate_network( + self.fake_context, + fake_share_server, + fake_share_network, + allocation_info={'count': 1}) + + self.bind_plugin._has_provider_network_extension.assert_any_call() + save_nw_data.assert_called_once_with(self.fake_context, + fake_share_network) + save_subnet_data.assert_called_once_with(self.fake_context, + fake_share_network) + expected_kwargs = { + 'binding:vnic_type': 'normal', + 'host_id': 'foohost1', + 'network_id': fake_share_network['neutron_net_id'], + 'subnet_id': fake_share_network['neutron_subnet_id'], + 'device_owner': 'manila:share', + 'device_id': fake_share_network['id'], + } + self.bind_plugin.neutron_api.create_port.assert_called_once_with( + fake_share_network['project_id'], **expected_kwargs) + db_api.network_allocation_create.assert_called_once_with( + self.fake_context, + fake_network_allocation) + self.bind_plugin._wait_for_ports_bind.assert_not_called() + + def test_update_network_allocation(self): + self.mock_object(self.bind_plugin, '_wait_for_ports_bind') + self.mock_object(db_api, 'network_allocations_get_for_share_server') + db_api.network_allocations_get_for_share_server.return_value = [ + fake_neutron_port] + + self.bind_plugin.update_network_allocation(self.fake_context, + fake_share_server) + + self.bind_plugin._wait_for_ports_bind.assert_called_once_with( + [fake_neutron_port], fake_share_server) diff --git a/releasenotes/notes/neutron-binding-driver-43f01565051b031b.yaml b/releasenotes/notes/neutron-binding-driver-43f01565051b031b.yaml new file mode 100644 index 0000000000..4c8b0633f4 --- /dev/null +++ b/releasenotes/notes/neutron-binding-driver-43f01565051b031b.yaml @@ -0,0 +1,3 @@ +--- +features: + - Added neutron driver for port bind actions.