Add Cisco N1K plugin support in Horizon

This adds Horizon capability to configure and
use network profiles and policy profiles
on the Cisco Nexus 1000V to be used with
networks and instances as is done via neutron
CLI.

To turn on the new dashboard the local_settings
file needs to turn the profile_settings NEUTRON
config variable to cisco.

TODO items include creating a better palate
of unit tests that will run not only when
the cisco n1k subplugin is being used but
in default setting as well.
Need to remove the conditional statements
in the unit tests and make it inclusive.
Additionally need to get a better solution
for how the dashboard/panel registration will
work. Should it be a boolean or check for vendor
specific names.
Also some cleanup and refactoring of commonly
used code everywhere.

Implements: blueprint horizon-cisco-n1k

Change-Id: If6e797ac28c8486cfa92e02742c409d193d9ec84
This commit is contained in:
Abishek Subramanian 2013-09-03 15:27:03 -04:00
parent 9ebb52b4e9
commit 38ba3df8b8
29 changed files with 1264 additions and 40 deletions

View File

@ -93,6 +93,15 @@ class Port(NeutronAPIDictWrapper):
super(Port, self).__init__(apiresource)
class Profile(NeutronAPIDictWrapper):
"""Wrapper for neutron profiles."""
_attrs = ['profile_id', 'name', 'segment_type',
'segment_range', 'multicast_ip_index', 'multicast_ip_range']
def __init__(self, apiresource):
super(Profile, self).__init__(apiresource)
class Router(NeutronAPIDictWrapper):
"""Wrapper for neutron routers"""
@ -461,6 +470,9 @@ def network_create(request, **kwargs):
:returns: Subnet object
"""
LOG.debug("network_create(): kwargs = %s" % kwargs)
# In the case network profiles are being used, profile id is needed.
if 'net_profile_id' in kwargs:
kwargs['n1kv:profile_id'] = kwargs.pop('net_profile_id')
body = {'network': kwargs}
network = neutronclient(request).create_network(body=body).get('network')
return Network(network)
@ -551,6 +563,9 @@ def port_create(request, network_id, **kwargs):
:returns: Port object
"""
LOG.debug("port_create(): netid=%s, kwargs=%s" % (network_id, kwargs))
# In the case policy profiles are being used, profile id is needed.
if 'policy_profile_id' in kwargs:
kwargs['n1kv:profile_id'] = kwargs.pop('policy_profile_id')
body = {'port': {'network_id': network_id}}
body['port'].update(kwargs)
port = neutronclient(request).create_port(body=body).get('port')
@ -569,6 +584,65 @@ def port_modify(request, port_id, **kwargs):
return Port(port)
def profile_list(request, type_p, **params):
LOG.debug(_("profile_list(): "
"profile_type=%(profile_type)s, params=%(params)s"),
{'profile_type': type_p, 'params': params})
if type_p == 'network':
profiles = neutronclient(request).list_network_profiles(
**params).get('network_profiles')
elif type_p == 'policy':
profiles = neutronclient(request).list_policy_profiles(
**params).get('policy_profiles')
return [Profile(n) for n in profiles]
def profile_get(request, profile_id, **params):
LOG.debug(_("profile_get(): "
"profileid=%(profileid)s, params=%(params)s"),
{'profileid': profile_id, 'params': params})
profile = neutronclient(request).show_network_profile(
profile_id, **params).get('network_profile')
return Profile(profile)
def profile_create(request, **kwargs):
LOG.debug(_("profile_create(): kwargs=%s") % kwargs)
body = {'network_profile': {}}
body['network_profile'].update(kwargs)
profile = neutronclient(request).create_network_profile(
body=body).get('network_profile')
return Profile(profile)
def profile_delete(request, profile_id):
LOG.debug(_("profile_delete(): profile_id=%s") % profile_id)
neutronclient(request).delete_network_profile(profile_id)
def profile_modify(request, profile_id, **kwargs):
LOG.debug(_("profile_modify(): "
"profileid=%(profileid)s, kwargs=%(kwargs)s"),
{'profileid': profile_id, 'kwargs': kwargs})
body = {'network_profile': kwargs}
profile = neutronclient(request).update_network_profile(
profile_id, body=body).get('network_profile')
return Profile(profile)
def profile_bindings_list(request, type_p, **params):
LOG.debug(_("profile_bindings_list(): "
"profile_type=%(profile_type)s params=%(params)s"),
{'profile_type': type_p, 'params': params})
if type_p == 'network':
bindings = neutronclient(request).list_network_profile_bindings(
**params).get('network_profile_bindings')
elif type_p == 'policy':
bindings = neutronclient(request).list_policy_profile_bindings(
**params).get('policy_profile_bindings')
return [Profile(n) for n in bindings]
def router_create(request, **kwargs):
LOG.debug("router_create():, kwargs=%s" % kwargs)
body = {'router': {}}
@ -659,3 +733,21 @@ def is_quotas_extension_supported(request):
return True
else:
return False
# Using this mechanism till a better plugin/sub-plugin detection
# mechanism is available.
# Using local_settings to detect if the "router" dashboard
# should be turned on or not. When using specific plugins the
# profile_support can be turned on if needed.
# Since this is a temporary mechanism used to detect profile_support
# @memorize is not being used. This is mainly used in the run_tests
# environment to detect when to use profile_support neutron APIs.
# TODO(absubram): Change this config variable check with
# subplugin/plugin detection API when it becomes available.
def is_port_profiles_supported():
network_config = getattr(settings, 'OPENSTACK_NEUTRON_NETWORK', {})
# Can be used to check for vendor specific plugin
profile_support = network_config.get('profile_support', None)
if str(profile_support).lower() == 'cisco':
return True

View File

