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:
Abishek Subramanian 2015-01-07 14:08:23 -05:00
parent f0e1e3bdb3
commit faee0c0c92
3 changed files with 305 additions and 26 deletions

View File

@ -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',

View File

@ -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

View File

@ -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))