Fix multi-nic issue with nexus plugin
Fixes inability to assign different networks to multi-nics when launching a vm using the cisco n1k plugin. This fixes an issue wherein with the cisco n1k plugin launching an instance with multiple nics via horizon causes the same network to get assigned to all nics. Additionally modifying the existing test_launch_instance_post test to handle the situation wherein two nics are created with the N1K plugin using the N1K policy profile. Hence a new unit test reflects two ports being created. Two more unit tests to test port create exceptions are added to test exception handling as well. Closes bug: #1260436 Change-Id: Ie91c94e534e1cc1dddbb6ac563b0bef102451cf1
This commit is contained in:
parent
f0e1e3bdb3
commit
faee0c0c92
|
@ -1682,7 +1682,8 @@ class InstanceTests(helpers.TestCase):
|
|||
def test_launch_instance_post(self,
|
||||
disk_config=True,
|
||||
config_drive=True,
|
||||
test_with_profile=False):
|
||||
test_with_profile=False,
|
||||
test_with_multi_nics=False):
|
||||
flavor = self.flavors.first()
|
||||
image = self.images.first()
|
||||
keypair = self.keypairs.first()
|
||||
|
@ -1723,15 +1724,24 @@ class InstanceTests(helpers.TestCase):
|
|||
if test_with_profile:
|
||||
policy_profiles = self.policy_profiles.list()
|
||||
policy_profile_id = self.policy_profiles.first().id
|
||||
port = self.ports.first()
|
||||
port_one = self.ports.first()
|
||||
nics = [{"port-id": port_one.id}]
|
||||
api.neutron.profile_list(
|
||||
IsA(http.HttpRequest),
|
||||
'policy').AndReturn(policy_profiles)
|
||||
api.neutron.port_create(
|
||||
IsA(http.HttpRequest),
|
||||
self.networks.first().id,
|
||||
policy_profile_id=policy_profile_id).AndReturn(port)
|
||||
nics = [{"port-id": port.id}]
|
||||
api.neutron.port_create(IsA(http.HttpRequest),
|
||||
self.networks.first().id,
|
||||
policy_profile_id=policy_profile_id) \
|
||||
.AndReturn(port_one)
|
||||
if test_with_multi_nics:
|
||||
port_two = self.ports.get(name="port5")
|
||||
nics = [{"port-id": port_one.id},
|
||||
{"port-id": port_two.id}]
|
||||
# Add a second port to test multiple nics
|
||||
api.neutron.port_create(IsA(http.HttpRequest),
|
||||
self.networks.get(name="net4")['id'],
|
||||
policy_profile_id=policy_profile_id) \
|
||||
.AndReturn(port_two)
|
||||
api.nova.extension_supported('DiskConfig',
|
||||
IsA(http.HttpRequest)) \
|
||||
.AndReturn(disk_config)
|
||||
|
@ -1793,6 +1803,9 @@ class InstanceTests(helpers.TestCase):
|
|||
form_data['config_drive'] = True
|
||||
if test_with_profile:
|
||||
form_data['profile'] = self.policy_profiles.first().id
|
||||
if test_with_multi_nics:
|
||||
form_data['network'] = [self.networks.first().id,
|
||||
self.networks.get(name="net4")['id']]
|
||||
url = reverse('horizon:project:instances:launch')
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
|
@ -1810,6 +1823,158 @@ class InstanceTests(helpers.TestCase):
|
|||
def test_launch_instance_post_with_profile(self):
|
||||
self.test_launch_instance_post(test_with_profile=True)
|
||||
|
||||
@helpers.update_settings(
|
||||
OPENSTACK_NEUTRON_NETWORK={'profile_support': 'cisco'})
|
||||
def test_launch_instance_post_with_profile_and_multi_nics(self):
|
||||
self.test_launch_instance_post(test_with_profile=True,
|
||||
test_with_multi_nics=True)
|
||||
|
||||
def _test_launch_instance_post_with_profile_and_port_error(
|
||||
self,
|
||||
test_with_multi_nics=False,
|
||||
):
|
||||
flavor = self.flavors.first()
|
||||
image = self.images.first()
|
||||
keypair = self.keypairs.first()
|
||||
server = self.servers.first()
|
||||
sec_group = self.security_groups.first()
|
||||
avail_zone = self.availability_zones.first()
|
||||
customization_script = 'user data'
|
||||
quota_usages = self.quota_usages.first()
|
||||
|
||||
api.nova.extension_supported('BlockDeviceMappingV2Boot',
|
||||
IsA(http.HttpRequest)) \
|
||||
.AndReturn(True)
|
||||
api.nova.flavor_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.flavors.list())
|
||||
api.nova.keypair_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.keypairs.list())
|
||||
api.network.security_group_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.security_groups.list())
|
||||
api.nova.availability_zone_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.availability_zones.list())
|
||||
api.glance.image_list_detailed(IsA(http.HttpRequest),
|
||||
filters={'is_public': True,
|
||||
'status': 'active'}) \
|
||||
.AndReturn([self.images.list(), False, False])
|
||||
api.glance.image_list_detailed(
|
||||
IsA(http.HttpRequest),
|
||||
filters={'property-owner_id': self.tenant.id,
|
||||
'status': 'active'}) \
|
||||
.AndReturn([[], False, False])
|
||||
api.neutron.network_list(IsA(http.HttpRequest),
|
||||
tenant_id=self.tenant.id,
|
||||
shared=False) \
|
||||
.AndReturn(self.networks.list()[:1])
|
||||
api.neutron.network_list(IsA(http.HttpRequest),
|
||||
shared=True) \
|
||||
.AndReturn(self.networks.list()[1:])
|
||||
|
||||
policy_profiles = self.policy_profiles.list()
|
||||
policy_profile_id = self.policy_profiles.first().id
|
||||
port_one = self.ports.first()
|
||||
api.neutron.profile_list(
|
||||
IsA(http.HttpRequest),
|
||||
'policy').AndReturn(policy_profiles)
|
||||
if test_with_multi_nics:
|
||||
api.neutron.port_create(IsA(http.HttpRequest),
|
||||
self.networks.first().id,
|
||||
policy_profile_id=policy_profile_id) \
|
||||
.AndReturn(port_one)
|
||||
# Add a second port which has the exception to test multiple nics
|
||||
api.neutron.port_create(IsA(http.HttpRequest),
|
||||
self.networks.get(name="net4")['id'],
|
||||
policy_profile_id=policy_profile_id) \
|
||||
.AndRaise(self.exceptions.neutron)
|
||||
# Delete the first port
|
||||
api.neutron.port_delete(IsA(http.HttpRequest),
|
||||
port_one.id)
|
||||
else:
|
||||
api.neutron.port_create(IsA(http.HttpRequest),
|
||||
self.networks.first().id,
|
||||
policy_profile_id=policy_profile_id) \
|
||||
.AndRaise(self.exceptions.neutron)
|
||||
api.nova.extension_supported('DiskConfig',
|
||||
IsA(http.HttpRequest)) \
|
||||
.AndReturn(True)
|
||||
api.nova.extension_supported('ConfigDrive',
|
||||
IsA(http.HttpRequest)).AndReturn(True)
|
||||
cinder.volume_list(IsA(http.HttpRequest),
|
||||
search_opts=VOLUME_SEARCH_OPTS) \
|
||||
.AndReturn([])
|
||||
cinder.volume_snapshot_list(IsA(http.HttpRequest),
|
||||
search_opts=SNAPSHOT_SEARCH_OPTS) \
|
||||
.AndReturn([])
|
||||
quotas.tenant_quota_usages(IsA(http.HttpRequest)) \
|
||||
.AndReturn(quota_usages)
|
||||
api.nova.flavor_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.flavors.list())
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'flavor': flavor.id,
|
||||
'source_type': 'image_id',
|
||||
'image_id': image.id,
|
||||
'keypair': keypair.name,
|
||||
'name': server.name,
|
||||
'script_source': 'raw',
|
||||
'script_data': customization_script,
|
||||
'project_id': self.tenants.first().id,
|
||||
'user_id': self.user.id,
|
||||
'groups': sec_group.name,
|
||||
'availability_zone': avail_zone.zoneName,
|
||||
'volume_type': '',
|
||||
'network': self.networks.first().id,
|
||||
'count': 1,
|
||||
'disk_config': 'AUTO',
|
||||
'config_drive': True,
|
||||
'profile': self.policy_profiles.first().id}
|
||||
if test_with_multi_nics:
|
||||
form_data['network'] = [self.networks.first().id,
|
||||
self.networks.get(name="net4")['id']]
|
||||
url = reverse('horizon:project:instances:launch')
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@helpers.update_settings(
|
||||
OPENSTACK_NEUTRON_NETWORK={'profile_support': 'cisco'})
|
||||
@helpers.create_stubs({api.glance: ('image_list_detailed',),
|
||||
api.neutron: ('network_list',
|
||||
'profile_list',
|
||||
'port_create',
|
||||
'port_delete',),
|
||||
api.nova: ('extension_supported',
|
||||
'flavor_list',
|
||||
'keypair_list',
|
||||
'availability_zone_list',),
|
||||
api.network: ('security_group_list',),
|
||||
cinder: ('volume_list',
|
||||
'volume_snapshot_list',),
|
||||
quotas: ('tenant_quota_usages',)})
|
||||
def test_launch_instance_post_with_profile_and_port_error(self):
|
||||
self._test_launch_instance_post_with_profile_and_port_error()
|
||||
|
||||
@helpers.update_settings(
|
||||
OPENSTACK_NEUTRON_NETWORK={'profile_support': 'cisco'})
|
||||
@helpers.create_stubs({api.glance: ('image_list_detailed',),
|
||||
api.neutron: ('network_list',
|
||||
'profile_list',
|
||||
'port_create',
|
||||
'port_delete',),
|
||||
api.nova: ('extension_supported',
|
||||
'flavor_list',
|
||||
'keypair_list',
|
||||
'availability_zone_list',),
|
||||
api.network: ('security_group_list',),
|
||||
cinder: ('volume_list',
|
||||
'volume_snapshot_list',),
|
||||
quotas: ('tenant_quota_usages',)})
|
||||
def test_lnch_inst_post_w_profile_and_multi_nics_w_port_error(self):
|
||||
self._test_launch_instance_post_with_profile_and_port_error(
|
||||
test_with_multi_nics=True)
|
||||
|
||||
@helpers.create_stubs({api.glance: ('image_list_detailed',),
|
||||
api.neutron: ('network_list',
|
||||
'profile_list',
|
||||
|
|
|
@ -898,26 +898,10 @@ class LaunchInstance(workflows.Workflow):
|
|||
|
||||
avail_zone = context.get('availability_zone', None)
|
||||
|
||||
# Create port with Network Name and Port Profile
|
||||
# for the use with the plugin supporting port profiles.
|
||||
# neutron port-create <Network name> --n1kv:profile <Port Profile ID>
|
||||
# for net_id in context['network_id']:
|
||||
# HACK for now use first network.
|
||||
if api.neutron.is_port_profiles_supported():
|
||||
net_id = context['network_id'][0]
|
||||
LOG.debug("Horizon->Create Port with %(netid)s %(profile_id)s",
|
||||
{'netid': net_id, 'profile_id': context['profile_id']})
|
||||
port = None
|
||||
try:
|
||||
port = api.neutron.port_create(
|
||||
request, net_id, policy_profile_id=context['profile_id'])
|
||||
except Exception:
|
||||
msg = (_('Port not created for profile-id (%s).') %
|
||||
context['profile_id'])
|
||||
exceptions.handle(request, msg)
|
||||
|
||||
if port and port.id:
|
||||
nics = [{"port-id": port.id}]
|
||||
nics = self.set_network_port_profiles(request,
|
||||
context['network_id'],
|
||||
context['profile_id'])
|
||||
|
||||
try:
|
||||
api.nova.server_create(request,
|
||||
|
@ -939,3 +923,40 @@ class LaunchInstance(workflows.Workflow):
|
|||
except Exception:
|
||||
exceptions.handle(request)
|
||||
return False
|
||||
|
||||
def set_network_port_profiles(self, request, net_ids, profile_id):
|
||||
# Create port with Network ID and Port Profile
|
||||
# for the use with the plugin supporting port profiles.
|
||||
nics = []
|
||||
for net_id in net_ids:
|
||||
try:
|
||||
port = api.neutron.port_create(
|
||||
request,
|
||||
net_id,
|
||||
policy_profile_id=profile_id,
|
||||
)
|
||||
except Exception as e:
|
||||
msg = (_('Unable to create port for profile '
|
||||
'"%(profile_id)s": %(reason)s'),
|
||||
{'profile_id': profile_id,
|
||||
'reason': e})
|
||||
for nic in nics:
|
||||
try:
|
||||
port_id = nic['port-id']
|
||||
api.neutron.port_delete(request, port_id)
|
||||
except Exception:
|
||||
msg = (msg +
|
||||
_(' Also failed to delete port %s') % port_id)
|
||||
redirect = self.success_url
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
if port:
|
||||
nics.append({"port-id": port.id})
|
||||
LOG.debug("Created Port %(portid)s with "
|
||||
"network %(netid)s "
|
||||
"policy profile %(profile_id)s",
|
||||
{'portid': port.id,
|
||||
'netid': net_id,
|
||||
'profile_id': profile_id})
|
||||
|
||||
return nics
|
||||
|
|
|
@ -1052,3 +1052,96 @@ def data(TEST):
|
|||
TEST.api_network_profile_binding.add(network_profile_binding_dict)
|
||||
TEST.network_profile_binding.add(neutron.Profile(
|
||||
network_profile_binding_dict))
|
||||
|
||||
# Adding a new network and new network and policy profile
|
||||
# similar to the first to test launching an instance with multiple
|
||||
# nics and multiple profiles.
|
||||
|
||||
# 4th network to use for testing instances with multiple-nics & profiles
|
||||
network_dict = {'admin_state_up': True,
|
||||
'id': '7aa23d91-ffff-abab-dcdc-3411ae767e8a',
|
||||
'name': 'net4',
|
||||
'status': 'ACTIVE',
|
||||
'subnets': ['31be4a21-aadd-73da-6422-821ff249a4bb'],
|
||||
'tenant_id': '1',
|
||||
'router:external': False,
|
||||
'shared': False}
|
||||
subnet_dict = {'allocation_pools': [{'end': '11.10.0.254',
|
||||
'start': '11.10.0.2'}],
|
||||
'dns_nameservers': [],
|
||||
'host_routes': [],
|
||||
'cidr': '11.10.0.0/24',
|
||||
'enable_dhcp': True,
|
||||
'gateway_ip': '11.10.0.1',
|
||||
'id': network_dict['subnets'][0],
|
||||
'ip_version': 4,
|
||||
'name': 'mysubnet4',
|
||||
'network_id': network_dict['id'],
|
||||
'tenant_id': network_dict['tenant_id']}
|
||||
|
||||
TEST.api_networks.add(network_dict)
|
||||
TEST.api_subnets.add(subnet_dict)
|
||||
|
||||
network = copy.deepcopy(network_dict)
|
||||
subnet = neutron.Subnet(subnet_dict)
|
||||
network['subnets'] = [subnet]
|
||||
TEST.networks.add(neutron.Network(network))
|
||||
TEST.subnets.add(subnet)
|
||||
|
||||
# 5th network profile for network when using the cisco n1k plugin
|
||||
# Network Profile applied on 4th network
|
||||
net_profile_dict = {'name': 'net_profile_test5',
|
||||
'segment_type': 'vlan',
|
||||
'physical_network': 'phys5',
|
||||
'segment_range': '400-450',
|
||||
'id':
|
||||
'00000000-5555-5555-5555-000000000000',
|
||||
'project': TEST.networks.get(name="net4")['tenant_id']}
|
||||
|
||||
TEST.api_net_profiles.add(net_profile_dict)
|
||||
TEST.net_profiles.add(neutron.Profile(net_profile_dict))
|
||||
|
||||
# 2nd policy profile for port when using the cisco n1k plugin
|
||||
policy_profile_dict = {'name': 'policy_profile_test2',
|
||||
'id':
|
||||
'11111111-9999-9999-9999-111111111111'}
|
||||
|
||||
TEST.api_policy_profiles.add(policy_profile_dict)
|
||||
TEST.policy_profiles.add(neutron.Profile(policy_profile_dict))
|
||||
|
||||
# network profile binding
|
||||
network_profile_binding_dict = {'profile_id':
|
||||
'00000000-5555-5555-5555-000000000000',
|
||||
'tenant_id':
|
||||
TEST.networks.get(name="net4")['tenant_id']
|
||||
}
|
||||
|
||||
TEST.api_network_profile_binding.add(network_profile_binding_dict)
|
||||
TEST.network_profile_binding.add(neutron.Profile(
|
||||
network_profile_binding_dict))
|
||||
|
||||
# policy profile binding
|
||||
policy_profile_binding_dict = {'profile_id':
|
||||
'11111111-9999-9999-9999-111111111111',
|
||||
'tenant_id':
|
||||
TEST.networks.get(name="net4")['tenant_id']}
|
||||
|
||||
TEST.api_policy_profile_binding.add(policy_profile_binding_dict)
|
||||
TEST.policy_profile_binding.add(neutron.Profile(
|
||||
policy_profile_binding_dict))
|
||||
|
||||
# ports on 4th network
|
||||
port_dict = {'admin_state_up': True,
|
||||
'device_id': '9872faaa-b2b2-eeee-9911-21332eedaa77',
|
||||
'device_owner': 'network:dhcp',
|
||||
'fixed_ips': [{'ip_address': '11.10.0.3',
|
||||
'subnet_id':
|
||||
TEST.subnets.get(name="mysubnet4")['id']}],
|
||||
'id': 'a21dcd22-6733-cccc-aa32-22adafaf16a2',
|
||||
'mac_address': '78:22:ff:1a:ba:23',
|
||||
'name': 'port5',
|
||||
'network_id': TEST.networks.get(name="net4")['id'],
|
||||
'status': 'ACTIVE',
|
||||
'tenant_id': TEST.networks.get(name="net4")['tenant_id']}
|
||||
TEST.api_ports.add(port_dict)
|
||||
TEST.ports.add(neutron.Port(port_dict))
|
||||
|
|
Loading…
Reference in New Issue