@ -17,6 +17,7 @@
import logging
from django.core.urlresolvers import reverse # noqa
from django.utils import datastructures # noqa
from django.utils.translation import ugettext_lazy as _ # noqa
from horizon import exceptions
@ -34,6 +35,8 @@ class CreateNetwork(forms.SelfHandlingForm):
label=_("Name"),
required=False)
tenant_id = forms.ChoiceField(label=_("Project"))
if api.neutron.is_port_profiles_supported():
net_profile_id = forms.ChoiceField(label=_("Network Profile"))
admin_state = forms.BooleanField(label=_("Admin State"),
initial=True, required=False)
shared = forms.BooleanField(label=_("Shared"),
@ -54,6 +57,25 @@ class CreateNetwork(forms.SelfHandlingForm):
tenant_choices.append((tenant.id, tenant.name))
self.fields['tenant_id'].choices = tenant_choices
if api.neutron.is_port_profiles_supported():
self.fields['net_profile_id'].choices = (
self.get_network_profile_choices(request))
def get_network_profile_choices(self, request):
profile_choices = [('', _("Select a profile"))]
for profile in self._get_profiles(request, 'network'):
profile_choices.append((profile.id, profile.name))
return profile_choices
def _get_profiles(self, request, type_p):
profiles = []
try:
profiles = api.neutron.profile_list(request, type_p)
except Exception:
msg = _('Network Profiles could not be retrieved.')
exceptions.handle(request, msg)
return profiles
def handle(self, request, data):
try:
params = {'name': data['name'],
@ -61,6 +83,8 @@ class CreateNetwork(forms.SelfHandlingForm):
'admin_state_up': data['admin_state'],
'shared': data['shared'],
'router:external': data['external']}
if api.neutron.is_port_profiles_supported():
params['net_profile_id'] = data['net_profile_id']
network = api.neutron.network_create(request, **params)
msg = _('Network %s was successfully created.') % data['name']
LOG.debug(msg)

View File

@ -149,11 +149,19 @@ class NetworkTests(test.BaseAdminViewTests):
self.assertItemsEqual(subnets, [self.subnets.first()])
self.assertEqual(len(ports), 0)
@test.create_stubs({api.keystone: ('tenant_list',)})
@test.create_stubs({api.neutron: ('profile_list',),
api.keystone: ('tenant_list',)})
def test_network_create_get(self):
tenants = self.tenants.list()
api.keystone.tenant_list(IsA(http.HttpRequest))\
.AndReturn([tenants, False])
api.keystone.tenant_list(IsA(
http.HttpRequest)).AndReturn([tenants, False])
# TODO(absubram): Remove if clause and create separate
# test stubs for when profile_support is being used.
# Additionally ensure those are always run even in default setting
if api.neutron.is_port_profiles_supported():
net_profiles = self.net_profiles.list()
api.neutron.profile_list(IsA(http.HttpRequest),
'network').AndReturn(net_profiles)
self.mox.ReplayAll()
url = reverse('horizon:admin:networks:create')
@ -161,7 +169,8 @@ class NetworkTests(test.BaseAdminViewTests):
self.assertTemplateUsed(res, 'admin/networks/create.html')
@test.create_stubs({api.neutron: ('network_create',),
@test.create_stubs({api.neutron: ('network_create',
'profile_list',),
api.keystone: ('tenant_list',)})
def test_network_create_post(self):
tenants = self.tenants.list()
@ -174,6 +183,15 @@ class NetworkTests(test.BaseAdminViewTests):
'admin_state_up': network.admin_state_up,
'router:external': True,
'shared': True}
# TODO(absubram): Remove if clause and create separate
# test stubs for when profile_support is being used.
# Additionally ensure those are always run even in default setting
if api.neutron.is_port_profiles_supported():
net_profiles = self.net_profiles.list()
net_profile_id = self.net_profiles.first().id
api.neutron.profile_list(IsA(http.HttpRequest),
'network').AndReturn(net_profiles)
params['net_profile_id'] = net_profile_id
api.neutron.network_create(IsA(http.HttpRequest), **params)\
.AndReturn(network)
self.mox.ReplayAll()
@ -183,13 +201,16 @@ class NetworkTests(test.BaseAdminViewTests):
'admin_state': network.admin_state_up,
'external': True,
'shared': True}
if api.neutron.is_port_profiles_supported():
form_data['net_profile_id'] = net_profile_id
url = reverse('horizon:admin:networks:create')
res = self.client.post(url, form_data)
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api.neutron: ('network_create',),
@test.create_stubs({api.neutron: ('network_create',
'profile_list',),
api.keystone: ('tenant_list',)})
def test_network_create_post_network_exception(self):
tenants = self.tenants.list()
@ -202,6 +223,15 @@ class NetworkTests(test.BaseAdminViewTests):
'admin_state_up': network.admin_state_up,
'router:external': True,
'shared': False}
# TODO(absubram): Remove if clause and create separate
# test stubs for when profile_support is being used.
# Additionally ensure those are always run even in default setting
if api.neutron.is_port_profiles_supported():
net_profiles = self.net_profiles.list()
net_profile_id = self.net_profiles.first().id
api.neutron.profile_list(IsA(http.HttpRequest),
'network').AndReturn(net_profiles)
params['net_profile_id'] = net_profile_id
api.neutron.network_create(IsA(http.HttpRequest), **params)\
.AndRaise(self.exceptions.neutron)
self.mox.ReplayAll()
@ -211,6 +241,8 @@ class NetworkTests(test.BaseAdminViewTests):
'admin_state': network.admin_state_up,
'external': True,
'shared': False}
if api.neutron.is_port_profiles_supported():
form_data['net_profile_id'] = net_profile_id
url = reverse('horizon:admin:networks:create')
res = self.client.post(url, form_data)

View File

