Merge "svm migration across physical networks with multiple port bindings"
This commit is contained in:
commit
1ffa0c1273
@ -1333,6 +1333,12 @@ def share_server_backend_details_set(context, share_server_id, server_details):
|
|||||||
server_details)
|
server_details)
|
||||||
|
|
||||||
|
|
||||||
|
def share_server_backend_details_get_item(context, share_server_id, meta_key):
|
||||||
|
"""Get backend details."""
|
||||||
|
return IMPL.share_server_backend_details_get_item(context, share_server_id,
|
||||||
|
meta_key)
|
||||||
|
|
||||||
|
|
||||||
def share_server_backend_details_delete(context, share_server_id):
|
def share_server_backend_details_delete(context, share_server_id):
|
||||||
"""Delete backend details DB records for a share server."""
|
"""Delete backend details DB records for a share server."""
|
||||||
return IMPL.share_server_backend_details_delete(context, share_server_id)
|
return IMPL.share_server_backend_details_delete(context, share_server_id)
|
||||||
|
@ -5617,6 +5617,17 @@ def share_server_backend_details_set(context, share_server_id, server_details):
|
|||||||
return server_details
|
return server_details
|
||||||
|
|
||||||
|
|
||||||
|
@require_context
|
||||||
|
@context_manager.reader
|
||||||
|
def share_server_backend_details_get_item(context, share_server_id, meta_key):
|
||||||
|
try:
|
||||||
|
meta_ref = _share_server_backend_details_get_item(
|
||||||
|
context, share_server_id, meta_key)
|
||||||
|
except exception.ShareServerBackendDetailsNotFound:
|
||||||
|
return None
|
||||||
|
return meta_ref.get('value')
|
||||||
|
|
||||||
|
|
||||||
@require_context
|
@require_context
|
||||||
@context_manager.writer
|
@context_manager.writer
|
||||||
def share_server_backend_details_delete(context, share_server_id):
|
def share_server_backend_details_delete(context, share_server_id):
|
||||||
@ -5784,7 +5795,7 @@ def network_allocations_get_for_share_server(
|
|||||||
share_server_id=share_server_id,
|
share_server_id=share_server_id,
|
||||||
)
|
)
|
||||||
if label:
|
if label:
|
||||||
if label != 'admin':
|
if label == 'user':
|
||||||
query = query.filter(or_(
|
query = query.filter(or_(
|
||||||
# NOTE(vponomaryov): we treat None as alias for 'user'.
|
# NOTE(vponomaryov): we treat None as alias for 'user'.
|
||||||
models.NetworkAllocation.label == None, # noqa
|
models.NetworkAllocation.label == None, # noqa
|
||||||
|
@ -66,6 +66,19 @@ CONF = cfg.CONF
|
|||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class PortBindingAlreadyExistsClient(neutron_client_exc.Conflict):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# We need to monkey-patch neutronclient.common.exceptions module, to make
|
||||||
|
# neutron client to raise error specific exceptions. E.g. exception
|
||||||
|
# PortBindingAlreadyExistsClient is raised for Neutron API error
|
||||||
|
# PortBindingAlreadyExists. If not defined, a general exception of type
|
||||||
|
# Conflict will be raised.
|
||||||
|
neutron_client_exc.PortBindingAlreadyExistsClient = \
|
||||||
|
PortBindingAlreadyExistsClient
|
||||||
|
|
||||||
|
|
||||||
def list_opts():
|
def list_opts():
|
||||||
return client_auth.AuthClientLoader.list_opts(NEUTRON_GROUP)
|
return client_auth.AuthClientLoader.list_opts(NEUTRON_GROUP)
|
||||||
|
|
||||||
@ -336,6 +349,32 @@ class API(object):
|
|||||||
raise exception.NetworkException(code=e.status_code,
|
raise exception.NetworkException(code=e.status_code,
|
||||||
message=e.message)
|
message=e.message)
|
||||||
|
|
||||||
|
def bind_port_to_host(self, port_id, host, vnic_type):
|
||||||
|
"""Add an inactive binding to existing port."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = {"binding": {"host": host, "vnic_type": vnic_type}}
|
||||||
|
return self.client.create_port_binding(port_id, data)['binding']
|
||||||
|
except neutron_client_exc.PortBindingAlreadyExistsClient as e:
|
||||||
|
LOG.warning('Port binding already exists: %s', e)
|
||||||
|
except neutron_client_exc.NeutronClientException as e:
|
||||||
|
raise exception.NetworkException(
|
||||||
|
code=e.status_code, message=e.message)
|
||||||
|
|
||||||
|
def delete_port_binding(self, port_id, host):
|
||||||
|
try:
|
||||||
|
return self.client.delete_port_binding(port_id, host)
|
||||||
|
except neutron_client_exc.NeutronClientException as e:
|
||||||
|
raise exception.NetworkException(
|
||||||
|
code=e.status_code, message=e.message)
|
||||||
|
|
||||||
|
def activate_port_binding(self, port_id, host):
|
||||||
|
try:
|
||||||
|
return self.client.activate_port_binding(port_id, host)
|
||||||
|
except neutron_client_exc.NeutronClientException as e:
|
||||||
|
raise exception.NetworkException(
|
||||||
|
code=e.status_code, message=e.message)
|
||||||
|
|
||||||
def show_router(self, router_id):
|
def show_router(self, router_id):
|
||||||
try:
|
try:
|
||||||
return self.client.show_router(router_id).get('router', {})
|
return self.client.show_router(router_id).get('router', {})
|
||||||
|
@ -26,6 +26,7 @@ from manila.i18n import _
|
|||||||
from manila import network
|
from manila import network
|
||||||
from manila.network.neutron import api as neutron_api
|
from manila.network.neutron import api as neutron_api
|
||||||
from manila.network.neutron import constants as neutron_constants
|
from manila.network.neutron import constants as neutron_constants
|
||||||
|
from manila.share import utils as share_utils
|
||||||
from manila import utils
|
from manila import utils
|
||||||
|
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
@ -718,6 +719,70 @@ class NeutronBindNetworkPlugin(NeutronNetworkPlugin):
|
|||||||
context, port['id'], port_info)
|
context, port['id'], port_info)
|
||||||
return ports
|
return ports
|
||||||
|
|
||||||
|
@utils.retry(retry_param=exception.NetworkException, retries=20)
|
||||||
|
def _wait_for_network_segment(self, share_server, host):
|
||||||
|
network_id = share_server['share_network_subnet']['neutron_net_id']
|
||||||
|
network = self.neutron_api.get_network(network_id)
|
||||||
|
for segment in network['segments']:
|
||||||
|
if segment['provider:physical_network'] == (
|
||||||
|
self.config.neutron_physical_net_name):
|
||||||
|
return segment['provider:segmentation_id']
|
||||||
|
msg = _('Network segment not found on host %s') % host
|
||||||
|
raise exception.NetworkException(msg)
|
||||||
|
|
||||||
|
def extend_network_allocations(self, context, share_server):
|
||||||
|
"""Extend network to target host.
|
||||||
|
|
||||||
|
This will create port bindings on target host without activating them.
|
||||||
|
If network segment does not exist on target host, it will be created.
|
||||||
|
|
||||||
|
:return: list of port bindings with new segmentation id on target host
|
||||||
|
"""
|
||||||
|
vnic_type = self.config.neutron_vnic_type
|
||||||
|
host_id = self.config.neutron_host_id
|
||||||
|
|
||||||
|
active_port_bindings = (
|
||||||
|
self.db.network_allocations_get_for_share_server(
|
||||||
|
context, share_server['id'], label='user'))
|
||||||
|
if len(active_port_bindings) == 0:
|
||||||
|
raise exception.NetworkException(
|
||||||
|
'Can not extend network with no active bindings')
|
||||||
|
|
||||||
|
# Create port binding on destination backend. It's safe to call neutron
|
||||||
|
# api bind_port_to_host if the port is already bound to destination
|
||||||
|
# host.
|
||||||
|
for port in active_port_bindings:
|
||||||
|
self.neutron_api.bind_port_to_host(port['id'], host_id,
|
||||||
|
vnic_type)
|
||||||
|
|
||||||
|
# Wait for network segment to be created on destination host.
|
||||||
|
vlan = self._wait_for_network_segment(share_server, host_id)
|
||||||
|
for port in active_port_bindings:
|
||||||
|
port['segmentation_id'] = vlan
|
||||||
|
|
||||||
|
return active_port_bindings
|
||||||
|
|
||||||
|
def delete_extended_allocations(self, context, share_server):
|
||||||
|
host_id = self.config.neutron_host_id
|
||||||
|
ports = self.db.network_allocations_get_for_share_server(
|
||||||
|
context, share_server['id'], label='user')
|
||||||
|
for port in ports:
|
||||||
|
try:
|
||||||
|
self.neutron_api.delete_port_binding(port['id'], host_id)
|
||||||
|
except exception.NetworkException as e:
|
||||||
|
msg = 'Failed to delete port binding on port %{port}s: %{err}s'
|
||||||
|
LOG.warning(msg, {'port': port['id'], 'err': e})
|
||||||
|
|
||||||
|
def cutover_network_allocations(self, context, src_share_server):
|
||||||
|
src_host = share_utils.extract_host(src_share_server['host'], 'host')
|
||||||
|
dest_host = self.config.neutron_host_id
|
||||||
|
ports = self.db.network_allocations_get_for_share_server(
|
||||||
|
context, src_share_server['id'], label='user')
|
||||||
|
for port in ports:
|
||||||
|
self.neutron_api.activate_port_binding(port['id'], dest_host)
|
||||||
|
self.neutron_api.delete_port_binding(port['id'], src_host)
|
||||||
|
return ports
|
||||||
|
|
||||||
|
|
||||||
class NeutronBindSingleNetworkPlugin(NeutronSingleNetworkPlugin,
|
class NeutronBindSingleNetworkPlugin(NeutronSingleNetworkPlugin,
|
||||||
NeutronBindNetworkPlugin):
|
NeutronBindNetworkPlugin):
|
||||||
|
@ -6291,6 +6291,7 @@ class NetAppCmodeClient(client_base.NetAppBaseClient):
|
|||||||
raise exception.NetAppException(message=e.message)
|
raise exception.NetAppException(message=e.message)
|
||||||
msg = (_('Failed to check migration support. Reason: '
|
msg = (_('Failed to check migration support. Reason: '
|
||||||
'%s' % e.message))
|
'%s' % e.message))
|
||||||
|
LOG.error(msg)
|
||||||
raise exception.NetAppException(msg)
|
raise exception.NetAppException(msg)
|
||||||
|
|
||||||
@na_utils.trace
|
@na_utils.trace
|
||||||
|
@ -113,6 +113,14 @@ share_manager_opts = [
|
|||||||
'the share manager will poll the driver to perform the '
|
'the share manager will poll the driver to perform the '
|
||||||
'next step of migration in the storage backend, for a '
|
'next step of migration in the storage backend, for a '
|
||||||
'migrating share server.'),
|
'migrating share server.'),
|
||||||
|
cfg.BoolOpt('server_migration_extend_neutron_network',
|
||||||
|
default=False,
|
||||||
|
help='If set to True, neutron network are extended to '
|
||||||
|
'destination host during share server migration. This '
|
||||||
|
'option should only be enabled if using '
|
||||||
|
'NeutronNetworkPlugin or its derivatives and when '
|
||||||
|
'multiple bindings of Manila ports are supported by '
|
||||||
|
'Neutron ML2 plugin.'),
|
||||||
cfg.IntOpt('share_usage_size_update_interval',
|
cfg.IntOpt('share_usage_size_update_interval',
|
||||||
default=300,
|
default=300,
|
||||||
help='This value, specified in seconds, determines how often '
|
help='This value, specified in seconds, determines how often '
|
||||||
@ -5716,8 +5724,20 @@ class ShareManager(manager.SchedulerDependentManager):
|
|||||||
availability_zone_id=service['availability_zone_id'],
|
availability_zone_id=service['availability_zone_id'],
|
||||||
share_network_id=new_share_network_id))
|
share_network_id=new_share_network_id))
|
||||||
|
|
||||||
|
extended_allocs = None
|
||||||
dest_share_server = None
|
dest_share_server = None
|
||||||
try:
|
try:
|
||||||
|
# NOTE: Extend network allocations to destination host, i.e. create
|
||||||
|
# inactive port bindings on the destination host. Refresh
|
||||||
|
# network_allocations field in source_share_server with the new
|
||||||
|
# bindings, so that correct segmentation id is used during
|
||||||
|
# compatibility check and migration.
|
||||||
|
if CONF.server_migration_extend_neutron_network:
|
||||||
|
extended_allocs = (
|
||||||
|
self.driver.network_api.extend_network_allocations(
|
||||||
|
context, source_share_server))
|
||||||
|
source_share_server['network_allocations'] = extended_allocs
|
||||||
|
|
||||||
compatibility = (
|
compatibility = (
|
||||||
self.driver.share_server_migration_check_compatibility(
|
self.driver.share_server_migration_check_compatibility(
|
||||||
context, source_share_server, dest_host, old_share_network,
|
context, source_share_server, dest_host, old_share_network,
|
||||||
@ -5797,6 +5817,10 @@ class ShareManager(manager.SchedulerDependentManager):
|
|||||||
|
|
||||||
backend_details = (
|
backend_details = (
|
||||||
server_info.get('backend_details') if server_info else None)
|
server_info.get('backend_details') if server_info else None)
|
||||||
|
if extended_allocs:
|
||||||
|
backend_details = backend_details or {}
|
||||||
|
backend_details['segmentation_id'] = (
|
||||||
|
extended_allocs[0]['segmentation_id'])
|
||||||
if backend_details:
|
if backend_details:
|
||||||
self.db.share_server_backend_details_set(
|
self.db.share_server_backend_details_set(
|
||||||
context, dest_share_server['id'], backend_details)
|
context, dest_share_server['id'], backend_details)
|
||||||
@ -5816,6 +5840,10 @@ class ShareManager(manager.SchedulerDependentManager):
|
|||||||
context, constants.STATUS_AVAILABLE,
|
context, constants.STATUS_AVAILABLE,
|
||||||
share_instance_ids=share_instance_ids,
|
share_instance_ids=share_instance_ids,
|
||||||
snapshot_instance_ids=snapshot_instance_ids)
|
snapshot_instance_ids=snapshot_instance_ids)
|
||||||
|
# Rollback port bindings on destination host
|
||||||
|
if extended_allocs:
|
||||||
|
self.driver.network_api.delete_extended_allocations(
|
||||||
|
context, source_share_server, dest_share_server)
|
||||||
# Rollback read only access rules
|
# Rollback read only access rules
|
||||||
self._reset_read_only_access_rules_for_server(
|
self._reset_read_only_access_rules_for_server(
|
||||||
context, share_instances, source_share_server,
|
context, share_instances, source_share_server,
|
||||||
@ -5879,6 +5907,22 @@ class ShareManager(manager.SchedulerDependentManager):
|
|||||||
service = self.db.service_get_by_args(
|
service = self.db.service_get_by_args(
|
||||||
context, service_host, 'manila-share')
|
context, service_host, 'manila-share')
|
||||||
|
|
||||||
|
# NOTE: Extend network allocations to destination host, i.e. create
|
||||||
|
# inactive port bindings on destination host with the same ports in the
|
||||||
|
# share network subnet. Refresh share_server with new network
|
||||||
|
# allocations, so that correct segmentation id is used in the
|
||||||
|
# compatibility check.
|
||||||
|
if CONF.server_migration_extend_neutron_network:
|
||||||
|
try:
|
||||||
|
allocs = self.driver.network_api.extend_network_allocations(
|
||||||
|
context, share_server)
|
||||||
|
share_server['network_allocations'] = allocs
|
||||||
|
except Exception:
|
||||||
|
LOG.warning(
|
||||||
|
'Failed to extend network allocations for '
|
||||||
|
'share server %s.', share_server['id'])
|
||||||
|
return result
|
||||||
|
|
||||||
# NOTE(dviroel): We'll build a list of request specs and send it to
|
# NOTE(dviroel): We'll build a list of request specs and send it to
|
||||||
# the driver so vendors have a chance to validate if the destination
|
# the driver so vendors have a chance to validate if the destination
|
||||||
# host meets the requirements before starting the migration.
|
# host meets the requirements before starting the migration.
|
||||||
@ -5906,6 +5950,12 @@ class ShareManager(manager.SchedulerDependentManager):
|
|||||||
# the validations.
|
# the validations.
|
||||||
driver_result['compatible'] = False
|
driver_result['compatible'] = False
|
||||||
|
|
||||||
|
# NOTE: Delete port bindings on destination host after compatibility
|
||||||
|
# check
|
||||||
|
if CONF.server_migration_extend_neutron_network:
|
||||||
|
self.driver.network_api.delete_extended_allocations(
|
||||||
|
context, share_server)
|
||||||
|
|
||||||
result.update(driver_result)
|
result.update(driver_result)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
@ -6148,6 +6198,29 @@ class ShareManager(manager.SchedulerDependentManager):
|
|||||||
dest_snss = self.db.share_network_subnet_get_all_by_share_server_id(
|
dest_snss = self.db.share_network_subnet_get_all_by_share_server_id(
|
||||||
context, dest_share_server['id'])
|
context, dest_share_server['id'])
|
||||||
|
|
||||||
|
migration_extended_network_allocations = (
|
||||||
|
CONF.server_migration_extend_neutron_network)
|
||||||
|
|
||||||
|
# NOTE: Network allocations are extended to the destination host on
|
||||||
|
# previous (migration_start) step, i.e. port bindings are created on
|
||||||
|
# destination host with existing ports. The network allocations will be
|
||||||
|
# cut over on this (migration_complete) step, i.e. port bindings on
|
||||||
|
# destination host will be activated and bindings on source host will
|
||||||
|
# be deleted.
|
||||||
|
if migration_extended_network_allocations:
|
||||||
|
updated_allocations = (
|
||||||
|
self.driver.network_api.cutover_network_allocations(
|
||||||
|
context, source_share_server))
|
||||||
|
segmentation_id = self.db.share_server_backend_details_get_item(
|
||||||
|
context, dest_share_server['id'], 'segmentation_id')
|
||||||
|
alloc_update = {
|
||||||
|
'segmentation_id': segmentation_id,
|
||||||
|
'share_server_id': dest_share_server['id']
|
||||||
|
}
|
||||||
|
subnet_update = {
|
||||||
|
'segmentation_id': segmentation_id,
|
||||||
|
}
|
||||||
|
|
||||||
migration_reused_network_allocations = (len(
|
migration_reused_network_allocations = (len(
|
||||||
self.db.network_allocations_get_for_share_server(
|
self.db.network_allocations_get_for_share_server(
|
||||||
context, dest_share_server['id'])) == 0)
|
context, dest_share_server['id'])) == 0)
|
||||||
@ -6164,7 +6237,14 @@ class ShareManager(manager.SchedulerDependentManager):
|
|||||||
context, source_share_server, dest_share_server, share_instances,
|
context, source_share_server, dest_share_server, share_instances,
|
||||||
snapshot_instances, new_network_allocations)
|
snapshot_instances, new_network_allocations)
|
||||||
|
|
||||||
if not migration_reused_network_allocations:
|
if migration_extended_network_allocations:
|
||||||
|
for alloc in updated_allocations:
|
||||||
|
self.db.network_allocation_update(context, alloc['id'],
|
||||||
|
alloc_update)
|
||||||
|
for subnet in dest_snss:
|
||||||
|
self.db.share_network_subnet_update(context, subnet['id'],
|
||||||
|
subnet_update)
|
||||||
|
elif not migration_reused_network_allocations:
|
||||||
network_allocations = []
|
network_allocations = []
|
||||||
for net_allocation in new_network_allocations:
|
for net_allocation in new_network_allocations:
|
||||||
network_allocations += net_allocation['network_allocations']
|
network_allocations += net_allocation['network_allocations']
|
||||||
@ -6310,6 +6390,10 @@ class ShareManager(manager.SchedulerDependentManager):
|
|||||||
context, share_server, dest_share_server,
|
context, share_server, dest_share_server,
|
||||||
share_instances, snapshot_instances)
|
share_instances, snapshot_instances)
|
||||||
|
|
||||||
|
if CONF.server_migration_extend_neutron_network:
|
||||||
|
self.driver.network_api.delete_extended_allocations(
|
||||||
|
context, share_server)
|
||||||
|
|
||||||
# NOTE(dviroel): After cancelling the migration we should set the new
|
# NOTE(dviroel): After cancelling the migration we should set the new
|
||||||
# share server to INVALID since it may contain an invalid configuration
|
# share server to INVALID since it may contain an invalid configuration
|
||||||
# to be reused. We also cleanup the source_share_server_id to unblock
|
# to be reused. We also cleanup the source_share_server_id to unblock
|
||||||
|
@ -45,6 +45,15 @@ class FakeNeutronClient(object):
|
|||||||
def list_ports(self, **search_opts):
|
def list_ports(self, **search_opts):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def create_port_binding(self, port_id, body):
|
||||||
|
return body
|
||||||
|
|
||||||
|
def delete_port_binding(self, port_id, host_id):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def activate_port_binding(self, port_id, host_id):
|
||||||
|
pass
|
||||||
|
|
||||||
def list_networks(self):
|
def list_networks(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -536,6 +545,30 @@ class NeutronApiTest(test.TestCase):
|
|||||||
port_id, {'port': fixed_ips})
|
port_id, {'port': fixed_ips})
|
||||||
self.assertTrue(clientv20.Client.called)
|
self.assertTrue(clientv20.Client.called)
|
||||||
|
|
||||||
|
def test_bind_port_to_host(self):
|
||||||
|
port_id = 'test_port'
|
||||||
|
host = 'test_host'
|
||||||
|
vnic_type = 'test_vnic_type'
|
||||||
|
|
||||||
|
port = self.neutron_api.bind_port_to_host(port_id, host, vnic_type)
|
||||||
|
|
||||||
|
self.assertEqual(host, port['host'])
|
||||||
|
self.assertTrue(clientv20.Client.called)
|
||||||
|
|
||||||
|
def test_delete_port_binding(self):
|
||||||
|
port_id = 'test_port'
|
||||||
|
host = 'test_host'
|
||||||
|
self.neutron_api.delete_port_binding(port_id, host)
|
||||||
|
self.assertTrue(clientv20.Client.called)
|
||||||
|
|
||||||
|
def test_activate_port_binding(self):
|
||||||
|
port_id = 'test_port'
|
||||||
|
host = 'test_host'
|
||||||
|
|
||||||
|
self.neutron_api.activate_port_binding(port_id, host)
|
||||||
|
|
||||||
|
self.assertTrue(clientv20.Client.called)
|
||||||
|
|
||||||
def test_router_update_routes(self):
|
def test_router_update_routes(self):
|
||||||
# Set up test data
|
# Set up test data
|
||||||
router_id = 'test_router'
|
router_id = 'test_router'
|
||||||
|
@ -28,6 +28,7 @@ from manila import exception
|
|||||||
from manila.network.neutron import api as neutron_api
|
from manila.network.neutron import api as neutron_api
|
||||||
from manila.network.neutron import constants as neutron_constants
|
from manila.network.neutron import constants as neutron_constants
|
||||||
from manila.network.neutron import neutron_network_plugin as plugin
|
from manila.network.neutron import neutron_network_plugin as plugin
|
||||||
|
from manila.share import utils as share_utils
|
||||||
from manila import test
|
from manila import test
|
||||||
from manila.tests import utils as test_utils
|
from manila.tests import utils as test_utils
|
||||||
|
|
||||||
@ -132,6 +133,11 @@ fake_nw_info = {
|
|||||||
'provider:physical_network': 'net1',
|
'provider:physical_network': 'net1',
|
||||||
'provider:segmentation_id': 3926,
|
'provider:segmentation_id': 3926,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
'provider:network_type': 'vlan',
|
||||||
|
'provider:physical_network': 'net2',
|
||||||
|
'provider:segmentation_id': 1249,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'provider:network_type': 'vxlan',
|
'provider:network_type': 'vxlan',
|
||||||
'provider:physical_network': None,
|
'provider:physical_network': None,
|
||||||
@ -197,6 +203,23 @@ fake_binding_profile = {
|
|||||||
'neutron_switch_info': 'fake switch info'
|
'neutron_switch_info': 'fake switch info'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fake_network_allocation_ext = {
|
||||||
|
'id': 'fake port binding 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,
|
||||||
|
'label': fake_nw_info['segments'][1]['provider:physical_network'],
|
||||||
|
'network_type': fake_share_network_subnet['network_type'],
|
||||||
|
'segmentation_id': (
|
||||||
|
fake_nw_info['segments'][1]['provider:segmentation_id']
|
||||||
|
),
|
||||||
|
'ip_version': fake_share_network_subnet['ip_version'],
|
||||||
|
'cidr': fake_share_network_subnet['cidr'],
|
||||||
|
'gateway': fake_share_network_subnet['gateway'],
|
||||||
|
'mtu': 1509,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@ddt.ddt
|
@ddt.ddt
|
||||||
class NeutronNetworkPluginTest(test.TestCase):
|
class NeutronNetworkPluginTest(test.TestCase):
|
||||||
@ -1145,6 +1168,122 @@ class NeutronBindNetworkPluginTest(test.TestCase):
|
|||||||
fake_neutron_port['id'],
|
fake_neutron_port['id'],
|
||||||
network_allocation_update_data)
|
network_allocation_update_data)
|
||||||
|
|
||||||
|
def test_extend_network_allocations(self):
|
||||||
|
old_network_allocation = copy.deepcopy(fake_network_allocation)
|
||||||
|
fake_network = copy.deepcopy(fake_neutron_network_multi)
|
||||||
|
fake_ss = copy.deepcopy(fake_share_server)
|
||||||
|
fake_ss["share_network_subnet"] = fake_share_network_subnet
|
||||||
|
|
||||||
|
fake_host_id = "fake_host_id"
|
||||||
|
fake_physical_net = "net2"
|
||||||
|
fake_port_id = old_network_allocation["id"]
|
||||||
|
fake_vnic_type = "baremetal"
|
||||||
|
config_data = {
|
||||||
|
'DEFAULT': {
|
||||||
|
"neutron_host_id": fake_host_id,
|
||||||
|
"neutron_vnic_type": fake_vnic_type,
|
||||||
|
'neutron_physical_net_name': fake_physical_net,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.bind_plugin = self._get_neutron_network_plugin_instance(
|
||||||
|
config_data
|
||||||
|
)
|
||||||
|
self.mock_object(
|
||||||
|
self.bind_plugin.neutron_api,
|
||||||
|
"get_network",
|
||||||
|
mock.Mock(return_value=fake_network),
|
||||||
|
)
|
||||||
|
self.mock_object(self.bind_plugin.neutron_api, "bind_port_to_host")
|
||||||
|
self.mock_object(
|
||||||
|
self.bind_plugin.db,
|
||||||
|
"network_allocations_get_for_share_server",
|
||||||
|
mock.Mock(return_value=[old_network_allocation]))
|
||||||
|
|
||||||
|
# calling the extend_network_allocations method
|
||||||
|
self.bind_plugin.extend_network_allocations(self.fake_context, fake_ss)
|
||||||
|
|
||||||
|
# testing the calls, we expect the port to be bound to the current host
|
||||||
|
# and the new network allocation to be created
|
||||||
|
self.bind_plugin.neutron_api.bind_port_to_host.assert_called_once_with(
|
||||||
|
fake_port_id, fake_host_id, fake_vnic_type
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_delete_extended_allocations(self):
|
||||||
|
old_network_allocation = copy.deepcopy(fake_network_allocation)
|
||||||
|
fake_ss = copy.deepcopy(fake_share_server)
|
||||||
|
fake_host_id = "fake_host_id"
|
||||||
|
fake_physical_net = "net2"
|
||||||
|
fake_port_id = old_network_allocation["id"]
|
||||||
|
fake_vnic_type = "baremetal"
|
||||||
|
config_data = {
|
||||||
|
"DEFAULT": {
|
||||||
|
"neutron_host_id": fake_host_id,
|
||||||
|
"neutron_vnic_type": fake_vnic_type,
|
||||||
|
"neutron_physical_net_name": fake_physical_net,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.bind_plugin = self._get_neutron_network_plugin_instance(
|
||||||
|
config_data)
|
||||||
|
self.mock_object(self.bind_plugin.neutron_api, "delete_port_binding")
|
||||||
|
self.mock_object(self.bind_plugin.db, "network_allocation_delete")
|
||||||
|
self.mock_object(self.bind_plugin.db,
|
||||||
|
"network_allocations_get_for_share_server",
|
||||||
|
mock.Mock(return_value=[old_network_allocation]))
|
||||||
|
|
||||||
|
self.bind_plugin.delete_extended_allocations(self.fake_context,
|
||||||
|
fake_ss)
|
||||||
|
neutron_api = self.bind_plugin.neutron_api
|
||||||
|
neutron_api.delete_port_binding.assert_called_once_with(
|
||||||
|
fake_port_id, fake_host_id)
|
||||||
|
|
||||||
|
@ddt.unpack
|
||||||
|
def test_cutover_network_allocation(self):
|
||||||
|
fake_alloc = copy.deepcopy(fake_network_allocation)
|
||||||
|
fake_network = copy.deepcopy(fake_neutron_network_multi)
|
||||||
|
fake_old_ss = copy.deepcopy(fake_share_server)
|
||||||
|
fake_old_ss["share_network_subnet"] = fake_share_network_subnet
|
||||||
|
fake_dest_ss = copy.deepcopy(fake_share_server)
|
||||||
|
fake_dest_ss["host"] = "fake_host2@backend2#pool2"
|
||||||
|
fake_old_host = share_utils.extract_host(fake_old_ss["host"], "host")
|
||||||
|
|
||||||
|
fake_host_id = "fake_host_id"
|
||||||
|
fake_physical_net = "net2"
|
||||||
|
fake_port_id = fake_alloc["id"]
|
||||||
|
fake_vnic_type = "baremetal"
|
||||||
|
config_data = {
|
||||||
|
"DEFAULT": {
|
||||||
|
"neutron_host_id": fake_host_id,
|
||||||
|
"neutron_vnic_type": fake_vnic_type,
|
||||||
|
"neutron_physical_net_name": fake_physical_net,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.bind_plugin = self._get_neutron_network_plugin_instance(
|
||||||
|
config_data)
|
||||||
|
self.mock_object(self.bind_plugin.neutron_api, "get_network",
|
||||||
|
mock.Mock(return_value=fake_network))
|
||||||
|
self.mock_object(self.bind_plugin.neutron_api, "bind_port_to_host")
|
||||||
|
self.mock_object(self.bind_plugin.db, "network_allocation_create")
|
||||||
|
|
||||||
|
self.mock_object(self.bind_plugin.db,
|
||||||
|
"network_allocations_get_for_share_server",
|
||||||
|
mock.Mock(return_value=[fake_alloc]))
|
||||||
|
|
||||||
|
neutron_api = self.bind_plugin.neutron_api
|
||||||
|
db_api = self.bind_plugin.db
|
||||||
|
self.mock_object(neutron_api, "activate_port_binding")
|
||||||
|
self.mock_object(neutron_api, "delete_port_binding")
|
||||||
|
self.mock_object(db_api, "network_allocation_update")
|
||||||
|
self.mock_object(db_api, "network_allocation_delete")
|
||||||
|
self.mock_object(db_api, "share_network_subnet_update")
|
||||||
|
|
||||||
|
self.bind_plugin.cutover_network_allocations(
|
||||||
|
self.fake_context, fake_old_ss)
|
||||||
|
neutron_api.activate_port_binding.assert_called_once_with(
|
||||||
|
fake_port_id, fake_host_id)
|
||||||
|
neutron_api.delete_port_binding.assert_called_once_with(
|
||||||
|
fake_port_id, fake_old_host)
|
||||||
|
|
||||||
@ddt.data({
|
@ddt.data({
|
||||||
'neutron_binding_profiles': None,
|
'neutron_binding_profiles': None,
|
||||||
'binding_profiles': {}
|
'binding_profiles': {}
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Added support for share server migrations between different physical
|
||||||
|
networks. It is achieved by creating an inactive port binding on the target
|
||||||
|
host during share server migration, then cut it over to target host during
|
||||||
|
migration-complete step.
|
Loading…
Reference in New Issue
Block a user