@ -813,7 +813,8 @@ class InstanceTests(test.TestCase):
api.network: ('security_group_list',),
cinder: ('volume_snapshot_list',
'volume_list',),
api.neutron: ('network_list',),
api.neutron: ('network_list',
'profile_list',),
api.glance: ('image_list_detailed',)})
def test_launch_instance_get(self):
image = self.images.first()
@ -837,6 +838,13 @@ class InstanceTests(test.TestCase):
api.neutron.network_list(IsA(http.HttpRequest),
shared=True) \
.AndReturn(self.networks.list()[1:])
# TODO(absubram): Remove if clause and create separate
# test stubs for when profile_support is being used.
# Additionally ensure those are always run even in default setting
if api.neutron.is_port_profiles_supported():
policy_profiles = self.policy_profiles.list()
api.neutron.profile_list(IsA(http.HttpRequest),
'policy').AndReturn(policy_profiles)
api.nova.tenant_absolute_limits(IsA(http.HttpRequest))\
.AndReturn(self.limits['absolute'])
api.nova.flavor_list(IsA(http.HttpRequest)) \
@ -878,7 +886,9 @@ class InstanceTests(test.TestCase):
'<PostCreationStep: customizeaction>'])
@test.create_stubs({api.glance: ('image_list_detailed',),
api.neutron: ('network_list',),
api.neutron: ('network_list',
'profile_list',
'port_create',),
api.nova: ('flavor_list',
'keypair_list',
'availability_zone_list',
@ -921,6 +931,20 @@ class InstanceTests(test.TestCase):
api.neutron.network_list(IsA(http.HttpRequest),
shared=True) \
.AndReturn(self.networks.list()[1:])
# TODO(absubram): Remove if clause and create separate
# test stubs for when profile_support is being used.
# Additionally ensure those are always run even in default setting
if api.neutron.is_port_profiles_supported():
policy_profiles = self.policy_profiles.list()
policy_profile_id = self.policy_profiles.first().id
port = self.ports.first()
api.neutron.profile_list(
IsA(http.HttpRequest),
'policy').AndReturn(policy_profiles)
api.neutron.port_create(
IsA(http.HttpRequest),
network_id=self.networks.first().id,
policy_profile_id=policy_profile_id).AndReturn(port)
cinder.volume_list(IsA(http.HttpRequest)) \
.AndReturn([])
cinder.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])
@ -970,7 +994,8 @@ class InstanceTests(test.TestCase):
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api.glance: ('image_list_detailed',),
api.neutron: ('network_list',),
api.neutron: ('network_list',
'profile_list',),
api.nova: ('flavor_list',
'keypair_list',
'availability_zone_list',
@ -1016,6 +1041,21 @@ class InstanceTests(test.TestCase):
api.neutron.network_list(IsA(http.HttpRequest),
shared=True) \
.AndReturn(self.networks.list()[1:])
# TODO(absubram): Remove if clause and create separate
# test stubs for when profile_support is being used.
# Additionally ensure those are always run even in default setting
if api.neutron.is_port_profiles_supported():
policy_profiles = self.policy_profiles.list()
policy_profile_id = self.policy_profiles.first().id
port = self.ports.first()
api.neutron.profile_list(
IsA(http.HttpRequest),
'policy').AndReturn(policy_profiles)
api.neutron.port_create(
IsA(http.HttpRequest),
network_id=self.networks.first().id,
policy_profile_id=policy_profile_id).AndReturn(port)
nics = [{"port-id": port.id}]
cinder.volume_list(IsA(http.HttpRequest)) \
.AndReturn(self.volumes.list())
cinder.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])
@ -1067,7 +1107,9 @@ class InstanceTests(test.TestCase):
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api.glance: ('image_list_detailed',),
api.neutron: ('network_list',),
api.neutron: ('network_list',
'profile_list',
'port_create'),
api.nova: ('server_create',
'flavor_list',
'keypair_list',
@ -1115,6 +1157,21 @@ class InstanceTests(test.TestCase):
api.neutron.network_list(IsA(http.HttpRequest),
shared=True) \
.AndReturn(self.networks.list()[1:])
# TODO(absubram): Remove if clause and create separate
# test stubs for when profile_support is being used.
# Additionally ensure those are always run even in default setting
if api.neutron.is_port_profiles_supported():
policy_profiles = self.policy_profiles.list()
policy_profile_id = self.policy_profiles.first().id
port = self.ports.first()
api.neutron.profile_list(
IsA(http.HttpRequest),
'policy').AndReturn(policy_profiles)
api.neutron.port_create(
IsA(http.HttpRequest),
network_id=self.networks.first().id,
policy_profile_id=policy_profile_id).AndReturn(port)
nics = [{"port-id": port.id}]
cinder.volume_list(IsA(http.HttpRequest)) \
.AndReturn(self.volumes.list())
cinder.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])
@ -1168,7 +1225,8 @@ class InstanceTests(test.TestCase):
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api.glance: ('image_list_detailed',),
api.neutron: ('network_list',),
api.neutron: ('network_list',
'profile_list',),
api.nova: ('flavor_list',
'keypair_list',
'availability_zone_list',
@ -1205,6 +1263,13 @@ class InstanceTests(test.TestCase):
api.neutron.network_list(IsA(http.HttpRequest),
shared=True) \
.AndReturn(self.networks.list()[1:])
# TODO(absubram): Remove if clause and create separate
# test stubs for when profile_support is being used.
# Additionally ensure those are always run even in default setting
if api.neutron.is_port_profiles_supported():
policy_profiles = self.policy_profiles.list()
api.neutron.profile_list(IsA(http.HttpRequest),
'policy').AndReturn(policy_profiles)
api.nova.flavor_list(IsA(http.HttpRequest)) \
.AndReturn(self.flavors.list())
api.nova.keypair_list(IsA(http.HttpRequest)) \
@ -1252,7 +1317,8 @@ class InstanceTests(test.TestCase):
self.assertTemplateUsed(res, views.WorkflowView.template_name)
@test.create_stubs({api.glance: ('image_list_detailed',),
api.neutron: ('network_list',),
api.neutron: ('network_list',
'profile_list',),
cinder: ('volume_list',
'volume_snapshot_list',),
api.network: ('security_group_list',),
@ -1280,6 +1346,13 @@ class InstanceTests(test.TestCase):
api.neutron.network_list(IsA(http.HttpRequest),
shared=True) \
.AndReturn(self.networks.list()[1:])
# TODO(absubram): Remove if clause and create separate
# test stubs for when profile_support is being used.
# Additionally ensure those are always run even in default setting
if api.neutron.is_port_profiles_supported():
policy_profiles = self.policy_profiles.list()
api.neutron.profile_list(IsA(http.HttpRequest),
'policy').AndReturn(policy_profiles)
api.nova.tenant_absolute_limits(IsA(http.HttpRequest)) \
.AndReturn(self.limits['absolute'])
api.nova.flavor_list(IsA(http.HttpRequest)) \
@ -1309,7 +1382,9 @@ class InstanceTests(test.TestCase):
self.assertTemplateUsed(res, views.WorkflowView.template_name)
@test.create_stubs({api.glance: ('image_list_detailed',),
api.neutron: ('network_list',),
api.neutron: ('network_list',
'profile_list',
'port_create',),
api.nova: ('flavor_list',
'keypair_list',
'availability_zone_list',
@ -1352,6 +1427,21 @@ class InstanceTests(test.TestCase):
api.neutron.network_list(IsA(http.HttpRequest),
shared=True) \
.AndReturn(self.networks.list()[1:])
# TODO(absubram): Remove if clause and create separate
# test stubs for when profile_support is being used.
# Additionally ensure those are always run even in default setting
if api.neutron.is_port_profiles_supported():
policy_profiles = self.policy_profiles.list()
policy_profile_id = self.policy_profiles.first().id
port = self.ports.first()
api.neutron.profile_list(
IsA(http.HttpRequest),
'policy').AndReturn(policy_profiles)
api.neutron.port_create(
IsA(http.HttpRequest),
network_id=self.networks.first().id,
policy_profile_id=policy_profile_id).AndReturn(port)
nics = [{"port-id": port.id}]
cinder.volume_list(IgnoreArg()).AndReturn(self.volumes.list())
api.glance.image_list_detailed(IsA(http.HttpRequest),
filters={'is_public': True,
@ -1403,7 +1493,8 @@ class InstanceTests(test.TestCase):
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api.glance: ('image_list_detailed',),
api.neutron: ('network_list',),
api.neutron: ('network_list',
'profile_list',),
api.nova: ('flavor_list',
'keypair_list',
'tenant_absolute_limits',
@ -1448,6 +1539,13 @@ class InstanceTests(test.TestCase):
api.neutron.network_list(IsA(http.HttpRequest),
shared=True) \
.AndReturn(self.networks.list()[1:])
# TODO(absubram): Remove if clause and create separate
# test stubs for when profile_support is being used.
# Additionally ensure those are always run even in default setting
if api.neutron.is_port_profiles_supported():
policy_profiles = self.policy_profiles.list()
api.neutron.profile_list(IsA(http.HttpRequest),
'policy').AndReturn(policy_profiles)
cinder.volume_list(IsA(http.HttpRequest)) \
.AndReturn(self.volumes.list())
cinder.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])
@ -1551,7 +1649,8 @@ class InstanceTests(test.TestCase):
api.network: ('security_group_list',),
cinder: ('volume_snapshot_list',
'volume_list',),
api.neutron: ('network_list',),
api.neutron: ('network_list',
'profile_list'),
api.glance: ('image_list_detailed',)})
def test_select_default_keypair_if_only_one(self):
keypair = self.keypairs.first()
@ -1575,6 +1674,13 @@ class InstanceTests(test.TestCase):
api.neutron.network_list(IsA(http.HttpRequest),
shared=True) \
.AndReturn(self.networks.list()[1:])
# TODO(absubram): Remove if clause and create separate
# test stubs for when profile_support is being used.
# Additionally ensure those are always run even in default setting
if api.neutron.is_port_profiles_supported():
policy_profiles = self.policy_profiles.list()
api.neutron.profile_list(IsA(http.HttpRequest),
'policy').AndReturn(policy_profiles)
api.nova.tenant_absolute_limits(IsA(http.HttpRequest)) \
.AndReturn(self.limits['absolute'])
api.nova.flavor_list(IsA(http.HttpRequest)) \

View File

@ -486,6 +486,11 @@ class SetNetworkAction(workflows.Action):
" be specified.")},
help_text=_("Launch instance with"
" these networks"))
if api.neutron.is_port_profiles_supported():
profile = forms.ChoiceField(label=_("Policy Profiles"),
required=False,
help_text=_("Launch instance with "
"this policy profile"))
class Meta:
name = _("Networking")
@ -505,11 +510,26 @@ class SetNetworkAction(workflows.Action):
_('Unable to retrieve networks.'))
return network_list
def populate_profile_choices(self, request, context):
try:
profiles = api.neutron.profile_list(request, 'policy')
profile_list = [(profile.id, profile.name) for profile in profiles]
except Exception:
profile_list = []
exceptions.handle(request, _("Unable to retrieve profiles."))
return profile_list
class SetNetwork(workflows.Step):
action_class = SetNetworkAction
template_name = "project/instances/_update_networks.html"
contributes = ("network_id",)
# Disabling the template drag/drop only in the case port profiles
# are used till the issue with the drag/drop affecting the
# profile_id detection is fixed.
if api.neutron.is_port_profiles_supported():
contributes = ("network_id", "profile_id",)
else:
template_name = "project/instances/_update_networks.html"
contributes = ("network_id",)
def contribute(self, data, context):
if data:
@ -519,6 +539,9 @@ class SetNetwork(workflows.Step):
networks = [n for n in networks if n != '']
if networks:
context['network_id'] = networks
if api.neutron.is_port_profiles_supported():
context['profile_id'] = data.get('profile', None)
return context
@ -583,6 +606,26 @@ 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']})
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}]
try:
api.nova.server_create(request,
context['name'],

View File

@ -219,8 +219,15 @@ class NetworkTests(test.TestCase):
self.assertItemsEqual(subnets, [self.subnets.first()])
self.assertEqual(len(ports), 0)
@test.create_stubs({api.neutron: ('profile_list',)})
def test_network_create_get(self):
# no api methods are called.
# TODO(absubram): Remove if clause and create separate
# test stubs for when profile_support is being used.
# Additionally ensure those are always run even in default setting
if api.neutron.is_port_profiles_supported():
net_profiles = self.net_profiles.list()
api.neutron.profile_list(IsA(http.HttpRequest),
'network').AndReturn(net_profiles)
self.mox.ReplayAll()
url = reverse('horizon:project:networks:create')
@ -234,18 +241,31 @@ class NetworkTests(test.TestCase):
'<CreateSubnetDetail: createsubnetdetailaction>']
self.assertQuerysetEqual(workflow.steps, expected_objs)
@test.create_stubs({api.neutron: ('network_create',)})
@test.create_stubs({api.neutron: ('network_create',
'profile_list',)})
def test_network_create_post(self):
network = self.networks.first()
api.neutron.network_create(IsA(http.HttpRequest), name=network.name,
admin_state_up=network.admin_state_up)\
.AndReturn(network)
params = {'name': network.name,
'admin_state_up': network.admin_state_up}
# TODO(absubram): Remove if clause and create separate
# test stubs for when profile_support is being used.
# Additionally ensure those are always run even in default setting
if api.neutron.is_port_profiles_supported():
net_profiles = self.net_profiles.list()
net_profile_id = self.net_profiles.first().id
api.neutron.profile_list(IsA(http.HttpRequest),
'network').AndReturn(net_profiles)
params['net_profile_id'] = net_profile_id
api.neutron.network_create(IsA(http.HttpRequest),
**params).AndReturn(network)
self.mox.ReplayAll()
form_data = {'net_name': network.name,
'admin_state': network.admin_state_up,
# subnet
'with_subnet': False}
if api.neutron.is_port_profiles_supported():
form_data['net_profile_id'] = net_profile_id
form_data.update(form_data_no_subnet())
url = reverse('horizon:project:networks:create')
res = self.client.post(url, form_data)
@ -254,13 +274,24 @@ class NetworkTests(test.TestCase):
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api.neutron: ('network_create',
'subnet_create',)})
'subnet_create',
'profile_list',)})
def test_network_create_post_with_subnet(self):
network = self.networks.first()
subnet = self.subnets.first()
api.neutron.network_create(IsA(http.HttpRequest), name=network.name,
admin_state_up=network.admin_state_up)\
.AndReturn(network)
params = {'name': network.name,
'admin_state_up': network.admin_state_up}
# TODO(absubram): Remove if clause and create separate
# test stubs for when profile_support is being used.
# Additionally ensure those are always run even in default setting
if api.neutron.is_port_profiles_supported():
net_profiles = self.net_profiles.list()
net_profile_id = self.net_profiles.first().id
api.neutron.profile_list(IsA(http.HttpRequest),
'network').AndReturn(net_profiles)
params['net_profile_id'] = net_profile_id
api.neutron.network_create(IsA(http.HttpRequest),
**params).AndReturn(network)
api.neutron.subnet_create(IsA(http.HttpRequest),
network_id=network.id,
name=subnet.name,
@ -274,6 +305,8 @@ class NetworkTests(test.TestCase):
form_data = {'net_name': network.name,
'admin_state': network.admin_state_up,
'with_subnet': True}
if api.neutron.is_port_profiles_supported():
form_data['net_profile_id'] = net_profile_id
form_data.update(form_data_subnet(subnet, allocation_pools=[]))
url = reverse('horizon:project:networks:create')
res = self.client.post(url, form_data)
@ -281,18 +314,31 @@ class NetworkTests(test.TestCase):
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api.neutron: ('network_create',)})
@test.create_stubs({api.neutron: ('network_create',
'profile_list',)})
def test_network_create_post_network_exception(self):
network = self.networks.first()
api.neutron.network_create(IsA(http.HttpRequest), name=network.name,
admin_state_up=network.admin_state_up)\
.AndRaise(self.exceptions.neutron)
params = {'name': network.name,
'admin_state_up': network.admin_state_up}
# TODO(absubram): Remove if clause and create separate
# test stubs for when profile_support is being used.
# Additionally ensure those are always run even in default setting
if api.neutron.is_port_profiles_supported():
net_profiles = self.net_profiles.list()
net_profile_id = self.net_profiles.first().id
api.neutron.profile_list(IsA(http.HttpRequest),
'network').AndReturn(net_profiles)
params['net_profile_id'] = net_profile_id
api.neutron.network_create(IsA(http.HttpRequest),
**params).AndRaise(self.exceptions.neutron)
self.mox.ReplayAll()
form_data = {'net_name': network.name,
'admin_state': network.admin_state_up,
# subnet
'with_subnet': False}
if api.neutron.is_port_profiles_supported():
form_data['net_profile_id'] = net_profile_id
form_data.update(form_data_no_subnet())
url = reverse('horizon:project:networks:create')
res = self.client.post(url, form_data)
@ -300,18 +346,31 @@ class NetworkTests(test.TestCase):
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api.neutron: ('network_create',)})
@test.create_stubs({api.neutron: ('network_create',
'profile_list')})
def test_network_create_post_with_subnet_network_exception(self):
network = self.networks.first()
subnet = self.subnets.first()
api.neutron.network_create(IsA(http.HttpRequest), name=network.name,
admin_state_up=network.admin_state_up)\
.AndRaise(self.exceptions.neutron)
params = {'name': network.name,
'admin_state_up': network.admin_state_up}
# TODO(absubram): Remove if clause and create separate
# test stubs for when profile_support is being used.
# Additionally ensure those are always run even in default setting
if api.neutron.is_port_profiles_supported():
net_profiles = self.net_profiles.list()
net_profile_id = self.net_profiles.first().id
api.neutron.profile_list(IsA(http.HttpRequest),
'network').AndReturn(net_profiles)
params['net_profile_id'] = net_profile_id
api.neutron.network_create(IsA(http.HttpRequest),
**params).AndRaise(self.exceptions.neutron)
self.mox.ReplayAll()
form_data = {'net_name': network.name,
'admin_state': network.admin_state_up,
'with_subnet': True}
if api.neutron.is_port_profiles_supported():
form_data['net_profile_id'] = net_profile_id
form_data.update(form_data_subnet(subnet, allocation_pools=[]))
url = reverse('horizon:project:networks:create')
res = self.client.post(url, form_data)
@ -321,13 +380,24 @@ class NetworkTests(test.TestCase):
@test.create_stubs({api.neutron: ('network_create',
'network_delete',
'subnet_create',)})
'subnet_create',
'profile_list')})
def test_network_create_post_with_subnet_subnet_exception(self):
network = self.networks.first()
subnet = self.subnets.first()
api.neutron.network_create(IsA(http.HttpRequest), name=network.name,
admin_state_up=network.admin_state_up)\
.AndReturn(network)
params = {'name': network.name,
'admin_state_up': network.admin_state_up}
# TODO(absubram): Remove if clause and create separate
# test stubs for when profile_support is being used.
# Additionally ensure those are always run even in default setting
if api.neutron.is_port_profiles_supported():
net_profiles = self.net_profiles.list()
net_profile_id = self.net_profiles.first().id
api.neutron.profile_list(IsA(http.HttpRequest),
'network').AndReturn(net_profiles)
params['net_profile_id'] = net_profile_id
api.neutron.network_create(IsA(http.HttpRequest),
**params).AndReturn(network)
api.neutron.subnet_create(IsA(http.HttpRequest),
network_id=network.id,
name=subnet.name,
@ -343,6 +413,8 @@ class NetworkTests(test.TestCase):
form_data = {'net_name': network.name,
'admin_state': network.admin_state_up,
'with_subnet': True}
if api.neutron.is_port_profiles_supported():
form_data['net_profile_id'] = net_profile_id
form_data.update(form_data_subnet(subnet, allocation_pools=[]))
url = reverse('horizon:project:networks:create')
res = self.client.post(url, form_data)
@ -350,14 +422,25 @@ class NetworkTests(test.TestCase):
self.assertNoFormErrors(res)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api.neutron: ('profile_list',)})
def test_network_create_post_with_subnet_nocidr(self):
network = self.networks.first()
subnet = self.subnets.first()
# TODO(absubram): Remove if clause and create separate
# test stubs for when profile_support is being used.
# Additionally ensure those are always run even in default setting
if api.neutron.is_port_profiles_supported():
net_profiles = self.net_profiles.list()
net_profile_id = self.net_profiles.first().id
api.neutron.profile_list(IsA(http.HttpRequest),
'network').AndReturn(net_profiles)
self.mox.ReplayAll()
form_data = {'net_name': network.name,
'admin_state': network.admin_state_up,
'with_subnet': True}
if api.neutron.is_port_profiles_supported():
form_data['net_profile_id'] = net_profile_id
form_data.update(form_data_subnet(subnet, cidr='',
allocation_pools=[]))
url = reverse('horizon:project:networks:create')
@ -366,13 +449,25 @@ class NetworkTests(test.TestCase):
self.assertContains(res, escape('Specify "Network Address" or '
'clear "Create Subnet" checkbox.'))
@test.create_stubs({api.neutron: ('profile_list',)})
def test_network_create_post_with_subnet_cidr_without_mask(self):
network = self.networks.first()
subnet = self.subnets.first()
# TODO(absubram): Remove if clause and create separate
# test stubs for when profile_support is being used.
# Additionally ensure those are always run even in default setting
if api.neutron.is_port_profiles_supported():
net_profiles = self.net_profiles.list()
net_profile_id = self.net_profiles.first().id
api.neutron.profile_list(IsA(http.HttpRequest),
'network').AndReturn(net_profiles)
self.mox.ReplayAll()
form_data = {'net_name': network.name,
'admin_state': network.admin_state_up,
'with_subnet': True}
if api.neutron.is_port_profiles_supported():
form_data['net_profile_id'] = net_profile_id
form_data.update(form_data_subnet(subnet, cidr='10.0.0.0',
allocation_pools=[]))
url = reverse('horizon:project:networks:create')
@ -381,9 +476,18 @@ class NetworkTests(test.TestCase):
expected_msg = "The subnet in the Network Address is too small (/32)."
self.assertContains(res, expected_msg)
@test.create_stubs({api.neutron: ('profile_list',)})
def test_network_create_post_with_subnet_cidr_inconsistent(self):
network = self.networks.first()
subnet = self.subnets.first()
# TODO(absubram): Remove if clause and create separate
# test stubs for when profile_support is being used.
# Additionally ensure those are always run even in default setting
if api.neutron.is_port_profiles_supported():
net_profiles = self.net_profiles.list()
net_profile_id = self.net_profiles.first().id
api.neutron.profile_list(IsA(http.HttpRequest),
'network').AndReturn(net_profiles)
self.mox.ReplayAll()
# dummy IPv6 address
@ -391,6 +495,8 @@ class NetworkTests(test.TestCase):
form_data = {'net_name': network.name,
'admin_state': network.admin_state_up,
'with_subnet': True}
if api.neutron.is_port_profiles_supported():
form_data['net_profile_id'] = net_profile_id
form_data.update(form_data_subnet(subnet, cidr=cidr,
allocation_pools=[]))
url = reverse('horizon:project:networks:create')
@ -399,9 +505,18 @@ class NetworkTests(test.TestCase):
expected_msg = 'Network Address and IP version are inconsistent.'
self.assertContains(res, expected_msg)
@test.create_stubs({api.neutron: ('profile_list',)})
def test_network_create_post_with_subnet_gw_inconsistent(self):
network = self.networks.first()
subnet = self.subnets.first()
# TODO(absubram): Remove if clause and create separate
# test stubs for when profile_support is being used.
# Additionally ensure those are always run even in default setting
if api.neutron.is_port_profiles_supported():
net_profiles = self.net_profiles.list()
net_profile_id = self.net_profiles.first().id
api.neutron.profile_list(IsA(http.HttpRequest),
'network').AndReturn(net_profiles)
self.mox.ReplayAll()
# dummy IPv6 address
@ -409,6 +524,8 @@ class NetworkTests(test.TestCase):
form_data = {'net_name': network.name,
'admin_state': network.admin_state_up,
'with_subnet': True}
if api.neutron.is_port_profiles_supported():
form_data['net_profile_id'] = net_profile_id
form_data.update(form_data_subnet(subnet, gateway_ip=gateway_ip,
allocation_pools=[]))
url = reverse('horizon:project:networks:create')

View File

@ -37,9 +37,35 @@ class CreateNetworkInfoAction(workflows.Action):
net_name = forms.CharField(max_length=255,
label=_("Network Name"),
required=False)
if api.neutron.is_port_profiles_supported():
net_profile_id = forms.ChoiceField(label=_("Network Profile"))
admin_state = forms.BooleanField(label=_("Admin State"),
initial=True, required=False)
if api.neutron.is_port_profiles_supported():
def __init__(self, request, *args, **kwargs):
super(CreateNetworkInfoAction, self).__init__(request,
*args, **kwargs)
self.fields['net_profile_id'].choices = (
self.get_network_profile_choices(request))
def get_network_profile_choices(self, request):
profile_choices = [('', _("Select a profile"))]
for profile in self._get_profiles(request, 'network'):
profile_choices.append((profile.id, profile.name))
return profile_choices
def _get_profiles(self, request, type_p):
try:
profiles = api.neutron.profile_list(request, type_p)
except Exception:
profiles = []
msg = _('Network Profiles could not be retrieved.')
exceptions.handle(request, msg)
return profiles
# TODO(absubram): Add ability to view network profile information
# in the network detail if a profile is used.
class Meta:
name = _("Network")
help_text = _("From here you can create a new network.\n"
@ -49,7 +75,10 @@ class CreateNetworkInfoAction(workflows.Action):
class CreateNetworkInfo(workflows.Step):
action_class = CreateNetworkInfoAction
contributes = ("net_name", "admin_state")
if api.neutron.is_port_profiles_supported():
contributes = ("net_name", "admin_state", "net_profile_id")
else:
contributes = ("net_name", "admin_state")
class CreateSubnetInfoAction(workflows.Action):
@ -257,6 +286,8 @@ class CreateNetwork(workflows.Workflow):
try:
params = {'name': data['net_name'],
'admin_state_up': data['admin_state']}
if api.neutron.is_port_profiles_supported():
params['net_profile_id'] = data['net_profile_id']
network = api.neutron.network_create(request, **params)
network.set_id_as_name_if_empty()
self.context['net_id'] = network.id

View File

@ -0,0 +1,33 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# @author: Abishek Subramanian, Cisco Systems, Inc.
from django.utils.translation import ugettext_lazy as _ # noqa
from openstack_dashboard.api import neutron
import horizon
class Router(horizon.Dashboard):
name = _("Router")
slug = "router"
panels = ('nexus1000v',)
default_panel = 'nexus1000v'
permissions = ('openstack.roles.admin',)
if neutron.is_port_profiles_supported():
horizon.register(Router)

View File

@ -0,0 +1,3 @@
"""
Stub file to work around django bug: https://code.djangoproject.com/ticket/7198
"""

View File

@ -0,0 +1,159 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# @author: Abishek Subramanian, Cisco Systems, Inc.
# @author: Sergey Sudakovich, Cisco Systems, Inc.
import logging
from horizon import exceptions
from horizon import forms
from horizon import messages
from openstack_dashboard import api
from django.core.urlresolvers import reverse # noqa
from django.utils.translation import ugettext_lazy as _ # noqa
LOG = logging.getLogger(__name__)
def get_tenant_choices(request):
tenant_choices = [('', _("Select a tenant"))]
tenants = []
try:
tenants, has_more = api.keystone.tenant_list(request)
except Exception:
msg = _('Projects could not be retrieved.')
exceptions.handle(request, msg)
for tenant in tenants:
if tenant.enabled:
tenant_choices.append((tenant.id, tenant.name))
return tenant_choices
class CreateNetworkProfile(forms.SelfHandlingForm):
""" Create Network Profile form."""
name = forms.CharField(max_length=255,
label=_("Name"),
required=True)
segment_type = forms.ChoiceField(label=_('Segment Type'),
choices=[('vlan', _('VLAN')),
('vxlan', _('VXLAN'))],
widget=forms.Select
(attrs={'class': 'switchable',
'data-slug': 'segtype'}))
segment_range = forms.CharField(max_length=255,
label=_("Segment Range"),
required=True,
help_text=_("1-4093 for VLAN"))
# TODO(absubram): Update help text for VXLAN segment range value.
multicast_ip_range = forms.CharField(max_length=30,
label=_("Multicast IP Range"),
required=False,
widget=forms.TextInput
(attrs={'class': 'switched',
'data-switch-on':
'segtype',
'data-segtype-vxlan':
_("Multicast IP Range")}))
physical_network = forms.CharField(max_length=255,
label=_("Physical Network"),
required=False,
widget=forms.TextInput
(attrs={'class': 'switched',
'data-switch-on': 'segtype',
'data-segtype-vlan':
_("Physical Network")}))
project_id = forms.ChoiceField(label=_("Project"),
required=False)
def __init__(self, request, *args, **kwargs):
super(CreateNetworkProfile, self).__init__(request, *args, **kwargs)
self.fields['project_id'].choices = get_tenant_choices(request)
def handle(self, request, data):
try:
LOG.debug(_('request = %(req)s, params = %(params)s'),
{'req': request, 'params': data})
profile = api.neutron.profile_create(request,
name=data['name'],
segment_type=
data['segment_type'],
segment_range=
data['segment_range'],
physical_network=
data['physical_network'],
multicast_ip_range=
data['multicast_ip_range'],
tenant_id=data['project_id'])
msg = _('Network Profile %s '
'was successfully created.') % data['name']
LOG.debug(msg)
messages.success(request, msg)
return profile
except Exception:
redirect = reverse('horizon:router:nexus1000v:index')
msg = _('Failed to create network profile %s') % data['name']
LOG.error(msg)
exceptions.handle(request, msg, redirect=redirect)
class UpdateNetworkProfile(forms.SelfHandlingForm):
""" Update Network Profile form."""
profile_id = forms.CharField(label=_("ID"),
widget=forms.HiddenInput())
name = forms.CharField(max_length=255,
label=_("Name"), required=True)
segment_type = forms.ChoiceField(label=_('Segment Type'),
choices=[('vlan', 'VLAN'),
('vxlan', 'VXLAN')],
widget=forms.Select
(attrs={'class': 'switchable'}))
segment_range = forms.CharField(max_length=255,
label=_("Segment Range"),
required=True)
physical_network = forms.CharField(max_length=255,
label=_("Physical Network"),
required=False)
project_id = forms.CharField(label=_("Project"), required=False)
def handle(self, request, data):
try:
LOG.debug(_('request = %(req)s, params = %(params)s'),
{'req': request, 'params': data})
profile = api.neutron.profile_modify(request,
data['profile_id'],
name=data['name'],
segment_type=
data['segment_type'],
segment_range=
data['segment_range'],
physical_network=
data['physical_network'])
msg = _('Network Profile %s '
'was successfully updated.') % data['profile_id']
LOG.debug(msg)
messages.success(request, msg)
return profile
except Exception:
LOG.error(_('Failed to update network profile (%s).') %
data['profile_id'])
redirect = reverse('horizon:router:nexus1000v:index')
exceptions.handle(request, msg, redirect=redirect)

View File

@ -0,0 +1,31 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# @author: Abishek Subramanian, Cisco Systems, Inc.
# @author: Sergey Sudakovich, Cisco Systems, Inc.
from django.utils.translation import ugettext_lazy as _ # noqa
import horizon
from openstack_dashboard.api import neutron as neutron
from openstack_dashboard.dashboards.router import dashboard
class Nexus1000v(horizon.Panel):
name = _("Cisco Nexus 1000v")
slug = 'nexus1000v'
permissions = ('openstack.services.network',)
if neutron.is_port_profiles_supported():
dashboard.Router.register(Nexus1000v)

View File

@ -0,0 +1,93 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# @author: Abishek Subramanian, Cisco Systems, Inc.
# @author: Sergey Sudakovich, Cisco Systems, Inc.
import logging
from django.core.urlresolvers import reverse # noqa
from django.utils.translation import ugettext_lazy as _ # noqa
from horizon import exceptions
from horizon import tables
from openstack_dashboard import api
LOG = logging.getLogger(__name__)
class CreateNetworkProfile(tables.LinkAction):
name = "create"
verbose_name = _("Create Network Profile")
url = "horizon:router:nexus1000v:create_network_profile"
classes = ("ajax-modal", "btn-create")
class DeleteNetworkProfile(tables.DeleteAction):
data_type_singular = _("Network Profile")
data_type_plural = _("Netork Profiles")
def delete(self, request, obj_id):
try:
api.neutron.profile_delete(request, obj_id)
except Exception:
msg = _('Failed to delete network profile (%s).') % obj_id
LOG.info(msg)
redirect = reverse('horizon:router:nexus1000v:index')
exceptions.handle(request, msg, redirect=redirect)
class EditNetworkProfile(tables.LinkAction):
name = "update"
verbose_name = _("Edit Network Profile")
url = "horizon:router:nexus1000v:update_network_profile"
classes = ("ajax-modal", "btn-edit")
class NetworkProfile(tables.DataTable):
id = tables.Column("profile_id", verbose_name=_("Profile ID"), hidden=True)
name = tables.Column("name", verbose_name=_("Network Profile"), )
project = tables.Column("project_name", verbose_name=_("Project"))
segment_type = tables.Column("segment_type",
verbose_name=_("Segment Type"))
segment_range = tables.Column("segment_range",
verbose_name=_("Segment Range"))
multicast_ip_range = tables.Column("multicast_ip_range",
verbose_name=_("Multicast IP Range"))
physical_network = tables.Column("physical_network",
verbose_name=_("Physical Network Name"))
class Meta:
name = "network_profile"
verbose_name = _("Network Profile")
table_actions = (CreateNetworkProfile, DeleteNetworkProfile,)
row_actions = (EditNetworkProfile, DeleteNetworkProfile,)
class EditPolicyProfile(tables.LinkAction):
name = "edit"
verbose_name = _("Edit Policy Profile")
url = "horizon:project:images_and_snapshots:images:update"
classes = ("ajax-modal", "btn-edit")
class PolicyProfile(tables.DataTable):
id = tables.Column("profile_id", verbose_name=_("Profile ID"), hidden=True)
name = tables.Column("name", verbose_name=_("Policy Profile"), )
project_id = tables.Column("project_name", verbose_name=_("Project"))
class Meta:
name = "policy_profile"
verbose_name = _("Policy Profile")

View File

@ -0,0 +1,41 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# @author: Abishek Subramanian, Cisco Systems, Inc.
# @author: Sergey Sudakovich, Cisco Systems, Inc.
from django.utils.translation import ugettext as _ # noqa
from horizon import tabs
class NetworkProfileTab(tabs.Tab):
name = _("Network Profile")
slug = "network_profile"
template_name = 'router/nexus1000v/network_profile/index.html'
def get_context_data(self, request):
return None
class PolicyProfileTab(tabs.Tab):
name = _("Policy Profile")
slug = "policy_profile"
template_name = 'router/nexus1000v/policy_profile/index.html'
preload = False
class IndexTabs(tabs.TabGroup):
slug = "indextabs"
tabs = (NetworkProfileTab, PolicyProfileTab)

View File

@ -0,0 +1,26 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% load url from future %}
{% block form_id %}create_network_profile_form{% endblock %}
{% block form_action %}{% url 'horizon:router:nexus1000v:create_network_profile' %}{% endblock %}
{% block modal_id %}create_network_profile_modal{% endblock %}
{% block modal-header %}{% trans "Create Network Profile" %}{% endblock %}
{% block modal-body %}
<div class="left">
<fieldset>
{% include "horizon/common/_form_fields.html" %}
</fieldset>
</div>
<div class="right">
<h3>{% trans "Description" %}:</h3>
<p>{% trans "Select a name for your network profile."%}</p>
</div>
{% endblock %}
{% block modal-footer %}
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Create Network Profile" %}" />
<a href="{% url 'horizon:router:nexus1000v:index' %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
{% endblock %}

View File

@ -0,0 +1,25 @@
{% extends "horizon/common/_modal_form.html" %}
{% load i18n %}
{% load url from future %}
{% block form_id %}update_networkprofile_form{% endblock %}
{% block form_action %}{% url 'horizon:router:nexus1000v:update_network_profile profile_id' %}{% endblock %}
{% block modal-header %}{% trans "Edit Network Profile" %}{% endblock %}
{% block modal-body %}
<div class="left">
<fieldset>
{% include "horizon/common/_form_fields.html" %}
</fieldset>
</div>
<div class="right">
<h3>{% trans "Description:" %}</h3>
<p>{% trans "You may update the editable properties of your network profile here." %}</p>
</div>
{% endblock %}
{% block modal-footer %}
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Save Changes" %}" />
<a href="{% url horizon:router:nexus1000v:index %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Create Network Profile" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Create Network Profile") %}
{% endblock page_header %}
{% block main %}
{% include "router/nexus1000v/_create_network_profile.html" %}
{% endblock %}

View File

@ -0,0 +1,23 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Cisco Nexus 1000V Networking" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Cisco Nexus 1000V") %}
{% endblock page_header %}
{% block main %}
<div id="network_profile">
{{ network_profile_table.render }}
</div>
<div id="policy_profile">
{{ policy_profile_table.render }}
</div>
{% comment %}
<div class="row-fluid">
<div class="span12">
{{ tab_group.render }}
</div>
</div>
{% endcomment %}
{% endblock %}

View File

@ -0,0 +1,13 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Cisco Nexus 1000V Networking" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Cisco Nexus 1000V") %}
{% endblock page_header %}
{% block main %}
<div id="network_profile">
{{ table.render }}
</div>
{% endblock %}

View File

@ -0,0 +1,13 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Cisco Nexus 1000V Networking" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Cisco Nexus 1000V") %}
{% endblock page_header %}
{% block main %}
<div id="policy_profile">
{{ table.render }}
</div>
{% endblock %}

View File

@ -0,0 +1,11 @@
{% extends 'base.html' %}
{% load i18n %}
{% block title %}{% trans "Update Network" %}{% endblock %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Update Network Profile") %}
{% endblock page_header %}
{% block main %}
{% include 'router/nexus1000v/_update_network_profile.html' %}
{% endblock %}

View File

@ -0,0 +1,57 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# @author: Abishek Subramanian, Cisco Systems, Inc.
from django.core.urlresolvers import reverse # noqa
from django import http
from mox import IsA # noqa
from openstack_dashboard import api
from openstack_dashboard.test import helpers as test
# TODO(absubram): Remove if clause and create separate
# test stubs for when profile_support is being used and when not.
# Additionally ensure those are always run even in default setting
if api.neutron.is_port_profiles_supported():
class Nexus1000vTest(test.BaseAdminViewTests):
@test.create_stubs({api.neutron: ('profile_list',
'profile_bindings_list'),
api.keystone: ('tenant_list',)})
def test_index(self):
tenants = self.tenants.list()
net_profiles = self.net_profiles.list()
policy_profiles = self.policy_profiles.list()
net_profile_binding = self.network_profile_binding.list()
policy_profile_binding = self.policy_profile_binding.list()
api.neutron.profile_list(IsA(http.HttpRequest),
'network').AndReturn(net_profiles)
api.neutron.profile_list(IsA(http.HttpRequest),
'policy').AndReturn(policy_profiles)
api.neutron.profile_bindings_list(
IsA(http.HttpRequest),
'network').AndReturn(net_profile_binding)
api.neutron.profile_bindings_list(
IsA(http.HttpRequest),
'policy').AndReturn(policy_profile_binding)
api.keystone.tenant_list(
IsA(http.HttpRequest)).AndReturn([tenants, False])
api.keystone.tenant_list(
IsA(http.HttpRequest)).AndReturn([tenants, False])
self.mox.ReplayAll()
res = self.client.get(reverse('horizon:router:nexus1000v:index'))
self.assertTemplateUsed(res, 'router/nexus1000v/index.html')

View File

@ -0,0 +1,32 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# @author: Abishek Subramanian, Cisco Systems, Inc.
# @author: Sergey Sudakovich, Cisco Systems, Inc.
from django.conf.urls.defaults import patterns # noqa
from django.conf.urls.defaults import url # noqa
from openstack_dashboard.dashboards.router.nexus1000v import views
urlpatterns = patterns('',
url(r'^$', views.IndexView.as_view(), name='index'),
#Network Profile
url(r'^network_profile/create$', views.CreateNetworkProfileView.as_view(),
name='create_network_profile'),
url(r'^network_profile/(?P<profile_id>[^/]+)/update$',
views.UpdateNetworkProfileView.as_view(),
name='update_network_profile'),
)

View File

@ -0,0 +1,141 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
# @author: Abishek Subramanian, Cisco Systems, Inc.
# @author: Sergey Sudakovich, Cisco Systems, Inc.
import logging
from django.core import urlresolvers
from django.utils import datastructures
from django.utils.translation import ugettext_lazy as _ # noqa
from horizon import exceptions
from horizon import forms
from horizon import tables
from horizon import tabs
from openstack_dashboard import api
from openstack_dashboard.dashboards.router.nexus1000v \
import forms as profileforms
from openstack_dashboard.dashboards.router.nexus1000v \
import tables as profiletables
LOG = logging.getLogger(__name__)
def _get_tenant_list(request):
tenants = []
try:
tenants, has_more = api.keystone.tenant_list(request)
except Exception:
msg = _('Unable to retrieve project information.')
exceptions.handle(request, msg)
return datastructures.SortedDict([(t.id, t) for t in tenants])
def _get_profiles(request, type_p):
try:
profiles = api.neutron.profile_list(request, type_p)
except Exception:
profiles = []
msg = _('Network Profiles could not be retrieved.')
exceptions.handle(request, msg)
if profiles:
tenant_dict = _get_tenant_list(request)
bindings = api.neutron.profile_bindings_list(request, type_p)
for p in profiles:
# Set tenant name
if bindings:
for b in bindings:
if (p.id == b.profile_id):
tenant = tenant_dict.get(b.tenant_id, None)
p.tenant_name = getattr(tenant, 'name', None)
return profiles
class NetworkProfileIndexView(tables.DataTableView):
table_class = profiletables.NetworkProfile
template_name = 'router/nexus1000v/network_profile/index.html'
def get_data(self):
return _get_profiles(self.request, 'network')
class PolicyProfileIndexView(tables.DataTableView):
table_class = profiletables.PolicyProfile
template_name = 'router/nexus1000v/policy_profile/index.html'
def get_data(self):
return _get_profiles(self.request, 'policy')
class IndexTabGroup(tabs.TabGroup):
slug = "group"
tabs = (NetworkProfileIndexView, PolicyProfileIndexView,)
class IndexView(tables.MultiTableView):
table_classes = (profiletables.NetworkProfile,
profiletables.PolicyProfile,)
template_name = 'router/nexus1000v/index.html'
def get_network_profile_data(self):
return _get_profiles(self.request, 'network')
def get_policy_profile_data(self):
return _get_profiles(self.request, 'policy')
class CreateNetworkProfileView(forms.ModalFormView):
form_class = profileforms.CreateNetworkProfile
template_name = 'router/nexus1000v/create_network_profile.html'
success_url = urlresolvers.reverse_lazy('horizon:router:nexus1000v:index')
class UpdateNetworkProfileView(forms.ModalFormView):
form_class = profileforms.UpdateNetworkProfile
template_name = 'router/nexus1000v/update_network_profile.html'
context_object_name = 'network_profile'
success_url = urlresolvers.reverse_lazy('horizon:router:nexus1000v:index')
def get_context_data(self, **kwargs):
context = super(UpdateNetworkProfileView,
self).get_context_data(**kwargs)
context["profile_id"] = self.kwargs['profile_id']
return context
def _get_object(self, *args, **kwargs):
if not hasattr(self, "_object"):
profile_id = self.kwargs['profile_id']
try:
self._object = api.neutron.profile_get(self.request,
profile_id)
LOG.debug(_("Network Profile object=%s") % self._object)
except Exception:
redirect = self.success_url
msg = _('Unable to retrieve network profile details.')
exceptions.handle(self.request, msg, redirect=redirect)
return self._object
def get_initial(self):
profile = self._get_object()
return {'profile_id': profile['id'],
'name': profile['name'],
'segment_range': profile['segment_range'],
'segment_type': profile['segment_type'],
'physical_network': profile['physical_network']}

View File

@ -160,6 +160,11 @@ OPENSTACK_NEUTRON_NETWORK = {
'enable_lb': False,
'enable_quotas': True,
'enable_security_group': True,
# The profile_support option is used to detect if an external router can be
# configured via the dashboard. When using specific plugins the
# profile_support can be turned on if needed.
'profile_support': None,
#'profile_support': 'cisco',
}
# The OPENSTACK_IMAGE_BACKEND settings can be used to customize features

View File

@ -54,7 +54,7 @@ STATIC_URL = '/static/'
ROOT_URLCONF = 'openstack_dashboard.urls'
HORIZON_CONFIG = {
'dashboards': ('project', 'admin', 'settings',),
'dashboards': ('project', 'admin', 'settings', 'router',),
'default_dashboard': 'project',
'user_home': 'openstack_dashboard.views.get_user_home',
'ajax_queue_limit': 10,
@ -139,6 +139,7 @@ INSTALLED_APPS = (
'openstack_dashboard.dashboards.admin',
'openstack_dashboard.dashboards.settings',
'openstack_auth',
'openstack_dashboard.dashboards.router',
)
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'

View File

@ -37,6 +37,13 @@ INSTALLED_APPS = (
'openstack_dashboard.dashboards.project',
'openstack_dashboard.dashboards.admin',
'openstack_dashboard.dashboards.settings',
# If the profile_support config is turned on in local_settings
# the "router" dashboard will be enabled which can be used to
# create and use profiles with networks and instances. In which case
# using run_tests will require the registration of the "router" dashboard.
# TODO (absubram): Need to make this permanent when a better solution
# for run_tests is implemented to use with and without the n1k sub-plugin.
#'openstack_dashboard.dashboards.router',
)
AUTHENTICATION_BACKENDS = ('openstack_auth.backend.KeystoneBackend',)
@ -44,7 +51,15 @@ AUTHENTICATION_BACKENDS = ('openstack_auth.backend.KeystoneBackend',)
SITE_BRANDING = 'OpenStack'
HORIZON_CONFIG = {
'dashboards': ('project', 'admin', 'settings'),
'dashboards': ('project', 'admin', 'settings',),
# If the profile_support config is turned on in local_settings
# the "router" dashboard will be enabled which can be used to
# create and use profiles with networks and instances. In which case
# using run_tests will require the registration of the "router" dashboard.
# TODO (absubram): Need to make this permanent when a better solution
# for run_tests is implemented to use with and without the n1k sub-plugin.
#'openstack_dashboard.dashboards.router',
#'dashboards': ('project', 'admin', 'settings', 'router',),
'default_dashboard': 'project',
"password_validator": {
"regex": '^.{8,18}$',

View File

@ -37,6 +37,10 @@ def data(TEST):
TEST.members = utils.TestDataContainer()
TEST.monitors = utils.TestDataContainer()
TEST.neutron_quotas = utils.TestDataContainer()
TEST.net_profiles = utils.TestDataContainer()
TEST.policy_profiles = utils.TestDataContainer()
TEST.network_profile_binding = utils.TestDataContainer()
TEST.policy_profile_binding = utils.TestDataContainer()
# data return by neutronclient
TEST.api_agents = utils.TestDataContainer()
@ -52,6 +56,10 @@ def data(TEST):
TEST.api_members = utils.TestDataContainer()
TEST.api_monitors = utils.TestDataContainer()
TEST.api_extensions = utils.TestDataContainer()
TEST.api_net_profiles = utils.TestDataContainer()
TEST.api_policy_profiles = utils.TestDataContainer()
TEST.api_network_profile_binding = utils.TestDataContainer()
TEST.api_policy_profile_binding = utils.TestDataContainer()
#------------------------------------------------------------
# 1st network
@ -85,6 +93,44 @@ def data(TEST):
TEST.networks.add(neutron.Network(network))
TEST.subnets.add(subnet)
# network profile for network when using the cisco n1k plugin
net_profile_dict = {'name': 'net_profile_test1',
'segment_type': 'vlan',
'physical_network': 'phys1',
'segment_range': '3000-31000',
'id':
'00000000-1111-1111-1111-000000000000',
'tenant_id': network_dict['tenant_id']}
TEST.api_net_profiles.add(net_profile_dict)
TEST.net_profiles.add(neutron.Profile(net_profile_dict))
# policy profile for port when using the cisco n1k plugin
policy_profile_dict = {'name': 'policy_profile_test1',
'id':
'00000000-9999-9999-9999-000000000000'}
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-1111-1111-1111-000000000000',
'tenant_id': network_dict['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':
'00000000-9999-9999-9999-000000000000',
'tenant_id': network_dict['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 1st network
port_dict = {'admin_state_up': True,
'device_id': 'af75c8e5-a1cc-4567-8d04-44fcd6922890',