Initial support of Quantum V2.
Implementes blueprint quantum-horizon and blueprint readd-quantum-support. This commit also covers blueprint quantum-workflow-integration. - Added quantum API layer, - Added network/subnet/port CRUD operations, - Added 'Network' user panel, - Added 'Network' system panel, - Added 'Networking' tab in instance creation workflow. - Supported launching an instance with specified network(s) Change-Id: I7ad608e17cb6fb4f0de02721888e96a68cf926e8
This commit is contained in:
parent
b0066309fd
commit
05cf900492
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,5 +1,6 @@
|
||||
*.pyc
|
||||
*.swp
|
||||
*.sqlite3
|
||||
.environment_version
|
||||
.selenium_log
|
||||
.coverage*
|
||||
|
@ -36,3 +36,4 @@ from horizon.api.glance import *
|
||||
from horizon.api.keystone import *
|
||||
from horizon.api.nova import *
|
||||
from horizon.api.swift import *
|
||||
from horizon.api.quantum import *
|
||||
|
@ -292,11 +292,13 @@ def keypair_list(request):
|
||||
|
||||
|
||||
def server_create(request, name, image, flavor, key_name, user_data,
|
||||
security_groups, block_device_mapping, instance_count=1):
|
||||
security_groups, block_device_mapping, nics=None,
|
||||
instance_count=1):
|
||||
return Server(novaclient(request).servers.create(
|
||||
name, image, flavor, userdata=user_data,
|
||||
security_groups=security_groups,
|
||||
key_name=key_name, block_device_mapping=block_device_mapping,
|
||||
nics=nics,
|
||||
min_count=instance_count), request)
|
||||
|
||||
|
||||
|
239
horizon/api/quantum.py
Normal file
239
horizon/api/quantum.py
Normal file
@ -0,0 +1,239 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Copyright 2012 Cisco Systems, Inc.
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
|
||||
from quantumclient.v2_0 import client as quantum_client
|
||||
from django.utils.datastructures import SortedDict
|
||||
|
||||
from horizon.api.base import APIDictWrapper, url_for
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class QuantumAPIDictWrapper(APIDictWrapper):
|
||||
|
||||
def set_id_as_name_if_empty(self, length=8):
|
||||
try:
|
||||
if not self._apidict['name']:
|
||||
id = self._apidict['id']
|
||||
if length:
|
||||
id = id[:length]
|
||||
self._apidict['name'] = '(%s)' % id
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def items(self):
|
||||
return self._apidict.items()
|
||||
|
||||
|
||||
class Network(QuantumAPIDictWrapper):
|
||||
"""Wrapper for quantum Networks"""
|
||||
_attrs = ['name', 'id', 'subnets', 'tenant_id', 'status', 'admin_state_up']
|
||||
|
||||
def __init__(self, apiresource):
|
||||
apiresource['admin_state'] = \
|
||||
'UP' if apiresource['admin_state_up'] else 'DOWN'
|
||||
super(Network, self).__init__(apiresource)
|
||||
|
||||
|
||||
class Subnet(QuantumAPIDictWrapper):
|
||||
"""Wrapper for quantum subnets"""
|
||||
_attrs = ['name', 'id', 'cidr', 'network_id', 'tenant_id',
|
||||
'ip_version', 'ipver_str']
|
||||
|
||||
def __init__(self, apiresource):
|
||||
apiresource['ipver_str'] = get_ipver_str(apiresource['ip_version'])
|
||||
super(Subnet, self).__init__(apiresource)
|
||||
|
||||
|
||||
class Port(QuantumAPIDictWrapper):
|
||||
"""Wrapper for quantum ports"""
|
||||
_attrs = ['name', 'id', 'network_id', 'tenant_id',
|
||||
'admin_state_up', 'status', 'mac_address',
|
||||
'fixed_ips', 'host_routes', 'device_id']
|
||||
|
||||
def __init__(self, apiresource):
|
||||
apiresource['admin_state'] = \
|
||||
'UP' if apiresource['admin_state_up'] else 'DOWN'
|
||||
super(Port, self).__init__(apiresource)
|
||||
|
||||
|
||||
IP_VERSION_DICT = {4: 'IPv4', 6: 'IPv6'}
|
||||
|
||||
|
||||
def get_ipver_str(ip_version):
|
||||
"""Convert an ip version number to a human-friendly string"""
|
||||
return IP_VERSION_DICT.get(ip_version, '')
|
||||
|
||||
|
||||
def quantumclient(request):
|
||||
LOG.debug('quantumclient connection created using token "%s" and url "%s"'
|
||||
% (request.user.token.id, url_for(request, 'network')))
|
||||
LOG.debug('user_id=%(user)s, tenant_id=%(tenant)s' %
|
||||
{'user': request.user.id, 'tenant': request.user.tenant_id})
|
||||
c = quantum_client.Client(token=request.user.token.id,
|
||||
endpoint_url=url_for(request, 'network'))
|
||||
return c
|
||||
|
||||
|
||||
def network_list(request, **params):
|
||||
LOG.debug("network_list(): params=%s" % (params))
|
||||
networks = quantumclient(request).list_networks(**params).get('networks')
|
||||
# Get subnet list to expand subnet info in network list.
|
||||
subnets = subnet_list(request)
|
||||
subnet_dict = SortedDict([(s['id'], s) for s in subnets])
|
||||
# Expand subnet list from subnet_id to values.
|
||||
for n in networks:
|
||||
n['subnets'] = [subnet_dict[s] for s in n['subnets']]
|
||||
return [Network(n) for n in networks]
|
||||
|
||||
|
||||
def network_get(request, network_id, **params):
|
||||
LOG.debug("network_get(): netid=%s, params=%s" % (network_id, params))
|
||||
network = quantumclient(request).show_network(network_id,
|
||||
**params).get('network')
|
||||
# Since the number of subnets per network must be small,
|
||||
# call subnet_get() for each subnet instead of calling
|
||||
# subnet_list() once.
|
||||
network['subnets'] = [subnet_get(request, sid)
|
||||
for sid in network['subnets']]
|
||||
return Network(network)
|
||||
|
||||
|
||||
def network_create(request, **kwargs):
|
||||
"""
|
||||
Create a subnet on a specified network.
|
||||
:param request: request context
|
||||
:param tenant_id: (optional) tenant id of the network created
|
||||
:param name: (optional) name of the network created
|
||||
:returns: Subnet object
|
||||
"""
|
||||
LOG.debug("network_create(): kwargs = %s" % kwargs)
|
||||
body = {'network': kwargs}
|
||||
network = quantumclient(request).create_network(body=body).get('network')
|
||||
return Network(network)
|
||||
|
||||
|
||||
def network_modify(request, network_id, **kwargs):
|
||||
LOG.debug("network_modify(): netid=%s, params=%s" % (network_id, kwargs))
|
||||
body = {'network': kwargs}
|
||||
network = quantumclient(request).update_network(network_id,
|
||||
body=body).get('network')
|
||||
return Network(network)
|
||||
|
||||
|
||||
def network_delete(request, network_id):
|
||||
LOG.debug("network_delete(): netid=%s" % network_id)
|
||||
quantumclient(request).delete_network(network_id)
|
||||
|
||||
|
||||
def subnet_list(request, **params):
|
||||
LOG.debug("subnet_list(): params=%s" % (params))
|
||||
subnets = quantumclient(request).list_subnets(**params).get('subnets')
|
||||
return [Subnet(s) for s in subnets]
|
||||
|
||||
|
||||
def subnet_get(request, subnet_id, **params):
|
||||
LOG.debug("subnet_get(): subnetid=%s, params=%s" % (subnet_id, params))
|
||||
subnet = quantumclient(request).show_subnet(subnet_id,
|
||||
**params).get('subnet')
|
||||
return Subnet(subnet)
|
||||
|
||||
|
||||
def subnet_create(request, network_id, cidr, ip_version, **kwargs):
|
||||
"""
|
||||
Create a subnet on a specified network.
|
||||
:param request: request context
|
||||
:param network_id: network id a subnet is created on
|
||||
:param cidr: subnet IP address range
|
||||
:param ip_version: IP version (4 or 6)
|
||||
:param gateway_ip: (optional) IP address of gateway
|
||||
:param tenant_id: (optional) tenant id of the subnet created
|
||||
:param name: (optional) name of the subnet created
|
||||
:returns: Subnet object
|
||||
"""
|
||||
LOG.debug("subnet_create(): netid=%s, cidr=%s, ipver=%d, kwargs=%s"
|
||||
% (network_id, cidr, ip_version, kwargs))
|
||||
body = {'subnet':
|
||||
{'network_id': network_id,
|
||||
'ip_version': ip_version,
|
||||
'cidr': cidr}}
|
||||
body['subnet'].update(kwargs)
|
||||
subnet = quantumclient(request).create_subnet(body=body).get('subnet')
|
||||
return Subnet(subnet)
|
||||
|
||||
|
||||
def subnet_modify(request, subnet_id, **kwargs):
|
||||
LOG.debug("subnet_modify(): subnetid=%s, kwargs=%s" % (subnet_id, kwargs))
|
||||
body = {'subnet': kwargs}
|
||||
subnet = quantumclient(request).update_subnet(subnet_id,
|
||||
body=body).get('subnet')
|
||||
return Subnet(subnet)
|
||||
|
||||
|
||||
def subnet_delete(request, subnet_id):
|
||||
LOG.debug("subnet_delete(): subnetid=%s" % subnet_id)
|
||||
quantumclient(request).delete_subnet(subnet_id)
|
||||
|
||||
|
||||
def port_list(request, **params):
|
||||
LOG.debug("port_list(): params=%s" % (params))
|
||||
ports = quantumclient(request).list_ports(**params).get('ports')
|
||||
return [Port(p) for p in ports]
|
||||
|
||||
|
||||
def port_get(request, port_id, **params):
|
||||
LOG.debug("port_get(): portid=%s, params=%s" % (port_id, params))
|
||||
port = quantumclient(request).show_port(port_id, **params).get('port')
|
||||
return Port(port)
|
||||
|
||||
|
||||
def port_create(request, network_id, **kwargs):
|
||||
"""
|
||||
Create a port on a specified network.
|
||||
:param request: request context
|
||||
:param network_id: network id a subnet is created on
|
||||
:param device_id: (optional) device id attached to the port
|
||||
:param tenant_id: (optional) tenant id of the port created
|
||||
:param name: (optional) name of the port created
|
||||
:returns: Port object
|
||||
"""
|
||||
LOG.debug("port_create(): netid=%s, kwargs=%s" % (network_id, kwargs))
|
||||
body = {'port': {'network_id': network_id}}
|
||||
body['port'].update(kwargs)
|
||||
port = quantumclient(request).create_port(body=body).get('port')
|
||||
return Port(port)
|
||||
|
||||
|
||||
def port_delete(request, port_id):
|
||||
LOG.debug("port_delete(): portid=%s" % port_id)
|
||||
quantumclient(request).delete_port(port_id)
|
||||
|
||||
|
||||
def port_modify(request, port_id, **kwargs):
|
||||
LOG.debug("port_modify(): portid=%s, kwargs=%s" % (port_id, kwargs))
|
||||
body = {'port': kwargs}
|
||||
port = quantumclient(request).update_port(port_id, body=body).get('port')
|
||||
return Port(port)
|
@ -26,7 +26,8 @@ class BasePanels(horizon.PanelGroup):
|
||||
'instances',
|
||||
'volumes',
|
||||
'images_and_snapshots',
|
||||
'access_and_security')
|
||||
'access_and_security',
|
||||
'networks')
|
||||
|
||||
|
||||
class ObjectStorePanels(horizon.PanelGroup):
|
||||
|
@ -589,6 +589,7 @@ class InstanceTests(test.TestCase):
|
||||
'security_group_list',
|
||||
'volume_snapshot_list',
|
||||
'volume_list',),
|
||||
api.quantum: ('network_list',),
|
||||
api.glance: ('image_list_detailed',)})
|
||||
def test_launch_instance_get(self):
|
||||
quota_usages = self.quota_usages.first()
|
||||
@ -604,6 +605,8 @@ class InstanceTests(test.TestCase):
|
||||
api.glance.image_list_detailed(IsA(http.HttpRequest),
|
||||
filters={'property-owner_id': self.tenant.id}) \
|
||||
.AndReturn([[], False])
|
||||
api.quantum.network_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.networks.list())
|
||||
api.nova.tenant_quota_usages(IsA(http.HttpRequest)) \
|
||||
.AndReturn(quota_usages)
|
||||
api.nova.flavor_list(IsA(http.HttpRequest)) \
|
||||
@ -631,10 +634,12 @@ class InstanceTests(test.TestCase):
|
||||
self.assertQuerysetEqual(workflow.steps,
|
||||
['<SetInstanceDetails: setinstancedetailsaction>',
|
||||
'<SetAccessControls: setaccesscontrolsaction>',
|
||||
'<SetNetwork: setnetworkaction>',
|
||||
'<VolumeOptions: volumeoptionsaction>',
|
||||
'<PostCreationStep: customizeaction>'])
|
||||
|
||||
@test.create_stubs({api.glance: ('image_list_detailed',),
|
||||
api.quantum: ('network_list',),
|
||||
api.nova: ('flavor_list',
|
||||
'keypair_list',
|
||||
'security_group_list',
|
||||
@ -653,6 +658,7 @@ class InstanceTests(test.TestCase):
|
||||
device_name = u'vda'
|
||||
volume_choice = "%s:vol" % volume.id
|
||||
block_device_mapping = {device_name: u"%s::0" % volume_choice}
|
||||
nics = [{"net-id": self.networks.first().id, "v4-fixed-ip": ''}]
|
||||
|
||||
api.nova.flavor_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.flavors.list())
|
||||
@ -666,6 +672,8 @@ class InstanceTests(test.TestCase):
|
||||
api.glance.image_list_detailed(IsA(http.HttpRequest),
|
||||
filters={'property-owner_id': self.tenant.id}) \
|
||||
.AndReturn([[], False])
|
||||
api.quantum.network_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.networks.list())
|
||||
api.nova.volume_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.volumes.list())
|
||||
api.nova.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])
|
||||
@ -677,6 +685,7 @@ class InstanceTests(test.TestCase):
|
||||
customization_script,
|
||||
[sec_group.name],
|
||||
block_device_mapping,
|
||||
nics=nics,
|
||||
instance_count=IsA(int))
|
||||
|
||||
self.mox.ReplayAll()
|
||||
@ -693,6 +702,7 @@ class InstanceTests(test.TestCase):
|
||||
'volume_type': 'volume_id',
|
||||
'volume_id': volume_choice,
|
||||
'device_name': device_name,
|
||||
'network': self.networks.first().id,
|
||||
'count': 1}
|
||||
url = reverse('horizon:nova:instances:launch')
|
||||
res = self.client.post(url, form_data)
|
||||
@ -702,6 +712,7 @@ class InstanceTests(test.TestCase):
|
||||
reverse('horizon:nova:instances:index'))
|
||||
|
||||
@test.create_stubs({api.glance: ('image_list_detailed',),
|
||||
api.quantum: ('network_list',),
|
||||
api.nova: ('flavor_list',
|
||||
'keypair_list',
|
||||
'security_group_list',
|
||||
@ -727,6 +738,8 @@ class InstanceTests(test.TestCase):
|
||||
api.glance.image_list_detailed(IsA(http.HttpRequest),
|
||||
filters={'property-owner_id': self.tenant.id}) \
|
||||
.AndReturn([[], False])
|
||||
api.quantum.network_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.networks.list())
|
||||
api.nova.flavor_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.flavors.list())
|
||||
api.nova.keypair_list(IsA(http.HttpRequest)) \
|
||||
@ -762,6 +775,7 @@ class InstanceTests(test.TestCase):
|
||||
'nova/instances/launch.html')
|
||||
|
||||
@test.create_stubs({api.glance: ('image_list_detailed',),
|
||||
api.quantum: ('network_list',),
|
||||
api.nova: ('tenant_quota_usages',
|
||||
'flavor_list',
|
||||
'keypair_list',
|
||||
@ -779,6 +793,8 @@ class InstanceTests(test.TestCase):
|
||||
api.glance.image_list_detailed(IsA(http.HttpRequest),
|
||||
filters={'property-owner_id': self.tenant.id}) \
|
||||
.AndReturn([[], False])
|
||||
api.quantum.network_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.networks.list())
|
||||
api.nova.tenant_quota_usages(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.quota_usages.first())
|
||||
api.nova.flavor_list(IsA(http.HttpRequest)) \
|
||||
@ -799,6 +815,7 @@ class InstanceTests(test.TestCase):
|
||||
'nova/instances/launch.html')
|
||||
|
||||
@test.create_stubs({api.glance: ('image_list_detailed',),
|
||||
api.quantum: ('network_list',),
|
||||
api.nova: ('flavor_list',
|
||||
'keypair_list',
|
||||
'security_group_list',
|
||||
@ -812,6 +829,7 @@ class InstanceTests(test.TestCase):
|
||||
server = self.servers.first()
|
||||
sec_group = self.security_groups.first()
|
||||
customization_script = 'userData'
|
||||
nics = [{"net-id": self.networks.first().id, "v4-fixed-ip": ''}]
|
||||
|
||||
api.nova.volume_snapshot_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.volumes.list())
|
||||
@ -825,6 +843,8 @@ class InstanceTests(test.TestCase):
|
||||
api.glance.image_list_detailed(IsA(http.HttpRequest),
|
||||
filters={'property-owner_id': self.tenant.id}) \
|
||||
.AndReturn([[], False])
|
||||
api.quantum.network_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.networks.list())
|
||||
api.nova.volume_list(IgnoreArg()).AndReturn(self.volumes.list())
|
||||
api.nova.server_create(IsA(http.HttpRequest),
|
||||
server.name,
|
||||
@ -834,6 +854,7 @@ class InstanceTests(test.TestCase):
|
||||
customization_script,
|
||||
[sec_group.name],
|
||||
None,
|
||||
nics=nics,
|
||||
instance_count=IsA(int)) \
|
||||
.AndRaise(self.exceptions.keystone)
|
||||
|
||||
@ -849,6 +870,7 @@ class InstanceTests(test.TestCase):
|
||||
'user_id': self.user.id,
|
||||
'groups': sec_group.name,
|
||||
'volume_type': '',
|
||||
'network': self.networks.first().id,
|
||||
'count': 1}
|
||||
url = reverse('horizon:nova:instances:launch')
|
||||
res = self.client.post(url, form_data)
|
||||
@ -856,6 +878,7 @@ class InstanceTests(test.TestCase):
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.create_stubs({api.glance: ('image_list_detailed',),
|
||||
api.quantum: ('network_list',),
|
||||
api.nova: ('flavor_list',
|
||||
'keypair_list',
|
||||
'security_group_list',
|
||||
@ -885,6 +908,8 @@ class InstanceTests(test.TestCase):
|
||||
api.glance.image_list_detailed(IsA(http.HttpRequest),
|
||||
filters={'property-owner_id': self.tenant.id}) \
|
||||
.AndReturn([[], False])
|
||||
api.quantum.network_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.networks.list())
|
||||
api.nova.volume_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.volumes.list())
|
||||
api.nova.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])
|
||||
|
@ -18,6 +18,8 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import logging
|
||||
|
||||
from django.utils.text import normalize_newlines
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
@ -28,6 +30,9 @@ from horizon import workflows
|
||||
from horizon.openstack.common import jsonutils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SelectProjectUserAction(workflows.Action):
|
||||
project_id = forms.ChoiceField(label=_("Project"))
|
||||
user_id = forms.ChoiceField(label=_("User"))
|
||||
@ -400,6 +405,46 @@ class PostCreationStep(workflows.Step):
|
||||
contributes = ("customization_script",)
|
||||
|
||||
|
||||
class SetNetworkAction(workflows.Action):
|
||||
network = forms.MultipleChoiceField(label=_("Networks"),
|
||||
required=True,
|
||||
widget=forms.CheckboxSelectMultiple(),
|
||||
help_text=_("Launch instance with"
|
||||
"these networks"))
|
||||
|
||||
class Meta:
|
||||
name = _("Networking")
|
||||
permissions = ('openstack.services.network',)
|
||||
help_text = _("Select networks for your instance.")
|
||||
|
||||
def populate_network_choices(self, request, context):
|
||||
try:
|
||||
networks = api.quantum.network_list(request)
|
||||
for n in networks:
|
||||
n.set_id_as_name_if_empty()
|
||||
network_list = [(network.id, network.name) for network in networks]
|
||||
except:
|
||||
network_list = []
|
||||
exceptions.handle(request,
|
||||
_('Unable to retrieve networks.'))
|
||||
return network_list
|
||||
|
||||
|
||||
class SetNetwork(workflows.Step):
|
||||
action_class = SetNetworkAction
|
||||
contributes = ("network_id",)
|
||||
|
||||
def contribute(self, data, context):
|
||||
if data:
|
||||
networks = self.workflow.request.POST.getlist("network")
|
||||
# If no networks are explicitly specified, network list
|
||||
# contains an empty string, so remove it.
|
||||
networks = [n for n in networks if n != '']
|
||||
if networks:
|
||||
context['network_id'] = networks
|
||||
return context
|
||||
|
||||
|
||||
class LaunchInstance(workflows.Workflow):
|
||||
slug = "launch_instance"
|
||||
name = _("Launch Instance")
|
||||
@ -410,6 +455,7 @@ class LaunchInstance(workflows.Workflow):
|
||||
default_steps = (SelectProjectUser,
|
||||
SetInstanceDetails,
|
||||
SetAccessControls,
|
||||
SetNetwork,
|
||||
VolumeOptions,
|
||||
PostCreationStep)
|
||||
|
||||
@ -437,6 +483,13 @@ class LaunchInstance(workflows.Workflow):
|
||||
else:
|
||||
dev_mapping = None
|
||||
|
||||
netids = context.get('network_id', None)
|
||||
if netids:
|
||||
nics = [{"net-id": netid, "v4-fixed-ip": ""}
|
||||
for netid in netids]
|
||||
else:
|
||||
nics = None
|
||||
|
||||
try:
|
||||
api.nova.server_create(request,
|
||||
context['name'],
|
||||
@ -446,6 +499,7 @@ class LaunchInstance(workflows.Workflow):
|
||||
normalize_newlines(custom_script),
|
||||
context['security_group_ids'],
|
||||
dev_mapping,
|
||||
nics=nics,
|
||||
instance_count=int(context['count']))
|
||||
return True
|
||||
except:
|
||||
|
0
horizon/dashboards/nova/networks/__init__.py
Normal file
0
horizon/dashboards/nova/networks/__init__.py
Normal file
55
horizon/dashboards/nova/networks/forms.py
Normal file
55
horizon/dashboards/nova/networks/forms.py
Normal file
@ -0,0 +1,55 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 United States Government as represented by the
|
||||
# Administrator of the National Aeronautics and Space Administration.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class UpdateNetwork(forms.SelfHandlingForm):
|
||||
name = forms.CharField(label=_("Name"), required=False)
|
||||
tenant_id = forms.CharField(widget=forms.HiddenInput)
|
||||
network_id = forms.CharField(label=_("ID"),
|
||||
widget=forms.TextInput(
|
||||
attrs={'readonly': 'readonly'}))
|
||||
failure_url = 'horizon:nova:networks:index'
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
network = api.quantum.network_modify(request, data['network_id'],
|
||||
name=data['name'])
|
||||
msg = _('Network %s was successfully updated.') % data['name']
|
||||
LOG.debug(msg)
|
||||
messages.success(request, msg)
|
||||
return network
|
||||
except:
|
||||
msg = _('Failed to update network %s') % data['name']
|
||||
LOG.info(msg)
|
||||
redirect = reverse(self.failure_url)
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
28
horizon/dashboards/nova/networks/panel.py
Normal file
28
horizon/dashboards/nova/networks/panel.py
Normal file
@ -0,0 +1,28 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
import horizon
|
||||
from horizon.dashboards.nova import dashboard
|
||||
|
||||
|
||||
class Networks(horizon.Panel):
|
||||
name = _("Networks")
|
||||
slug = 'networks'
|
||||
permissions = ('openstack.services.network',)
|
||||
|
||||
dashboard.Nova.register(Networks)
|
0
horizon/dashboards/nova/networks/ports/__init__.py
Normal file
0
horizon/dashboards/nova/networks/ports/__init__.py
Normal file
53
horizon/dashboards/nova/networks/ports/tables.py
Normal file
53
horizon/dashboards/nova/networks/ports/tables.py
Normal file
@ -0,0 +1,53 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
import logging
|
||||
|
||||
from django import template
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import tables
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_fixed_ips(port):
|
||||
template_name = 'nova/networks/ports/_port_ips.html'
|
||||
context = {"ips": port.fixed_ips}
|
||||
return template.loader.render_to_string(template_name, context)
|
||||
|
||||
|
||||
def get_attached(port):
|
||||
return _('Attached') if port['device_id'] else _('Detached')
|
||||
|
||||
|
||||
class PortsTable(tables.DataTable):
|
||||
name = tables.Column("name",
|
||||
verbose_name=_("Name"),
|
||||
link="horizon:nova:networks:ports:detail")
|
||||
fixed_ips = tables.Column(get_fixed_ips, verbose_name=_("Fixed IPs"))
|
||||
attached = tables.Column(get_attached, verbose_name=_("Device Attached"))
|
||||
status = tables.Column("status", verbose_name=_("Status"))
|
||||
admin_state = tables.Column("admin_state",
|
||||
verbose_name=_("Admin State"))
|
||||
|
||||
def get_object_display(self, port):
|
||||
return port.id
|
||||
|
||||
class Meta:
|
||||
name = "ports"
|
||||
verbose_name = _("Ports")
|
46
horizon/dashboards/nova/networks/ports/tabs.py
Normal file
46
horizon/dashboards/nova/networks/ports/tabs.py
Normal file
@ -0,0 +1,46 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import tabs
|
||||
|
||||
import logging
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OverviewTab(tabs.Tab):
|
||||
name = _("Overview")
|
||||
slug = "overview"
|
||||
template_name = "nova/networks/ports/_detail_overview.html"
|
||||
|
||||
def get_context_data(self, request):
|
||||
port_id = self.tab_group.kwargs['port_id']
|
||||
try:
|
||||
port = api.quantum.port_get(self.request, port_id)
|
||||
except:
|
||||
redirect = reverse('horizon:nova:networks:index')
|
||||
msg = _('Unable to retrieve port details.')
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
return {'port': port}
|
||||
|
||||
|
||||
class PortDetailTabs(tabs.TabGroup):
|
||||
slug = "port_details"
|
||||
tabs = (OverviewTab,)
|
24
horizon/dashboards/nova/networks/ports/urls.py
Normal file
24
horizon/dashboards/nova/networks/ports/urls.py
Normal file
@ -0,0 +1,24 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
from django.conf.urls.defaults import patterns, url
|
||||
|
||||
from .views import DetailView
|
||||
|
||||
PORTS = r'^(?P<port_id>[^/]+)/%s$'
|
||||
|
||||
urlpatterns = patterns('horizon.dashboards.nova.networks.ports.views',
|
||||
url(PORTS % 'detail', DetailView.as_view(), name='detail'))
|
28
horizon/dashboards/nova/networks/ports/views.py
Normal file
28
horizon/dashboards/nova/networks/ports/views.py
Normal file
@ -0,0 +1,28 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
import logging
|
||||
|
||||
from horizon import tabs
|
||||
from .tabs import PortDetailTabs
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DetailView(tabs.TabView):
|
||||
tab_group_class = PortDetailTabs
|
||||
template_name = 'nova/networks/ports/detail.html'
|
138
horizon/dashboards/nova/networks/subnets/forms.py
Normal file
138
horizon/dashboards/nova/networks/subnets/forms.py
Normal file
@ -0,0 +1,138 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
import logging
|
||||
import netaddr
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
from horizon import exceptions
|
||||
from horizon.utils import fields
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CreateSubnet(forms.SelfHandlingForm):
|
||||
network_name = forms.CharField(label=_("Network Name"),
|
||||
required=False,
|
||||
widget=forms.TextInput(
|
||||
attrs={'readonly': 'readonly'}))
|
||||
network_id = forms.CharField(label=_("Network ID"),
|
||||
widget=forms.TextInput(
|
||||
attrs={'readonly': 'readonly'}))
|
||||
name = forms.CharField(max_length=255,
|
||||
label=_("Name"),
|
||||
required=False)
|
||||
cidr = fields.IPField(label=_("Network Address"),
|
||||
required=True,
|
||||
initial="",
|
||||
help_text=_("Network address in CIDR format "
|
||||
"(e.g. 192.168.0.0/24)"),
|
||||
version=fields.IPv4 | fields.IPv6,
|
||||
mask=True)
|
||||
ip_version = forms.ChoiceField(choices=[(4, 'IPv4'), (6, 'IPv6')],
|
||||
label=_("IP Version"))
|
||||
gateway_ip = fields.IPField(label=_("Gateway IP"),
|
||||
required=False,
|
||||
initial="",
|
||||
help_text=_("IP address of Gateway "
|
||||
"(e.g. 192.168.0.1)"),
|
||||
version=fields.IPv4 | fields.IPv6,
|
||||
mask=False)
|
||||
failure_url = 'horizon:nova:networks:detail'
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(CreateSubnet, self).clean()
|
||||
cidr = cleaned_data.get('cidr')
|
||||
ip_version = int(cleaned_data.get('ip_version'))
|
||||
gateway_ip = cleaned_data.get('gateway_ip')
|
||||
if cidr:
|
||||
if netaddr.IPNetwork(cidr).version is not ip_version:
|
||||
msg = _('Network Address and IP version are inconsistent.')
|
||||
raise forms.ValidationError(msg)
|
||||
if gateway_ip:
|
||||
if netaddr.IPAddress(gateway_ip).version is not ip_version:
|
||||
msg = _('Gateway IP and IP version are inconsistent.')
|
||||
raise forms.ValidationError(msg)
|
||||
return cleaned_data
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
LOG.debug('params = %s' % data)
|
||||
data['ip_version'] = int(data['ip_version'])
|
||||
if not data['gateway_ip']:
|
||||
del data['gateway_ip']
|
||||
subnet = api.quantum.subnet_create(request, **data)
|
||||
msg = _('Subnet %s was successfully created.') % data['cidr']
|
||||
LOG.debug(msg)
|
||||
messages.success(request, msg)
|
||||
return subnet
|
||||
except Exception:
|
||||
msg = _('Failed to create subnet %s') % data['cidr']
|
||||
LOG.info(msg)
|
||||
redirect = reverse(self.failure_url, args=[data['network_id']])
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
|
||||
class UpdateSubnet(forms.SelfHandlingForm):
|
||||
network_id = forms.CharField(widget=forms.HiddenInput())
|
||||
subnet_id = forms.CharField(widget=forms.HiddenInput())
|
||||
cidr = forms.CharField(widget=forms.HiddenInput())
|
||||
ip_version = forms.CharField(widget=forms.HiddenInput())
|
||||
name = forms.CharField(max_length=255,
|
||||
label=_("Name"),
|
||||
required=False)
|
||||
gateway_ip = fields.IPField(label=_("Gateway IP"),
|
||||
required=True,
|
||||
initial="",
|
||||
help_text=_("IP address of Gateway "
|
||||
"(e.g. 192.168.0.1)"),
|
||||
version=fields.IPv4 | fields.IPv6,
|
||||
mask=False)
|
||||
failure_url = 'horizon:nova:networks:detail'
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(UpdateSubnet, self).clean()
|
||||
ip_version = int(cleaned_data.get('ip_version'))
|
||||
gateway_ip = cleaned_data.get('gateway_ip')
|
||||
if gateway_ip:
|
||||
if netaddr.IPAddress(gateway_ip).version is not ip_version:
|
||||
msg = _('Gateway IP and IP version are inconsistent.')
|
||||
raise forms.ValidationError(msg)
|
||||
return cleaned_data
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
LOG.debug('params = %s' % data)
|
||||
params = {'name': data['name']}
|
||||
params['gateway_ip'] = data['gateway_ip']
|
||||
subnet = api.quantum.subnet_modify(request, data['subnet_id'],
|
||||
name=data['name'],
|
||||
gateway_ip=data['gateway_ip'])
|
||||
msg = _('Subnet %s was successfully updated.') % data['cidr']
|
||||
LOG.debug(msg)
|
||||
messages.success(request, msg)
|
||||
return subnet
|
||||
except Exception:
|
||||
msg = _('Failed to update subnet %s') % data['cidr']
|
||||
LOG.info(msg)
|
||||
redirect = reverse(self.failure_url, args=[data['network_id']])
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
79
horizon/dashboards/nova/networks/subnets/tables.py
Normal file
79
horizon/dashboards/nova/networks/subnets/tables.py
Normal file
@ -0,0 +1,79 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import tables
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DeleteSubnet(tables.DeleteAction):
|
||||
data_type_singular = _("Subnet")
|
||||
data_type_plural = _("Subnets")
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
try:
|
||||
api.quantum.subnet_delete(request, obj_id)
|
||||
except:
|
||||
msg = _('Failed to delete subnet %s') % obj_id
|
||||
LOG.info(msg)
|
||||
network_id = self.table.kwargs['network_id']
|
||||
redirect = reverse('horizon:nova:networks:detail',
|
||||
args=[network_id])
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
|
||||
class CreateSubnet(tables.LinkAction):
|
||||
name = "create"
|
||||
verbose_name = _("Create Subnet")
|
||||
url = "horizon:nova:networks:addsubnet"
|
||||
classes = ("ajax-modal", "btn-create")
|
||||
|
||||
def get_link_url(self, datum=None):
|
||||
network_id = self.table.kwargs['network_id']
|
||||
return reverse(self.url, args=(network_id,))
|
||||
|
||||
|
||||
class UpdateSubnet(tables.LinkAction):
|
||||
name = "update"
|
||||
verbose_name = _("Edit Subnet")
|
||||
url = "horizon:nova:networks:editsubnet"
|
||||
classes = ("ajax-modal", "btn-edit")
|
||||
|
||||
def get_link_url(self, subnet):
|
||||
network_id = self.table.kwargs['network_id']
|
||||
return reverse(self.url, args=(network_id, subnet.id))
|
||||
|
||||
|
||||
class SubnetsTable(tables.DataTable):
|
||||
name = tables.Column("name", verbose_name=_("Name"),
|
||||
link='horizon:nova:networks:subnets:detail')
|
||||
cidr = tables.Column("cidr", verbose_name=_("Network Address"))
|
||||
ip_version = tables.Column("ipver_str", verbose_name=_("IP Version"))
|
||||
gateway_ip = tables.Column("gateway_ip", verbose_name=_("Gateway IP"))
|
||||
|
||||
class Meta:
|
||||
name = "subnets"
|
||||
verbose_name = _("Subnets")
|
||||
table_actions = (CreateSubnet, DeleteSubnet)
|
||||
row_actions = (UpdateSubnet, DeleteSubnet)
|
48
horizon/dashboards/nova/networks/subnets/tabs.py
Normal file
48
horizon/dashboards/nova/networks/subnets/tabs.py
Normal file
@ -0,0 +1,48 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import tabs
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OverviewTab(tabs.Tab):
|
||||
name = _("Overview")
|
||||
slug = "overview"
|
||||
template_name = "nova/networks/subnets/_detail_overview.html"
|
||||
|
||||
def get_context_data(self, request):
|
||||
subnet_id = self.tab_group.kwargs['subnet_id']
|
||||
try:
|
||||
subnet = api.quantum.subnet_get(self.request, subnet_id)
|
||||
except:
|
||||
redirect = reverse('horizon:nova:networks:index')
|
||||
msg = _('Unable to retrieve subnet details.')
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
return {'subnet': subnet}
|
||||
|
||||
|
||||
class SubnetDetailTabs(tabs.TabGroup):
|
||||
slug = "subnet_details"
|
||||
tabs = (OverviewTab,)
|
24
horizon/dashboards/nova/networks/subnets/urls.py
Normal file
24
horizon/dashboards/nova/networks/subnets/urls.py
Normal file
@ -0,0 +1,24 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
from django.conf.urls.defaults import patterns, url
|
||||
|
||||
from .views import DetailView
|
||||
|
||||
SUBNETS = r'^(?P<subnet_id>[^/]+)/%s$'
|
||||
|
||||
urlpatterns = patterns('horizon.dashboards.nova.networks.subnets.views',
|
||||
url(SUBNETS % 'detail', DetailView.as_view(), name='detail'))
|
109
horizon/dashboards/nova/networks/subnets/views.py
Normal file
109
horizon/dashboards/nova/networks/subnets/views.py
Normal file
@ -0,0 +1,109 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Views for managing Quantum Subnets.
|
||||
"""
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse_lazy, reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import forms
|
||||
from horizon import exceptions
|
||||
from horizon import api
|
||||
from horizon import tabs
|
||||
from .forms import CreateSubnet, UpdateSubnet
|
||||
from .tabs import SubnetDetailTabs
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CreateView(forms.ModalFormView):
|
||||
form_class = CreateSubnet
|
||||
template_name = 'nova/networks/subnets/create.html'
|
||||
success_url = 'horizon:nova:networks:detail'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_url,
|
||||
args=(self.kwargs['network_id'],))
|
||||
|
||||
def get_object(self):
|
||||
if not hasattr(self, "_object"):
|
||||
try:
|
||||
network_id = self.kwargs["network_id"]
|
||||
self._object = api.quantum.network_get(self.request,
|
||||
network_id)
|
||||
except:
|
||||
redirect = reverse('horizon:nova:networks:index')
|
||||
msg = _("Unable to retrieve network.")
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
return self._object
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(CreateView, self).get_context_data(**kwargs)
|
||||
context['network'] = self.get_object()
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
network = self.get_object()
|
||||
return {"network_id": self.kwargs['network_id'],
|
||||
"network_name": network.name}
|
||||
|
||||
|
||||
class UpdateView(forms.ModalFormView):
|
||||
form_class = UpdateSubnet
|
||||
template_name = 'nova/networks/subnets/update.html'
|
||||
context_object_name = 'subnet'
|
||||
success_url = reverse_lazy('horizon:nova:networks:detail')
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse('horizon:nova:networks:detail',
|
||||
args=(self.kwargs['network_id'],))
|
||||
|
||||
def _get_object(self, *args, **kwargs):
|
||||
if not hasattr(self, "_object"):
|
||||
subnet_id = self.kwargs['subnet_id']
|
||||
try:
|
||||
self._object = api.quantum.subnet_get(self.request, subnet_id)
|
||||
except:
|
||||
redirect = reverse("horizon:nova:networks:index")
|
||||
msg = _('Unable to retrieve subnet details')
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
return self._object
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UpdateView, self).get_context_data(**kwargs)
|
||||
subnet = self._get_object()
|
||||
context['subnet_id'] = subnet.id
|
||||
context['network_id'] = subnet.network_id
|
||||
context['cidr'] = subnet.cidr
|
||||
context['ip_version'] = subnet.ipver_str
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
subnet = self._get_object()
|
||||
return {'network_id': self.kwargs['network_id'],
|
||||
'subnet_id': subnet['id'],
|
||||
'cidr': subnet['cidr'],
|
||||
'ip_version': subnet['ip_version'],
|
||||
'name': subnet['name'],
|
||||
'gateway_ip': subnet['gateway_ip']}
|
||||
|
||||
|
||||
class DetailView(tabs.TabView):
|
||||
tab_group_class = SubnetDetailTabs
|
||||
template_name = 'nova/networks/subnets/detail.html'
|
94
horizon/dashboards/nova/networks/tables.py
Normal file
94
horizon/dashboards/nova/networks/tables.py
Normal file
@ -0,0 +1,94 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
import logging
|
||||
|
||||
from django import template
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import tables
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DeleteNetwork(tables.DeleteAction):
|
||||
data_type_singular = _("Network")
|
||||
data_type_plural = _("Networks")
|
||||
|
||||
def delete(self, request, network_id):
|
||||
try:
|
||||
# Retrieve existing subnets belonging to the network.
|
||||
subnets = api.quantum.subnet_list(request, network_id=network_id)
|
||||
LOG.debug('Network %s has subnets: %s' %
|
||||
(network_id, [s.id for s in subnets]))
|
||||
for s in subnets:
|
||||
api.quantum.subnet_delete(request, s.id)
|
||||
LOG.debug('Deleted subnet %s' % s.id)
|
||||
|
||||
api.quantum.network_delete(request, network_id)
|
||||
LOG.debug('Deleted network %s successfully' % network_id)
|
||||
except:
|
||||
msg = _('Failed to delete network %s') % network_id
|
||||
LOG.info(msg)
|
||||
redirect = reverse("horizon:nova:networks:index")
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
|
||||
class CreateNetwork(tables.LinkAction):
|
||||
name = "create"
|
||||
verbose_name = _("Create Network")
|
||||
url = "horizon:nova:networks:create"
|
||||
classes = ("ajax-modal", "btn-create")
|
||||
|
||||
|
||||
class EditNetwork(tables.LinkAction):
|
||||
name = "update"
|
||||
verbose_name = _("Edit Network")
|
||||
url = "horizon:nova:networks:update"
|
||||
classes = ("ajax-modal", "btn-edit")
|
||||
|
||||
|
||||
class CreateSubnet(tables.LinkAction):
|
||||
name = "subnet"
|
||||
verbose_name = _("Add Subnet")
|
||||
url = "horizon:nova:networks:addsubnet"
|
||||
classes = ("ajax-modal", "btn-create")
|
||||
|
||||
|
||||
def get_subnets(network):
|
||||
template_name = 'nova/networks/_network_ips.html'
|
||||
context = {"subnets": network.subnets}
|
||||
return template.loader.render_to_string(template_name, context)
|
||||
|
||||
|
||||
class NetworksTable(tables.DataTable):
|
||||
name = tables.Column("name",
|
||||
verbose_name=_("Name"),
|
||||
link='horizon:nova:networks:detail')
|
||||
subnets = tables.Column(get_subnets,
|
||||
verbose_name=_("Subnets Associated"),)
|
||||
status = tables.Column("status", verbose_name=_("Status"))
|
||||
admin_state = tables.Column("admin_state",
|
||||
verbose_name=_("Admin State"))
|
||||
|
||||
class Meta:
|
||||
name = "networks"
|
||||
verbose_name = _("Networks")
|
||||
table_actions = (CreateNetwork, DeleteNetwork)
|
||||
row_actions = (EditNetwork, CreateSubnet, DeleteNetwork)
|
@ -0,0 +1,24 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form_id %}create_network_form{% endblock %}
|
||||
{% block form_action %}{% url horizon:nova:networks:create %}{% endblock %}
|
||||
|
||||
{% block modal-header %}{% trans "Create Network" %}{% 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."%}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Create Network" %}" />
|
||||
<a href="{% url horizon:nova:networks:index %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
@ -0,0 +1,18 @@
|
||||
{% load i18n sizeformat %}
|
||||
|
||||
<h3>{% trans "Network Overview" %}</h3>
|
||||
|
||||
<div class="info detail">
|
||||
<dl>
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>{{ network.name|default:"None" }}</dd>
|
||||
<dt>{% trans "ID" %}</dt>
|
||||
<dd>{{ network.id|default:"None" }}</dd>
|
||||
<dt>{% trans "Project ID" %}</dt>
|
||||
<dd>{{ network.tenant_id|default:"-" }}</dd>
|
||||
<dt>{% trans "Status" %}</dt>
|
||||
<dd>{{ network.status|default:"Unknown" }}</dd>
|
||||
<dt>{% trans "Admin State" %}</dt>
|
||||
<dd>{{ network.admin_state|default:"Unknown" }}</dd>
|
||||
</dl>
|
||||
</div>
|
@ -0,0 +1,10 @@
|
||||
{% for subnet in subnets %}
|
||||
<ul>
|
||||
<li>
|
||||
{% if subnet.name|length > 0 %}
|
||||
<b>{{ subnet.name }}</b>
|
||||
{% endif %}
|
||||
{{ subnet.cidr }}
|
||||
</li>
|
||||
</ul>
|
||||
{% endfor %}
|
@ -0,0 +1,24 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form_id %}update_network_form{% endblock %}
|
||||
{% block form_action %}{% url horizon:nova:networks:update network_id %}{% endblock %}
|
||||
|
||||
{% block modal-header %}{% trans "Edit Network" %}{% 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 here." %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Save Changes" %}" />
|
||||
<a href="{% url horizon:nova:networks:index %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
@ -0,0 +1,11 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Create Network" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Create Network") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include "horizon/common/_workflow.html" %}
|
||||
{% endblock %}
|
@ -0,0 +1,18 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Network Detail"%}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Network Detail: ")|add:network.name %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include "nova/networks/_detail_overview.html" %}
|
||||
<hr>
|
||||
<div id="subnets">
|
||||
{{ subnets_table.render }}
|
||||
</div>
|
||||
<div id="ports">
|
||||
{{ ports_table.render }}
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,11 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Networks" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Networks") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
@ -0,0 +1,41 @@
|
||||
{% load i18n sizeformat %}
|
||||
|
||||
<h3>{% trans "Port Overview" %}</h3>
|
||||
|
||||
<div class="info row-fluid detail">
|
||||
<h4>{% trans "Port" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<dl>
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>{{ port.name|default:"None" }}</dd>
|
||||
<dt>{% trans "ID" %}</dt>
|
||||
<dd>{{ port.id|default:"None" }}</dd>
|
||||
<dt>{% trans "Network ID" %}</dt>
|
||||
<dd>{{ port.network_id|default:"None" }}</dd>
|
||||
<dt>{% trans "Project ID" %}</dt>
|
||||
<dd>{{ port.tenant_id|default:"-" }}</dd>
|
||||
<dt>{% trans "Fixed IP" %}</dt>
|
||||
<dd>
|
||||
{% if port.fixed_ips.items|length > 1 %}
|
||||
{% for ip in port.fixed_ips %}
|
||||
<b>{% trans "IP address:" %}</b> {{ ip.ip_address }},
|
||||
<b>{% trans "Subnet ID" %}</b> {{ ip.subnet_id }}<br>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
"None"
|
||||
{% endif %}
|
||||
</dd>
|
||||
<dt>{% trans "Mac Address" %}</dt>
|
||||
<dd>{{ port.mac_address|default:"None" }}</dd>
|
||||
<dt>{% trans "Status" %}</dt>
|
||||
<dd>{{ port.status|default:"None" }}</dd>
|
||||
<dt>{% trans "Admin State" %}</dt>
|
||||
<dd>{{ port.admin_state|default:"None" }}</dd>
|
||||
<dt>{% trans "Device ID" %}</dt>
|
||||
{% if port.device_id|length > 1 %}
|
||||
<dd>{{ port.device_id }}</dd>
|
||||
{% else %}
|
||||
<dd>No attached device</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
</div>
|
@ -0,0 +1,7 @@
|
||||
{% for ip in ips %}
|
||||
<ul>
|
||||
<li>
|
||||
{{ ip.ip_address }}
|
||||
</li>
|
||||
</ul>
|
||||
{% endfor %}
|
@ -0,0 +1,15 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Port Detail"%}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Port Detail") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
<div id="row-fluid">
|
||||
<div class="span12">
|
||||
{{ tab_group.render }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,25 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form_id %}create_subnet_form{% endblock %}
|
||||
{% block form_action %}{% url horizon:nova:networks:addsubnet network.id %}
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-header %}{% trans "Create Subnet" %}{% 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 can create a subnet for the network. Any network address can be specified unless the network address does not overlap other subnets in the network." %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Create Subnet" %}" />
|
||||
<a href="{% url horizon:nova:networks:index %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
@ -0,0 +1,29 @@
|
||||
{% load i18n sizeformat %}
|
||||
|
||||
<h3>{% trans "Subnet Overview" %}</h3>
|
||||
|
||||
<div class="info row-fluid detail">
|
||||
<h4>{% trans "Subnet" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<dl>
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>{{ subnet.name|default:"None" }}</dd>
|
||||
<dt>{% trans "ID" %}</dt>
|
||||
<dd>{{ subnet.id|default:"None" }}</dd>
|
||||
<dt>{% trans "Network ID" %}</dt>
|
||||
<dd>{{ subnet.network_id|default:"None" }}</dd>
|
||||
<dt>{% trans "CIDR" %}</dt>
|
||||
<dd>{{ subnet.cidr|default:"None" }}</dd>
|
||||
<dt>{% trans "IP version" %}</dt>
|
||||
<dd>{{ subnet.ipver_str|default:"-" }}</dd>
|
||||
<dt>{% trans "Gateway IP" %}</dt>
|
||||
<dd>{{ subnet.gateway_ip|default:"-" }}</dd>
|
||||
<dt>{% trans "IP allocation pool" %}</dt>
|
||||
<dd>
|
||||
{% for pool in subnet.allocation_pools %}
|
||||
{% trans "Start" %} {{ pool.start }}
|
||||
{% trans " - End" %} {{ pool.end }}<br>
|
||||
{% endfor %}
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
@ -0,0 +1,33 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form_id %}update_subnet_form{% endblock %}
|
||||
{% block form_action %}{% url horizon:nova:networks:editsubnet network_id subnet_id %}{% endblock %}
|
||||
|
||||
{% block modal-header %}{% trans "Edit Subnet" %}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="left">
|
||||
<dl>
|
||||
<dt>{% trans "ID" %}</dt>
|
||||
<dd>{{ subnet_id }}</dd>
|
||||
<dt>{% trans "Network Address" %}</dt>
|
||||
<dd>{{ cidr }}</dd>
|
||||
<dt>{% trans "IP version" %}</dt>
|
||||
<dd>{{ ip_version }}</dd>
|
||||
</dl>
|
||||
<hr>
|
||||
<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 subnet here." %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Save Changes" %}" />
|
||||
<a href="{% url horizon:nova:networks:detail network_id %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
@ -0,0 +1,11 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Create Subnet" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Create Subnet") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include "nova/networks/subnets/_create.html" %}
|
||||
{% endblock %}
|
@ -0,0 +1,15 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Subnet Detail"%}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Subnet Detail") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
<div id="row-fluid">
|
||||
<div class="span12">
|
||||
{{ tab_group.render }}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,11 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Network" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Network") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
@ -0,0 +1,11 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Update Subnet" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Update Subnet") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'nova/networks/subnets/_update.html' %}
|
||||
{% endblock %}
|
@ -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") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'nova/networks/_update.html' %}
|
||||
{% endblock %}
|
753
horizon/dashboards/nova/networks/tests.py
Normal file
753
horizon/dashboards/nova/networks/tests.py
Normal file
@ -0,0 +1,753 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
from django import http
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.http import urlencode
|
||||
from django.utils.html import escape
|
||||
from django.utils.datastructures import SortedDict
|
||||
from mox import IsA, IgnoreArg
|
||||
from copy import deepcopy
|
||||
|
||||
from horizon import api
|
||||
from horizon import test
|
||||
|
||||
from .workflows import CreateNetwork
|
||||
|
||||
|
||||
INDEX_URL = reverse('horizon:nova:networks:index')
|
||||
|
||||
|
||||
class NetworkTests(test.TestCase):
|
||||
@test.create_stubs({api.quantum: ('network_list',)})
|
||||
def test_index(self):
|
||||
api.quantum.network_list(IsA(http.HttpRequest),
|
||||
tenant_id=self.tenant.id) \
|
||||
.AndReturn(self.networks.list())
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(INDEX_URL)
|
||||
|
||||
self.assertTemplateUsed(res, 'nova/networks/index.html')
|
||||
networks = res.context['networks_table'].data
|
||||
self.assertItemsEqual(networks, self.networks.list())
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_list',)})
|
||||
def test_index_network_list_exception(self):
|
||||
api.quantum.network_list(IsA(http.HttpRequest),
|
||||
tenant_id=self.tenant.id) \
|
||||
.AndRaise(self.exceptions.quantum)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(INDEX_URL)
|
||||
|
||||
self.assertTemplateUsed(res, 'nova/networks/index.html')
|
||||
self.assertEqual(len(res.context['networks_table'].data), 0)
|
||||
self.assertMessageCount(res, error=1)
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_get',
|
||||
'subnet_list',
|
||||
'port_list',)})
|
||||
def test_network_detail(self):
|
||||
network_id = self.networks.first().id
|
||||
api.quantum.network_get(IsA(http.HttpRequest), network_id)\
|
||||
.AndReturn(self.networks.first())
|
||||
api.quantum.subnet_list(IsA(http.HttpRequest), network_id=network_id)\
|
||||
.AndReturn([self.subnets.first()])
|
||||
api.quantum.port_list(IsA(http.HttpRequest), network_id=network_id)\
|
||||
.AndReturn([self.ports.first()])
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('horizon:nova:networks:detail',
|
||||
args=[network_id]))
|
||||
|
||||
self.assertTemplateUsed(res, 'nova/networks/detail.html')
|
||||
subnets = res.context['subnets_table'].data
|
||||
ports = res.context['ports_table'].data
|
||||
self.assertItemsEqual(subnets, [self.subnets.first()])
|
||||
self.assertItemsEqual(ports, [self.ports.first()])
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_get',
|
||||
'subnet_list',
|
||||
'port_list',)})
|
||||
def test_network_detail_network_exception(self):
|
||||
network_id = self.networks.first().id
|
||||
api.quantum.network_get(IsA(http.HttpRequest), network_id)\
|
||||
.AndRaise(self.exceptions.quantum)
|
||||
api.quantum.subnet_list(IsA(http.HttpRequest), network_id=network_id)\
|
||||
.AndReturn([self.subnets.first()])
|
||||
api.quantum.port_list(IsA(http.HttpRequest), network_id=network_id)\
|
||||
.AndReturn([self.ports.first()])
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('horizon:nova:networks:detail', args=[network_id])
|
||||
res = self.client.get(url)
|
||||
|
||||
redir_url = INDEX_URL
|
||||
self.assertRedirectsNoFollow(res, redir_url)
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_get',
|
||||
'subnet_list',
|
||||
'port_list',)})
|
||||
def test_network_detail_subnet_exception(self):
|
||||
network_id = self.networks.first().id
|
||||
api.quantum.network_get(IsA(http.HttpRequest), network_id).\
|
||||
AndReturn(self.networks.first())
|
||||
api.quantum.subnet_list(IsA(http.HttpRequest), network_id=network_id).\
|
||||
AndRaise(self.exceptions.quantum)
|
||||
api.quantum.port_list(IsA(http.HttpRequest), network_id=network_id).\
|
||||
AndReturn([self.ports.first()])
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('horizon:nova:networks:detail',
|
||||
args=[network_id]))
|
||||
|
||||
self.assertTemplateUsed(res, 'nova/networks/detail.html')
|
||||
subnets = res.context['subnets_table'].data
|
||||
ports = res.context['ports_table'].data
|
||||
self.assertEqual(len(subnets), 0)
|
||||
self.assertItemsEqual(ports, [self.ports.first()])
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_get',
|
||||
'subnet_list',
|
||||
'port_list',)})
|
||||
def test_network_detail_port_exception(self):
|
||||
network_id = self.networks.first().id
|
||||
api.quantum.network_get(IsA(http.HttpRequest), network_id).\
|
||||
AndReturn(self.networks.first())
|
||||
api.quantum.subnet_list(IsA(http.HttpRequest), network_id=network_id).\
|
||||
AndReturn([self.subnets.first()])
|
||||
api.quantum.port_list(IsA(http.HttpRequest), network_id=network_id).\
|
||||
AndRaise(self.exceptions.quantum)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('horizon:nova:networks:detail',
|
||||
args=[network_id]))
|
||||
|
||||
self.assertTemplateUsed(res, 'nova/networks/detail.html')
|
||||
subnets = res.context['subnets_table'].data
|
||||
ports = res.context['ports_table'].data
|
||||
self.assertItemsEqual(subnets, [self.subnets.first()])
|
||||
self.assertEqual(len(ports), 0)
|
||||
|
||||
def test_network_create_get(self):
|
||||
# no api methods are called.
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('horizon:nova:networks:create')
|
||||
res = self.client.get(url)
|
||||
|
||||
workflow = res.context['workflow']
|
||||
self.assertTemplateUsed(res, 'nova/networks/create.html')
|
||||
self.assertEqual(workflow.name, CreateNetwork.name)
|
||||
expected_objs = ['<CreateNetworkInfo: createnetworkinfoaction>',
|
||||
'<CreateSubnetInfo: createsubnetinfoaction>']
|
||||
self.assertQuerysetEqual(workflow.steps, expected_objs)
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_create',)})
|
||||
def test_network_create_post(self):
|
||||
network = self.networks.first()
|
||||
api.quantum.network_create(IsA(http.HttpRequest), name=network.name)\
|
||||
.AndReturn(network)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'net_name': network.name,
|
||||
'with_subnet': False,
|
||||
'subnet_name': '',
|
||||
'cidr': '',
|
||||
'ip_version': 4,
|
||||
'gateway_ip': ''}
|
||||
url = reverse('horizon:nova:networks:create')
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_create',
|
||||
'subnet_create',)})
|
||||
def test_network_create_post_with_subnet(self):
|
||||
network = self.networks.first()
|
||||
subnet = self.subnets.first()
|
||||
api.quantum.network_create(IsA(http.HttpRequest), name=network.name)\
|
||||
.AndReturn(network)
|
||||
api.quantum.subnet_create(IsA(http.HttpRequest),
|
||||
network_id=network.id,
|
||||
name=subnet.name,
|
||||
cidr=subnet.cidr,
|
||||
ip_version=subnet.ip_version,
|
||||
gateway_ip=subnet.gateway_ip)\
|
||||
.AndReturn(subnet)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'net_name': network.name,
|
||||
'with_subnet': True,
|
||||
'subnet_name': subnet.name,
|
||||
'cidr': subnet.cidr,
|
||||
'ip_version': subnet.ip_version,
|
||||
'gateway_ip': subnet.gateway_ip}
|
||||
url = reverse('horizon:nova:networks:create')
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_create',)})
|
||||
def test_network_create_post_network_exception(self):
|
||||
network = self.networks.first()
|
||||
api.quantum.network_create(IsA(http.HttpRequest), name=network.name)\
|
||||
.AndRaise(self.exceptions.quantum)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'net_name': network.name,
|
||||
'with_subnet': False,
|
||||
'subnet_name': '',
|
||||
'cidr': '',
|
||||
'ip_version': 4,
|
||||
'gateway_ip': ''}
|
||||
url = reverse('horizon:nova:networks:create')
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_create',)})
|
||||
def test_network_create_post_with_subnet_network_exception(self):
|
||||
network = self.networks.first()
|
||||
subnet = self.subnets.first()
|
||||
api.quantum.network_create(IsA(http.HttpRequest), name=network.name)\
|
||||
.AndRaise(self.exceptions.quantum)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'net_name': network.name,
|
||||
'with_subnet': True,
|
||||
'subnet_name': subnet.name,
|
||||
'cidr': subnet.cidr,
|
||||
'ip_version': subnet.ip_version,
|
||||
'gateway_ip': subnet.gateway_ip}
|
||||
url = reverse('horizon:nova:networks:create')
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_create',
|
||||
'subnet_create',)})
|
||||
def test_network_create_post_with_subnet_subnet_exception(self):
|
||||
network = self.networks.first()
|
||||
subnet = self.subnets.first()
|
||||
api.quantum.network_create(IsA(http.HttpRequest), name=network.name)\
|
||||
.AndReturn(network)
|
||||
api.quantum.subnet_create(IsA(http.HttpRequest),
|
||||
network_id=network.id,
|
||||
name=subnet.name,
|
||||
cidr=subnet.cidr,
|
||||
ip_version=subnet.ip_version,
|
||||
gateway_ip=subnet.gateway_ip)\
|
||||
.AndRaise(self.exceptions.quantum)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'net_name': network.name,
|
||||
'with_subnet': True,
|
||||
'subnet_name': subnet.name,
|
||||
'cidr': subnet.cidr,
|
||||
'ip_version': subnet.ip_version,
|
||||
'gateway_ip': subnet.gateway_ip}
|
||||
url = reverse('horizon:nova:networks:create')
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
def test_network_create_post_with_subnet_nocidr(self):
|
||||
network = self.networks.first()
|
||||
subnet = self.subnets.first()
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'net_name': network.name,
|
||||
'with_subnet': True,
|
||||
'subnet_name': subnet.name,
|
||||
'cidr': '',
|
||||
'ip_version': subnet.ip_version,
|
||||
'gateway_ip': subnet.gateway_ip}
|
||||
url = reverse('horizon:nova:networks:create')
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
self.assertContains(res, escape('Specify "Network Address" or '
|
||||
'clear "Create Subnet" checkbox.'))
|
||||
|
||||
def test_network_create_post_with_subnet_cidr_inconsistent(self):
|
||||
network = self.networks.first()
|
||||
subnet = self.subnets.first()
|
||||
self.mox.ReplayAll()
|
||||
|
||||
# dummy IPv6 address
|
||||
cidr = '2001:0DB8:0:CD30:123:4567:89AB:CDEF/60'
|
||||
form_data = {'net_name': network.name,
|
||||
'with_subnet': True,
|
||||
'subnet_name': subnet.name,
|
||||
'cidr': cidr,
|
||||
'ip_version': subnet.ip_version,
|
||||
'gateway_ip': subnet.gateway_ip}
|
||||
url = reverse('horizon:nova:networks:create')
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
expected_msg = 'Network Address and IP version are inconsistent.'
|
||||
self.assertContains(res, expected_msg)
|
||||
|
||||
def test_network_create_post_with_subnet_gw_inconsistent(self):
|
||||
network = self.networks.first()
|
||||
subnet = self.subnets.first()
|
||||
self.mox.ReplayAll()
|
||||
|
||||
# dummy IPv6 address
|
||||
gateway_ip = '2001:0DB8:0:CD30:123:4567:89AB:CDEF'
|
||||
form_data = {'net_name': network.name,
|
||||
'with_subnet': True,
|
||||
'subnet_name': subnet.name,
|
||||
'cidr': subnet.cidr,
|
||||
'ip_version': subnet.ip_version,
|
||||
'gateway_ip': gateway_ip}
|
||||
url = reverse('horizon:nova:networks:create')
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
self.assertContains(res, 'Gateway IP and IP version are inconsistent.')
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_get',)})
|
||||
def test_network_update_get(self):
|
||||
network = self.networks.first()
|
||||
api.quantum.network_get(IsA(http.HttpRequest), network.id)\
|
||||
.AndReturn(network)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('horizon:nova:networks:update', args=[network.id])
|
||||
res = self.client.get(url)
|
||||
|
||||
self.assertTemplateUsed(res, 'nova/networks/update.html')
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_get',)})
|
||||
def test_network_update_get_exception(self):
|
||||
network = self.networks.first()
|
||||
api.quantum.network_get(IsA(http.HttpRequest), network.id)\
|
||||
.AndRaise(self.exceptions.quantum)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('horizon:nova:networks:update', args=[network.id])
|
||||
res = self.client.get(url)
|
||||
|
||||
redir_url = INDEX_URL
|
||||
self.assertRedirectsNoFollow(res, redir_url)
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_modify',
|
||||
'network_get',)})
|
||||
def test_network_update_post(self):
|
||||
network = self.networks.first()
|
||||
api.quantum.network_modify(IsA(http.HttpRequest), network.id,
|
||||
name=network.name)\
|
||||
.AndReturn(network)
|
||||
api.quantum.network_get(IsA(http.HttpRequest), network.id)\
|
||||
.AndReturn(network)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
formData = {'network_id': network.id,
|
||||
'name': network.name,
|
||||
'tenant_id': network.tenant_id}
|
||||
url = reverse('horizon:nova:networks:update', args=[network.id])
|
||||
res = self.client.post(url, formData)
|
||||
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_modify',
|
||||
'network_get',)})
|
||||
def test_network_update_post_exception(self):
|
||||
network = self.networks.first()
|
||||
api.quantum.network_modify(IsA(http.HttpRequest), network.id,
|
||||
name=network.name)\
|
||||
.AndRaise(self.exceptions.quantum)
|
||||
api.quantum.network_get(IsA(http.HttpRequest), network.id)\
|
||||
.AndReturn(network)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'network_id': network.id,
|
||||
'name': network.name,
|
||||
'tenant_id': network.tenant_id}
|
||||
url = reverse('horizon:nova:networks:update', args=[network.id])
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_list',
|
||||
'subnet_list',
|
||||
'network_delete')})
|
||||
def test_delete_network_no_subnet(self):
|
||||
network = self.networks.first()
|
||||
api.quantum.network_list(IsA(http.HttpRequest),
|
||||
tenant_id=network.tenant_id)\
|
||||
.AndReturn([network])
|
||||
api.quantum.subnet_list(IsA(http.HttpRequest), network_id=network.id)\
|
||||
.AndReturn([])
|
||||
api.quantum.network_delete(IsA(http.HttpRequest), network.id)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'action': 'networks__delete__%s' % network.id}
|
||||
res = self.client.post(INDEX_URL, form_data)
|
||||
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_list',
|
||||
'subnet_list',
|
||||
'network_delete',
|
||||
'subnet_delete')})
|
||||
def test_delete_network_with_subnet(self):
|
||||
network = self.networks.first()
|
||||
subnet = self.subnets.first()
|
||||
api.quantum.network_list(IsA(http.HttpRequest),
|
||||
tenant_id=network.tenant_id)\
|
||||
.AndReturn([network])
|
||||
api.quantum.subnet_list(IsA(http.HttpRequest), network_id=network.id)\
|
||||
.AndReturn([subnet])
|
||||
api.quantum.subnet_delete(IsA(http.HttpRequest), subnet.id)
|
||||
api.quantum.network_delete(IsA(http.HttpRequest), network.id)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'action': 'networks__delete__%s' % network.id}
|
||||
res = self.client.post(INDEX_URL, form_data)
|
||||
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_list',
|
||||
'subnet_list',
|
||||
'network_delete',
|
||||
'subnet_delete')})
|
||||
def test_delete_network_exception(self):
|
||||
network = self.networks.first()
|
||||
subnet = self.subnets.first()
|
||||
api.quantum.network_list(IsA(http.HttpRequest),
|
||||
tenant_id=network.tenant_id)\
|
||||
.AndReturn([network])
|
||||
api.quantum.subnet_list(IsA(http.HttpRequest), network_id=network.id)\
|
||||
.AndReturn([subnet])
|
||||
api.quantum.subnet_delete(IsA(http.HttpRequest), subnet.id)
|
||||
api.quantum.network_delete(IsA(http.HttpRequest), network.id)\
|
||||
.AndRaise(self.exceptions.quantum)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'action': 'networks__delete__%s' % network.id}
|
||||
res = self.client.post(INDEX_URL, form_data)
|
||||
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.create_stubs({api.quantum: ('subnet_get',)})
|
||||
def test_subnet_detail(self):
|
||||
subnet = self.subnets.first()
|
||||
api.quantum.subnet_get(IsA(http.HttpRequest), subnet.id)\
|
||||
.AndReturn(self.subnets.first())
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('horizon:nova:networks:subnets:detail',
|
||||
args=[subnet.id]))
|
||||
|
||||
self.assertTemplateUsed(res, 'nova/networks/subnets/detail.html')
|
||||
self.assertEqual(res.context['subnet'].id, subnet.id)
|
||||
|
||||
@test.create_stubs({api.quantum: ('subnet_get',)})
|
||||
def test_subnet_detail_exception(self):
|
||||
subnet = self.subnets.first()
|
||||
api.quantum.subnet_get(IsA(http.HttpRequest), subnet.id)\
|
||||
.AndRaise(self.exceptions.quantum)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('horizon:nova:networks:subnets:detail',
|
||||
args=[subnet.id]))
|
||||
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_get',)})
|
||||
def test_subnet_create_get(self):
|
||||
network = self.networks.first()
|
||||
api.quantum.network_get(IsA(http.HttpRequest),
|
||||
network.id)\
|
||||
.AndReturn(self.networks.first())
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('horizon:nova:networks:addsubnet',
|
||||
args=[network.id])
|
||||
res = self.client.get(url)
|
||||
|
||||
self.assertTemplateUsed(res, 'nova/networks/subnets/create.html')
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_get',
|
||||
'subnet_create',)})
|
||||
def test_subnet_create_post(self):
|
||||
network = self.networks.first()
|
||||
subnet = self.subnets.first()
|
||||
api.quantum.network_get(IsA(http.HttpRequest),
|
||||
network.id)\
|
||||
.AndReturn(self.networks.first())
|
||||
api.quantum.subnet_create(IsA(http.HttpRequest),
|
||||
network_id=network.id,
|
||||
network_name=network.name,
|
||||
name=subnet.name,
|
||||
cidr=subnet.cidr,
|
||||
ip_version=subnet.ip_version,
|
||||
gateway_ip=subnet.gateway_ip)\
|
||||
.AndReturn(subnet)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'network_id': subnet.network_id,
|
||||
'network_name': network.name,
|
||||
'name': subnet.name,
|
||||
'cidr': subnet.cidr,
|
||||
'ip_version': subnet.ip_version,
|
||||
'gateway_ip': subnet.gateway_ip}
|
||||
url = reverse('horizon:nova:networks:addsubnet',
|
||||
args=[subnet.network_id])
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
self.assertNoFormErrors(res)
|
||||
redir_url = reverse('horizon:nova:networks:detail',
|
||||
args=[subnet.network_id])
|
||||
self.assertRedirectsNoFollow(res, redir_url)
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_get',
|
||||
'subnet_create',)})
|
||||
def test_subnet_create_post_network_exception(self):
|
||||
network = self.networks.first()
|
||||
subnet = self.subnets.first()
|
||||
api.quantum.network_get(IsA(http.HttpRequest),
|
||||
network.id)\
|
||||
.AndRaise(self.exceptions.quantum)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'network_id': subnet.network_id,
|
||||
'network_name': network.name,
|
||||
'name': subnet.name,
|
||||
'cidr': subnet.cidr,
|
||||
'ip_version': subnet.ip_version,
|
||||
'gateway_ip': subnet.gateway_ip}
|
||||
url = reverse('horizon:nova:networks:addsubnet',
|
||||
args=[subnet.network_id])
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_get',
|
||||
'subnet_create',)})
|
||||
def test_subnet_create_post_subnet_exception(self):
|
||||
network = self.networks.first()
|
||||
subnet = self.subnets.first()
|
||||
api.quantum.network_get(IsA(http.HttpRequest),
|
||||
network.id)\
|
||||
.AndReturn(self.networks.first())
|
||||
api.quantum.subnet_create(IsA(http.HttpRequest),
|
||||
network_id=network.id,
|
||||
network_name=network.name,
|
||||
name=subnet.name,
|
||||
cidr=subnet.cidr,
|
||||
ip_version=subnet.ip_version,
|
||||
gateway_ip=subnet.gateway_ip)\
|
||||
.AndRaise(self.exceptions.quantum)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'network_id': subnet.network_id,
|
||||
'network_name': network.name,
|
||||
'name': subnet.name,
|
||||
'cidr': subnet.cidr,
|
||||
'ip_version': subnet.ip_version,
|
||||
'gateway_ip': subnet.gateway_ip}
|
||||
url = reverse('horizon:nova:networks:addsubnet',
|
||||
args=[subnet.network_id])
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
redir_url = reverse('horizon:nova:networks:detail',
|
||||
args=[subnet.network_id])
|
||||
self.assertRedirectsNoFollow(res, redir_url)
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_get',)})
|
||||
def test_subnet_create_post_cidr_inconsistent(self):
|
||||
network = self.networks.first()
|
||||
subnet = self.subnets.first()
|
||||
api.quantum.network_get(IsA(http.HttpRequest),
|
||||
network.id)\
|
||||
.AndReturn(self.networks.first())
|
||||
self.mox.ReplayAll()
|
||||
|
||||
# dummy IPv6 address
|
||||
cidr = '2001:0DB8:0:CD30:123:4567:89AB:CDEF/60'
|
||||
form_data = {'network_id': subnet.network_id,
|
||||
'network_name': network.name,
|
||||
'name': subnet.name,
|
||||
'cidr': cidr,
|
||||
'ip_version': subnet.ip_version,
|
||||
'gateway_ip': subnet.gateway_ip}
|
||||
url = reverse('horizon:nova:networks:addsubnet',
|
||||
args=[subnet.network_id])
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
expected_msg = 'Network Address and IP version are inconsistent.'
|
||||
self.assertContains(res, expected_msg)
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_get',)})
|
||||
def test_subnet_create_post_gw_inconsistent(self):
|
||||
network = self.networks.first()
|
||||
subnet = self.subnets.first()
|
||||
api.quantum.network_get(IsA(http.HttpRequest),
|
||||
network.id)\
|
||||
.AndReturn(self.networks.first())
|
||||
self.mox.ReplayAll()
|
||||
|
||||
# dummy IPv6 address
|
||||
gateway_ip = '2001:0DB8:0:CD30:123:4567:89AB:CDEF'
|
||||
form_data = {'network_id': subnet.network_id,
|
||||
'network_name': network.name,
|
||||
'name': subnet.name,
|
||||
'cidr': subnet.cidr,
|
||||
'ip_version': subnet.ip_version,
|
||||
'gateway_ip': gateway_ip}
|
||||
url = reverse('horizon:nova:networks:addsubnet',
|
||||
args=[subnet.network_id])
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
self.assertContains(res, 'Gateway IP and IP version are inconsistent.')
|
||||
|
||||
@test.create_stubs({api.quantum: ('subnet_modify',
|
||||
'subnet_get',)})
|
||||
def test_subnet_update_post(self):
|
||||
subnet = self.subnets.first()
|
||||
api.quantum.subnet_get(IsA(http.HttpRequest), subnet.id)\
|
||||
.AndReturn(subnet)
|
||||
api.quantum.subnet_modify(IsA(http.HttpRequest), subnet.id,
|
||||
name=subnet.name,
|
||||
gateway_ip=subnet.gateway_ip)\
|
||||
.AndReturn(subnet)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
formData = {'network_id': subnet.network_id,
|
||||
'subnet_id': subnet.id,
|
||||
'name': subnet.name,
|
||||
'cidr': subnet.cidr,
|
||||
'ip_version': subnet.ip_version,
|
||||
'gateway_ip': subnet.gateway_ip}
|
||||
url = reverse('horizon:nova:networks:editsubnet',
|
||||
args=[subnet.network_id, subnet.id])
|
||||
res = self.client.post(url, formData)
|
||||
|
||||
redir_url = reverse('horizon:nova:networks:detail',
|
||||
args=[subnet.network_id])
|
||||
self.assertRedirectsNoFollow(res, redir_url)
|
||||
|
||||
@test.create_stubs({api.quantum: ('subnet_modify',
|
||||
'subnet_get',)})
|
||||
def test_subnet_update_post_gw_inconsistent(self):
|
||||
subnet = self.subnets.first()
|
||||
api.quantum.subnet_get(IsA(http.HttpRequest), subnet.id)\
|
||||
.AndReturn(subnet)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
# dummy IPv6 address
|
||||
gateway_ip = '2001:0DB8:0:CD30:123:4567:89AB:CDEF'
|
||||
formData = {'network_id': subnet.network_id,
|
||||
'subnet_id': subnet.id,
|
||||
'name': subnet.name,
|
||||
'cidr': subnet.cidr,
|
||||
'ip_version': subnet.ip_version,
|
||||
'gateway_ip': gateway_ip}
|
||||
url = reverse('horizon:nova:networks:editsubnet',
|
||||
args=[subnet.network_id, subnet.id])
|
||||
res = self.client.post(url, formData)
|
||||
|
||||
self.assertContains(res, 'Gateway IP and IP version are inconsistent.')
|
||||
|
||||
@test.create_stubs({api.quantum: ('subnet_delete',
|
||||
'subnet_list',
|
||||
'port_list',)})
|
||||
def test_subnet_delete(self):
|
||||
subnet = self.subnets.first()
|
||||
network_id = subnet.network_id
|
||||
api.quantum.subnet_delete(IsA(http.HttpRequest), subnet.id)
|
||||
api.quantum.subnet_list(IsA(http.HttpRequest), network_id=network_id)\
|
||||
.AndReturn([self.subnets.first()])
|
||||
api.quantum.port_list(IsA(http.HttpRequest), network_id=network_id)\
|
||||
.AndReturn([self.ports.first()])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
formData = {'action': 'subnets__delete__%s' % subnet.id}
|
||||
url = reverse('horizon:nova:networks:detail',
|
||||
args=[network_id])
|
||||
res = self.client.post(url, formData)
|
||||
|
||||
self.assertRedirectsNoFollow(res, url)
|
||||
|
||||
@test.create_stubs({api.quantum: ('subnet_delete',
|
||||
'subnet_list',
|
||||
'port_list',)})
|
||||
def test_subnet_delete_exception(self):
|
||||
subnet = self.subnets.first()
|
||||
network_id = subnet.network_id
|
||||
api.quantum.subnet_delete(IsA(http.HttpRequest), subnet.id)\
|
||||
.AndRaise(self.exceptions.quantum)
|
||||
api.quantum.subnet_list(IsA(http.HttpRequest), network_id=network_id)\
|
||||
.AndReturn([self.subnets.first()])
|
||||
api.quantum.port_list(IsA(http.HttpRequest), network_id=network_id)\
|
||||
.AndReturn([self.ports.first()])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
formData = {'action': 'subnets__delete__%s' % subnet.id}
|
||||
url = reverse('horizon:nova:networks:detail',
|
||||
args=[network_id])
|
||||
res = self.client.post(url, formData)
|
||||
|
||||
self.assertRedirectsNoFollow(res, url)
|
||||
|
||||
@test.create_stubs({api.quantum: ('port_get',)})
|
||||
def test_port_detail(self):
|
||||
port = self.ports.first()
|
||||
api.quantum.port_get(IsA(http.HttpRequest), port.id)\
|
||||
.AndReturn(self.ports.first())
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('horizon:nova:networks:ports:detail',
|
||||
args=[port.id]))
|
||||
|
||||
self.assertTemplateUsed(res, 'nova/networks/ports/detail.html')
|
||||
self.assertEqual(res.context['port'].id, port.id)
|
||||
|
||||
@test.create_stubs({api.quantum: ('port_get',)})
|
||||
def test_port_detail_exception(self):
|
||||
port = self.ports.first()
|
||||
api.quantum.port_get(IsA(http.HttpRequest), port.id)\
|
||||
.AndRaise(self.exceptions.quantum)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('horizon:nova:networks:ports:detail',
|
||||
args=[port.id]))
|
||||
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
37
horizon/dashboards/nova/networks/urls.py
Normal file
37
horizon/dashboards/nova/networks/urls.py
Normal file
@ -0,0 +1,37 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
from django.conf.urls.defaults import patterns, url, include
|
||||
|
||||
from .views import IndexView, CreateView, DetailView, UpdateView
|
||||
from .subnets.views import CreateView as AddSubnetView
|
||||
from .subnets.views import UpdateView as EditSubnetView
|
||||
from .subnets import urls as subnet_urls
|
||||
from .ports import urls as port_urls
|
||||
|
||||
NETWORKS = r'^(?P<network_id>[^/]+)/%s$'
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$', IndexView.as_view(), name='index'),
|
||||
url(r'^create$', CreateView.as_view(), name='create'),
|
||||
url(NETWORKS % 'detail', DetailView.as_view(), name='detail'),
|
||||
url(NETWORKS % 'update', UpdateView.as_view(), name='update'),
|
||||
url(NETWORKS % 'subnets/create', AddSubnetView.as_view(),
|
||||
name='addsubnet'),
|
||||
url(r'^(?P<network_id>[^/]+)/subnets/(?P<subnet_id>[^/]+)/update$',
|
||||
EditSubnetView.as_view(), name='editsubnet'),
|
||||
url(r'^subnets/', include(subnet_urls, namespace='subnets')),
|
||||
url(r'^ports/', include(port_urls, namespace='ports')))
|
146
horizon/dashboards/nova/networks/views.py
Normal file
146
horizon/dashboards/nova/networks/views.py
Normal file
@ -0,0 +1,146 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""
|
||||
Views for managing Quantum Networks.
|
||||
"""
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import tables
|
||||
from horizon import workflows
|
||||
|
||||
from .tables import NetworksTable
|
||||
from .subnets.tables import SubnetsTable
|
||||
from .ports.tables import PortsTable
|
||||
from .forms import UpdateNetwork
|
||||
from .workflows import CreateNetwork
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IndexView(tables.DataTableView):
|
||||
table_class = NetworksTable
|
||||
template_name = 'nova/networks/index.html'
|
||||
|
||||
def get_data(self):
|
||||
try:
|
||||
# If a user has admin role, network list returned by Quantum API
|
||||
# contains networks that does not belong to that tenant.
|
||||
# So we need to specify tenant_id when calling network_list().
|
||||
tenant_id = self.request.user.tenant_id
|
||||
networks = api.quantum.network_list(self.request,
|
||||
tenant_id=tenant_id)
|
||||
except:
|
||||
networks = []
|
||||
msg = _('Network list can not be retrieved.')
|
||||
exceptions.handle(self.request, msg)
|
||||
for n in networks:
|
||||
n.set_id_as_name_if_empty()
|
||||
return networks
|
||||
|
||||
|
||||
class CreateView(workflows.WorkflowView):
|
||||
workflow_class = CreateNetwork
|
||||
template_name = 'nova/networks/create.html'
|
||||
|
||||
def get_initial(self):
|
||||
pass
|
||||
|
||||
|
||||
class UpdateView(forms.ModalFormView):
|
||||
form_class = UpdateNetwork
|
||||
template_name = 'nova/networks/update.html'
|
||||
context_object_name = 'network'
|
||||
success_url = reverse_lazy("horizon:nova:networks:index")
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UpdateView, self).get_context_data(**kwargs)
|
||||
context["network_id"] = self.kwargs['network_id']
|
||||
return context
|
||||
|
||||
def _get_object(self, *args, **kwargs):
|
||||
if not hasattr(self, "_object"):
|
||||
network_id = self.kwargs['network_id']
|
||||
try:
|
||||
self._object = api.quantum.network_get(self.request,
|
||||
network_id)
|
||||
except:
|
||||
redirect = self.success_url
|
||||
msg = _('Unable to retrieve network details.')
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
return self._object
|
||||
|
||||
def get_initial(self):
|
||||
network = self._get_object()
|
||||
return {'network_id': network['id'],
|
||||
'tenant_id': network['tenant_id'],
|
||||
'name': network['name']}
|
||||
|
||||
|
||||
class DetailView(tables.MultiTableView):
|
||||
table_classes = (SubnetsTable, PortsTable)
|
||||
template_name = 'nova/networks/detail.html'
|
||||
failure_url = reverse_lazy('horizon:nova:networks:index')
|
||||
|
||||
def get_subnets_data(self):
|
||||
try:
|
||||
network_id = self.kwargs['network_id']
|
||||
subnets = api.quantum.subnet_list(self.request,
|
||||
network_id=network_id)
|
||||
except:
|
||||
subnets = []
|
||||
msg = _('Subnet list can not be retrieved.')
|
||||
exceptions.handle(self.request, msg)
|
||||
for s in subnets:
|
||||
s.set_id_as_name_if_empty()
|
||||
return subnets
|
||||
|
||||
def get_ports_data(self):
|
||||
try:
|
||||
network_id = self.kwargs['network_id']
|
||||
ports = api.quantum.port_list(self.request, network_id=network_id)
|
||||
except:
|
||||
ports = []
|
||||
msg = _('Port list can not be retrieved.')
|
||||
exceptions.handle(self.request, msg)
|
||||
for p in ports:
|
||||
p.set_id_as_name_if_empty()
|
||||
return ports
|
||||
|
||||
def _get_data(self):
|
||||
if not hasattr(self, "_network"):
|
||||
try:
|
||||
network_id = self.kwargs['network_id']
|
||||
network = api.quantum.network_get(self.request, network_id)
|
||||
network.set_id_as_name_if_empty(length=0)
|
||||
except:
|
||||
msg = _('Unable to retrieve details for network "%s".') \
|
||||
% (network_id)
|
||||
exceptions.handle(self.request, msg, redirect=self.failure_url)
|
||||
self._network = network
|
||||
return self._network
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DetailView, self).get_context_data(**kwargs)
|
||||
context["network"] = self._get_data()
|
||||
return context
|
162
horizon/dashboards/nova/networks/workflows.py
Normal file
162
horizon/dashboards/nova/networks/workflows.py
Normal file
@ -0,0 +1,162 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
|
||||
import logging
|
||||
import netaddr
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import workflows
|
||||
from horizon.utils import fields
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CreateNetworkInfoAction(workflows.Action):
|
||||
net_name = forms.CharField(max_length=255,
|
||||
label=_("Network Name (optional)"),
|
||||
required=False)
|
||||
|
||||
class Meta:
|
||||
name = ("Network")
|
||||
help_text = _("From here you can create a new network.\n"
|
||||
"In addition a subnet associated with the network "
|
||||
"can be created in the next panel.")
|
||||
|
||||
|
||||
class CreateNetworkInfo(workflows.Step):
|
||||
action_class = CreateNetworkInfoAction
|
||||
contributes = ("net_name",)
|
||||
|
||||
|
||||
class CreateSubnetInfoAction(workflows.Action):
|
||||
with_subnet = forms.BooleanField(label=_("Create Subnet"),
|
||||
initial=True, required=False)
|
||||
subnet_name = forms.CharField(max_length=255,
|
||||
label=_("Subnet Name (optional)"),
|
||||
required=False)
|
||||
cidr = fields.IPField(label=_("Network Address"),
|
||||
required=False,
|
||||
initial="",
|
||||
help_text=_("Network address in CIDR format "
|
||||
"(e.g. 192.168.0.0/24)"),
|
||||
version=fields.IPv4 | fields.IPv6,
|
||||
mask=True)
|
||||
ip_version = forms.ChoiceField(choices=[(4, 'IPv4'), (6, 'IPv6')],
|
||||
label=_("IP Version"))
|
||||
gateway_ip = fields.IPField(label=_("Gateway IP (optional)"),
|
||||
required=False,
|
||||
initial="",
|
||||
help_text=_("IP address of Gateway "
|
||||
"(e.g. 192.168.0.1)"),
|
||||
version=fields.IPv4 | fields.IPv6,
|
||||
mask=False)
|
||||
|
||||
class Meta:
|
||||
name = ("Subnet")
|
||||
help_text = _("You can create a subnet associated with the new "
|
||||
"network. \"Network Address\" must be specified. "
|
||||
"\n\n"
|
||||
"If you are creating a network WITHOUT a subnet, "
|
||||
"clear \"Create Subnet\" checkbox.")
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super(CreateSubnetInfoAction, self).clean()
|
||||
with_subnet = cleaned_data.get('with_subnet')
|
||||
cidr = cleaned_data.get('cidr')
|
||||
ip_version = int(cleaned_data.get('ip_version'))
|
||||
gateway_ip = cleaned_data.get('gateway_ip')
|
||||
if with_subnet and not cidr:
|
||||
msg = _('Specify "Network Address" or '
|
||||
'clear "Create Subnet" checkbox.')
|
||||
raise forms.ValidationError(msg)
|
||||
if cidr:
|
||||
if netaddr.IPNetwork(cidr).version is not ip_version:
|
||||
msg = _('Network Address and IP version are inconsistent.')
|
||||
raise forms.ValidationError(msg)
|
||||
if gateway_ip:
|
||||
if netaddr.IPAddress(gateway_ip).version is not ip_version:
|
||||
msg = _('Gateway IP and IP version are inconsistent.')
|
||||
raise forms.ValidationError(msg)
|
||||
return cleaned_data
|
||||
|
||||
|
||||
class CreateSubnetInfo(workflows.Step):
|
||||
action_class = CreateSubnetInfoAction
|
||||
contributes = ("with_subnet", "subnet_name", "cidr",
|
||||
"ip_version", "gateway_ip")
|
||||
|
||||
|
||||
class CreateNetwork(workflows.Workflow):
|
||||
slug = "create_network"
|
||||
name = _("Create Network")
|
||||
finalize_button_name = _("Create")
|
||||
success_message = _('Created new network "%s".')
|
||||
failure_message = _('Unable to create network "%s".')
|
||||
success_url = "horizon:nova:networks:index"
|
||||
default_steps = (CreateNetworkInfo,
|
||||
CreateSubnetInfo)
|
||||
|
||||
def format_status_message(self, message):
|
||||
name = self.context.get('net_name') or self.context.get('net_id', '')
|
||||
return message % name
|
||||
|
||||
def handle(self, request, data):
|
||||
# create the network
|
||||
try:
|
||||
network = api.quantum.network_create(request,
|
||||
name=data['net_name'])
|
||||
network.set_id_as_name_if_empty()
|
||||
self.context['net_id'] = network.id
|
||||
msg = _('Network %s was successfully created.') % network.name
|
||||
LOG.debug(msg)
|
||||
except:
|
||||
msg = _('Failed to create network %s') % data['net_name']
|
||||
LOG.info(msg)
|
||||
redirect = reverse('horizon:nova:networks:index')
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
return False
|
||||
|
||||
# If we do not need to create a subnet, return here.
|
||||
if not data['with_subnet']:
|
||||
return True
|
||||
|
||||
# create the subnet
|
||||
try:
|
||||
params = {'network_id': network.id,
|
||||
'name': data['subnet_name'],
|
||||
'cidr': data['cidr'],
|
||||
'ip_version': int(data['ip_version'])}
|
||||
if data['gateway_ip']:
|
||||
params['gateway_ip'] = data['gateway_ip']
|
||||
api.quantum.subnet_create(request, **params)
|
||||
msg = _('Subnet %s was successfully created.') % data['cidr']
|
||||
LOG.debug(msg)
|
||||
except Exception:
|
||||
msg = _('Failed to create subnet %s for network %s') % \
|
||||
(data['cidr'], network.id)
|
||||
LOG.info(msg)
|
||||
redirect = reverse('horizon:nova:networks:index')
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
return False
|
||||
|
||||
return True
|
@ -23,7 +23,7 @@ class SystemPanels(horizon.PanelGroup):
|
||||
slug = "syspanel"
|
||||
name = _("System Panel")
|
||||
panels = ('overview', 'instances', 'volumes', 'services', 'flavors',
|
||||
'images', 'projects', 'users', 'quotas',)
|
||||
'images', 'projects', 'users', 'quotas', 'networks',)
|
||||
|
||||
|
||||
class Syspanel(horizon.Dashboard):
|
||||
|
@ -143,6 +143,7 @@ class InstanceViewTest(test.BaseAdminViewTests):
|
||||
'security_group_list', 'volume_list',
|
||||
'volume_snapshot_list',
|
||||
'tenant_quota_usages', 'server_create'),
|
||||
api.quantum: ('network_list',),
|
||||
api.glance: ('image_list_detailed',)})
|
||||
def test_launch_post(self):
|
||||
flavor = self.flavors.first()
|
||||
@ -155,6 +156,7 @@ class InstanceViewTest(test.BaseAdminViewTests):
|
||||
device_name = u'vda'
|
||||
volume_choice = "%s:vol" % volume.id
|
||||
block_device_mapping = {device_name: u"%s::0" % volume_choice}
|
||||
nics = [{"net-id": self.networks.first().id, "v4-fixed-ip": ''}]
|
||||
|
||||
api.nova.flavor_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.flavors.list())
|
||||
@ -171,6 +173,8 @@ class InstanceViewTest(test.BaseAdminViewTests):
|
||||
api.nova.volume_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.volumes.list())
|
||||
api.nova.volume_snapshot_list(IsA(http.HttpRequest)).AndReturn([])
|
||||
api.quantum.network_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.networks.list())
|
||||
api.nova.server_create(IsA(http.HttpRequest),
|
||||
server.name,
|
||||
image.id,
|
||||
@ -179,6 +183,7 @@ class InstanceViewTest(test.BaseAdminViewTests):
|
||||
customization_script,
|
||||
[sec_group.name],
|
||||
block_device_mapping,
|
||||
nics=nics,
|
||||
instance_count=IsA(int))
|
||||
self.mox.ReplayAll()
|
||||
|
||||
@ -194,6 +199,7 @@ class InstanceViewTest(test.BaseAdminViewTests):
|
||||
'volume_type': 'volume_id',
|
||||
'volume_id': volume_choice,
|
||||
'device_name': device_name,
|
||||
'network': self.networks.first().id,
|
||||
'count': 1}
|
||||
url = reverse('horizon:syspanel:instances:launch')
|
||||
res = self.client.post(url, form_data)
|
||||
|
0
horizon/dashboards/syspanel/networks/__init__.py
Normal file
0
horizon/dashboards/syspanel/networks/__init__.py
Normal file
67
horizon/dashboards/syspanel/networks/forms.py
Normal file
67
horizon/dashboards/syspanel/networks/forms.py
Normal file
@ -0,0 +1,67 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
|
||||
from horizon.dashboards.nova.networks import forms as user_forms
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CreateNetwork(forms.SelfHandlingForm):
|
||||
name = forms.CharField(max_length=255,
|
||||
label=_("Name"),
|
||||
required=False)
|
||||
tenant_id = forms.ChoiceField(label=_("Project"))
|
||||
|
||||
@classmethod
|
||||
def _instantiate(cls, request, *args, **kwargs):
|
||||
return cls(request, *args, **kwargs)
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(CreateNetwork, self).__init__(request, *args, **kwargs)
|
||||
tenant_choices = [('', _("Select a project"))]
|
||||
for tenant in api.keystone.tenant_list(request, admin=True):
|
||||
if tenant.enabled:
|
||||
tenant_choices.append((tenant.id, tenant.name))
|
||||
self.fields['tenant_id'].choices = tenant_choices
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
network = api.quantum.network_create(request,
|
||||
name=data['name'],
|
||||
tenant_id=data['tenant_id'])
|
||||
msg = _('Network %s was successfully created.') % data['name']
|
||||
LOG.debug(msg)
|
||||
messages.success(request, msg)
|
||||
return network
|
||||
except:
|
||||
redirect = reverse('horizon:syspanel:networks:index')
|
||||
msg = _('Failed to create network %s') % data['name']
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
|
||||
class UpdateNetwork(user_forms.UpdateNetwork):
|
||||
failure_url = 'horizon:syspanel:networks:index'
|
28
horizon/dashboards/syspanel/networks/panel.py
Normal file
28
horizon/dashboards/syspanel/networks/panel.py
Normal file
@ -0,0 +1,28 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
import horizon
|
||||
from horizon.dashboards.syspanel import dashboard
|
||||
|
||||
|
||||
class Networks(horizon.Panel):
|
||||
name = _("Networks")
|
||||
slug = 'networks'
|
||||
permissions = ('openstack.services.network',)
|
||||
|
||||
dashboard.Syspanel.register(Networks)
|
92
horizon/dashboards/syspanel/networks/ports/forms.py
Normal file
92
horizon/dashboards/syspanel/networks/ports/forms.py
Normal file
@ -0,0 +1,92 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import messages
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CreatePort(forms.SelfHandlingForm):
|
||||
network_name = forms.CharField(label=_("Network Name"),
|
||||
widget=forms.TextInput(
|
||||
attrs={'readonly': 'readonly'}))
|
||||
network_id = forms.CharField(label=_("Network ID"),
|
||||
widget=forms.TextInput(
|
||||
attrs={'readonly': 'readonly'}))
|
||||
name = forms.CharField(max_length=255,
|
||||
label=_("Name"),
|
||||
required=False)
|
||||
device_id = forms.CharField(max_length=100, label=_("Device ID"),
|
||||
help_text='Device ID attached to the port',
|
||||
required=False)
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
# We must specify tenant_id of the network which a subnet is
|
||||
# created for if admin user does not belong to the tenant.
|
||||
network = api.quantum.network_get(request, data['network_id'])
|
||||
data['tenant_id'] = network.tenant_id
|
||||
|
||||
port = api.quantum.port_create(request, **data)
|
||||
msg = _('Port %s was successfully created.') % port['id']
|
||||
LOG.debug(msg)
|
||||
messages.success(request, msg)
|
||||
return port
|
||||
except:
|
||||
msg = _('Failed to create a port for network %s') \
|
||||
% data['network_id']
|
||||
LOG.info(msg)
|
||||
redirect = reverse('horizon:syspanel:networks:detail',
|
||||
args=(data['network_id'],))
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
|
||||
class UpdatePort(forms.SelfHandlingForm):
|
||||
network_id = forms.CharField(widget=forms.HiddenInput())
|
||||
tenant_id = forms.CharField(widget=forms.HiddenInput())
|
||||
port_id = forms.CharField(widget=forms.HiddenInput())
|
||||
name = forms.CharField(max_length=255,
|
||||
label=_("Name"),
|
||||
required=False)
|
||||
device_id = forms.CharField(max_length=100, label=_("Device ID"),
|
||||
help_text='Device ID attached to the port',
|
||||
required=False)
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
LOG.debug('params = %s' % data)
|
||||
port = api.quantum.port_modify(request, data['port_id'],
|
||||
name=data['name'],
|
||||
device_id=data['device_id'])
|
||||
msg = _('Port %s was successfully updated.') % data['port_id']
|
||||
LOG.debug(msg)
|
||||
messages.success(request, msg)
|
||||
return port
|
||||
except Exception:
|
||||
msg = _('Failed to update port %s') % data['port_id']
|
||||
LOG.info(msg)
|
||||
redirect = reverse('horizon:syspanel:networks:detail',
|
||||
args=[data['network_id']])
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
85
horizon/dashboards/syspanel/networks/ports/tables.py
Normal file
85
horizon/dashboards/syspanel/networks/ports/tables.py
Normal file
@ -0,0 +1,85 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import tables
|
||||
|
||||
from horizon.dashboards.nova.networks.ports.tables import (get_fixed_ips,
|
||||
get_attached)
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DeletePort(tables.DeleteAction):
|
||||
data_type_singular = _("Port")
|
||||
data_type_plural = _("Ports")
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
try:
|
||||
api.quantum.port_delete(request, obj_id)
|
||||
except:
|
||||
msg = _('Failed to delete subnet %s') % obj_id
|
||||
LOG.info(msg)
|
||||
network_id = self.table.kwargs['network_id']
|
||||
redirect = reverse('horizon:syspanel:networks:detail',
|
||||
args=[network_id])
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
|
||||
class CreatePort(tables.LinkAction):
|
||||
name = "create"
|
||||
verbose_name = _("Create Port")
|
||||
url = "horizon:syspanel:networks:addport"
|
||||
classes = ("ajax-modal", "btn-create")
|
||||
|
||||
def get_link_url(self, datum=None):
|
||||
network_id = self.table.kwargs['network_id']
|
||||
return reverse(self.url, args=(network_id,))
|
||||
|
||||
|
||||
class UpdatePort(tables.LinkAction):
|
||||
name = "update"
|
||||
verbose_name = _("Edit Port")
|
||||
url = "horizon:syspanel:networks:editport"
|
||||
classes = ("ajax-modal", "btn-edit")
|
||||
|
||||
def get_link_url(self, port):
|
||||
network_id = self.table.kwargs['network_id']
|
||||
return reverse(self.url, args=(network_id, port.id))
|
||||
|
||||
|
||||
class PortsTable(tables.DataTable):
|
||||
name = tables.Column("name",
|
||||
verbose_name=_("Name"),
|
||||
link="horizon:syspanel:networks:ports:detail")
|
||||
fixed_ips = tables.Column(get_fixed_ips, verbose_name=_("Fixed IPs"))
|
||||
device_id = tables.Column(get_attached, verbose_name=_("Device Attached"))
|
||||
status = tables.Column("status", verbose_name=_("Status"))
|
||||
admin_state = tables.Column("admin_state",
|
||||
verbose_name=_("Admin State"))
|
||||
|
||||
class Meta:
|
||||
name = "ports"
|
||||
verbose_name = _("Ports")
|
||||
table_actions = (CreatePort, DeletePort)
|
||||
row_actions = (UpdatePort, DeletePort,)
|
46
horizon/dashboards/syspanel/networks/ports/tabs.py
Normal file
46
horizon/dashboards/syspanel/networks/ports/tabs.py
Normal file
@ -0,0 +1,46 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import tabs
|
||||
|
||||
import logging
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class OverviewTab(tabs.Tab):
|
||||
name = _("Overview")
|
||||
slug = "overview"
|
||||
template_name = "nova/networks/ports/_detail_overview.html"
|
||||
|
||||
def get_context_data(self, request):
|
||||
port_id = self.tab_group.kwargs['port_id']
|
||||
try:
|
||||
port = api.quantum.port_get(self.request, port_id)
|
||||
except:
|
||||
redirect = reverse('horizon:syspanel:networks:index')
|
||||
msg = _('Unable to retrieve port details.')
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
return {'port': port}
|
||||
|
||||
|
||||
class PortDetailTabs(tabs.TabGroup):
|
||||
slug = "port_details"
|
||||
tabs = (OverviewTab,)
|
24
horizon/dashboards/syspanel/networks/ports/urls.py
Normal file
24
horizon/dashboards/syspanel/networks/ports/urls.py
Normal file
@ -0,0 +1,24 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
from django.conf.urls.defaults import patterns, url
|
||||
|
||||
from horizon.dashboards.nova.networks.ports.views import DetailView
|
||||
|
||||
PORTS = r'^(?P<port_id>[^/]+)/%s$'
|
||||
|
||||
urlpatterns = patterns('horizon.dashboards.syspanel.networks.ports.views',
|
||||
url(PORTS % 'detail', DetailView.as_view(), name='detail'))
|
98
horizon/dashboards/syspanel/networks/ports/views.py
Normal file
98
horizon/dashboards/syspanel/networks/ports/views.py
Normal file
@ -0,0 +1,98 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from .forms import CreatePort, UpdatePort
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CreateView(forms.ModalFormView):
|
||||
form_class = CreatePort
|
||||
template_name = 'syspanel/networks/ports/create.html'
|
||||
success_url = 'horizon:syspanel:networks:detail'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_url,
|
||||
args=(self.kwargs['network_id'],))
|
||||
|
||||
def get_object(self):
|
||||
if not hasattr(self, "_object"):
|
||||
try:
|
||||
network_id = self.kwargs["network_id"]
|
||||
self._object = api.quantum.network_get(self.request,
|
||||
network_id)
|
||||
except:
|
||||
redirect = reverse("horizon:syspanel:networks:detail",
|
||||
args=(self.kwargs['network_id'],))
|
||||
msg = _("Unable to retrieve network.")
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
return self._object
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(CreateView, self).get_context_data(**kwargs)
|
||||
context['network'] = self.get_object()
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
network = self.get_object()
|
||||
return {"network_id": self.kwargs['network_id'],
|
||||
"network_name": network.name}
|
||||
|
||||
|
||||
class UpdateView(forms.ModalFormView):
|
||||
form_class = UpdatePort
|
||||
template_name = 'syspanel/networks/ports/update.html'
|
||||
context_object_name = 'port'
|
||||
success_url = 'horizon:syspanel:networks:detail'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_url,
|
||||
args=(self.kwargs['network_id'],))
|
||||
|
||||
def _get_object(self, *args, **kwargs):
|
||||
if not hasattr(self, "_object"):
|
||||
port_id = self.kwargs['port_id']
|
||||
try:
|
||||
self._object = api.quantum.port_get(self.request, port_id)
|
||||
except:
|
||||
redirect = reverse("horizon:syspanel:networks:detail",
|
||||
args=(self.kwargs['network_id'],))
|
||||
msg = _('Unable to retrieve port details')
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
return self._object
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UpdateView, self).get_context_data(**kwargs)
|
||||
port = self._get_object()
|
||||
context['port_id'] = port['id']
|
||||
context['network_id'] = port['network_id']
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
port = self._get_object()
|
||||
return {'port_id': port['id'],
|
||||
'network_id': port['network_id'],
|
||||
'tenant_id': port['tenant_id'],
|
||||
'name': port['name'],
|
||||
'device_id': port['device_id']}
|
52
horizon/dashboards/syspanel/networks/subnets/forms.py
Normal file
52
horizon/dashboards/syspanel/networks/subnets/forms.py
Normal file
@ -0,0 +1,52 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import forms
|
||||
from horizon import exceptions
|
||||
|
||||
from horizon.dashboards.nova.networks.subnets import forms as user_forms
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CreateSubnet(user_forms.CreateSubnet):
|
||||
failure_url = 'horizon:syspanel:networks:detail'
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
# We must specify tenant_id of the network which a subnet is
|
||||
# created for if admin user does not belong to the tenant.
|
||||
network = api.quantum.network_get(request, data['network_id'])
|
||||
data['tenant_id'] = network.tenant_id
|
||||
except:
|
||||
msg = _('Failed to retrieve network %s for a subnet') \
|
||||
% data['network_id']
|
||||
LOG.info(msg)
|
||||
redirect = reverse(self.failure_url, args=[data['network_id']])
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
return super(CreateSubnet, self).handle(request, data)
|
||||
|
||||
|
||||
class UpdateSubnet(user_forms.UpdateSubnet):
|
||||
tenant_id = forms.CharField(widget=forms.HiddenInput())
|
||||
failure_url = 'horizon:syspanel:networks:detail'
|
82
horizon/dashboards/syspanel/networks/subnets/tables.py
Normal file
82
horizon/dashboards/syspanel/networks/subnets/tables.py
Normal file
@ -0,0 +1,82 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import tables
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DeleteSubnet(tables.DeleteAction):
|
||||
data_type_singular = _("Subnet")
|
||||
data_type_plural = _("Subnets")
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
try:
|
||||
api.quantum.subnet_delete(request, obj_id)
|
||||
except:
|
||||
msg = _('Failed to delete subnet %s') % obj_id
|
||||
LOG.info(msg)
|
||||
network_id = self.table.kwargs['network_id']
|
||||
redirect = reverse('horizon:syspanel:networks:detail',
|
||||
args=[network_id])
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
|
||||
class CreateSubnet(tables.LinkAction):
|
||||
name = "create"
|
||||
verbose_name = _("Create Subnet")
|
||||
url = "horizon:syspanel:networks:addsubnet"
|
||||
classes = ("ajax-modal", "btn-create")
|
||||
|
||||
def get_link_url(self, datum=None):
|
||||
network_id = self.table.kwargs['network_id']
|
||||
return reverse(self.url, args=(network_id,))
|
||||
|
||||
|
||||
class UpdateSubnet(tables.LinkAction):
|
||||
name = "update"
|
||||
verbose_name = _("Edit Subnet")
|
||||
url = "horizon:syspanel:networks:editsubnet"
|
||||
classes = ("ajax-modal", "btn-edit")
|
||||
|
||||
def get_link_url(self, subnet):
|
||||
network_id = self.table.kwargs['network_id']
|
||||
return reverse(self.url, args=(network_id, subnet.id))
|
||||
|
||||
|
||||
class SubnetsTable(tables.DataTable):
|
||||
name = tables.Column("name", verbose_name=_("Name"),
|
||||
link='horizon:syspanel:networks:subnets:detail')
|
||||
cidr = tables.Column("cidr", verbose_name=_("CIDR"))
|
||||
ip_version = tables.Column("ipver_str", verbose_name=_("IP Version"))
|
||||
gateway_ip = tables.Column("gateway_ip", verbose_name=_("Gateway IP"))
|
||||
|
||||
def get_object_display(self, subnet):
|
||||
return subnet.id
|
||||
|
||||
class Meta:
|
||||
name = "subnets"
|
||||
verbose_name = _("Subnets")
|
||||
table_actions = (CreateSubnet, DeleteSubnet)
|
||||
row_actions = (UpdateSubnet, DeleteSubnet,)
|
24
horizon/dashboards/syspanel/networks/subnets/urls.py
Normal file
24
horizon/dashboards/syspanel/networks/subnets/urls.py
Normal file
@ -0,0 +1,24 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
from django.conf.urls.defaults import patterns, url
|
||||
|
||||
from horizon.dashboards.nova.networks.subnets.views import DetailView
|
||||
|
||||
SUBNETS = r'^(?P<subnet_id>[^/]+)/%s$'
|
||||
|
||||
urlpatterns = patterns('horizon.dashboards.syspanel.networks.subnets.views',
|
||||
url(SUBNETS % 'detail', DetailView.as_view(), name='detail'))
|
101
horizon/dashboards/syspanel/networks/subnets/views.py
Normal file
101
horizon/dashboards/syspanel/networks/subnets/views.py
Normal file
@ -0,0 +1,101 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from .forms import CreateSubnet, UpdateSubnet
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CreateView(forms.ModalFormView):
|
||||
form_class = CreateSubnet
|
||||
template_name = 'syspanel/networks/subnets/create.html'
|
||||
success_url = 'horizon:syspanel:networks:detail'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_url,
|
||||
args=(self.kwargs['network_id'],))
|
||||
|
||||
def get_object(self):
|
||||
if not hasattr(self, "_object"):
|
||||
try:
|
||||
network_id = self.kwargs["network_id"]
|
||||
self._object = api.quantum.network_get(self.request,
|
||||
network_id)
|
||||
except:
|
||||
redirect = reverse('horizon:nova:networks:index')
|
||||
msg = _("Unable to retrieve network.")
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
return self._object
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(CreateView, self).get_context_data(**kwargs)
|
||||
context['network'] = self.get_object()
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
network = self.get_object()
|
||||
return {"network_id": self.kwargs['network_id'],
|
||||
"network_name": network.name}
|
||||
|
||||
|
||||
class UpdateView(forms.ModalFormView):
|
||||
form_class = UpdateSubnet
|
||||
template_name = 'syspanel/networks/subnets/update.html'
|
||||
context_object_name = 'subnet'
|
||||
success_url = 'horizon:syspanel:networks:detail'
|
||||
|
||||
def get_success_url(self):
|
||||
return reverse(self.success_url,
|
||||
args=(self.kwargs['network_id'],))
|
||||
|
||||
def _get_object(self, *args, **kwargs):
|
||||
if not hasattr(self, "_object"):
|
||||
subnet_id = self.kwargs['subnet_id']
|
||||
try:
|
||||
self._object = api.quantum.subnet_get(self.request, subnet_id)
|
||||
except:
|
||||
redirect = reverse("horizon:syspanel:networks:detail",
|
||||
args=(self.kwargs['network_id'],))
|
||||
msg = _('Unable to retrieve subnet details')
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
return self._object
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(UpdateView, self).get_context_data(**kwargs)
|
||||
subnet = self._get_object()
|
||||
context['subnet_id'] = subnet['id']
|
||||
context['network_id'] = subnet['network_id']
|
||||
context['cidr'] = subnet['cidr']
|
||||
context['ip_version'] = {4: 'IPv4', 6: 'IPv6'}[subnet['ip_version']]
|
||||
return context
|
||||
|
||||
def get_initial(self):
|
||||
subnet = self._get_object()
|
||||
return {'network_id': self.kwargs['network_id'],
|
||||
'subnet_id': subnet['id'],
|
||||
'tenant_id': subnet['tenant_id'],
|
||||
'cidr': subnet['cidr'],
|
||||
'ip_version': subnet['ip_version'],
|
||||
'name': subnet['name'],
|
||||
'gateway_ip': subnet['gateway_ip']}
|
79
horizon/dashboards/syspanel/networks/tables.py
Normal file
79
horizon/dashboards/syspanel/networks/tables.py
Normal file
@ -0,0 +1,79 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import tables
|
||||
|
||||
from horizon.dashboards.nova.networks.tables import get_subnets
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DeleteNetwork(tables.DeleteAction):
|
||||
data_type_singular = _("Network")
|
||||
data_type_plural = _("Networks")
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
try:
|
||||
api.quantum.network_delete(request, obj_id)
|
||||
except:
|
||||
msg = _('Failed to delete network %s') % obj_id
|
||||
LOG.info(msg)
|
||||
redirect = reverse('horizon:syspanel:networks:index')
|
||||
exceptions.handle(request, msg, redirect=redirect)
|
||||
|
||||
|
||||
class CreateNetwork(tables.LinkAction):
|
||||
name = "create"
|
||||
verbose_name = _("Create Network")
|
||||
url = "horizon:syspanel:networks:create"
|
||||
classes = ("ajax-modal", "btn-create")
|
||||
|
||||
|
||||
class EditNetwork(tables.LinkAction):
|
||||
name = "update"
|
||||
verbose_name = _("Edit Network")
|
||||
url = "horizon:syspanel:networks:update"
|
||||
classes = ("ajax-modal", "btn-edit")
|
||||
|
||||
|
||||
#def _get_subnets(network):
|
||||
# cidrs = [subnet.get('cidr') for subnet in network.subnets]
|
||||
# return ','.join(cidrs)
|
||||
|
||||
|
||||
class NetworksTable(tables.DataTable):
|
||||
tenant = tables.Column("tenant_name", verbose_name=_("Project"))
|
||||
name = tables.Column("name", verbose_name=_("Network Name"),
|
||||
link='horizon:syspanel:networks:detail')
|
||||
subnets = tables.Column(get_subnets,
|
||||
verbose_name=_("Subnets Associated"),)
|
||||
status = tables.Column("status", verbose_name=_("Status"))
|
||||
admin_state = tables.Column("admin_state",
|
||||
verbose_name=_("Admin State"))
|
||||
|
||||
class Meta:
|
||||
name = "networks"
|
||||
verbose_name = _("Networks")
|
||||
table_actions = (CreateNetwork, DeleteNetwork)
|
||||
row_actions = (EditNetwork, DeleteNetwork)
|
@ -0,0 +1,25 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form_id %}create_network_form{% endblock %}
|
||||
{% block form_action %}{% url horizon:syspanel:networks:create %}{% endblock %}
|
||||
|
||||
{% block modal_id %}create_network_modal{% endblock %}
|
||||
{% block modal-header %}{% trans "Create Network" %}{% 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."%}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Create Network" %}" />
|
||||
<a href="{% url horizon:syspanel:networks:index %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
@ -0,0 +1,24 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form_id %}update_network_form{% endblock %}
|
||||
{% block form_action %}{% url horizon:syspanel:networks:update network_id %}{% endblock %}
|
||||
|
||||
{% block modal-header %}{% trans "Edit Network" %}{% 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 here." %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Save Changes" %}" />
|
||||
<a href="{% url horizon:syspanel:networks:index %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
@ -0,0 +1,11 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Create Network" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Create Network") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include "syspanel/networks/_create.html" %}
|
||||
{% endblock %}
|
@ -0,0 +1,21 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Networks" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Networks") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
<div id="networks">
|
||||
{{ networks_table.render }}
|
||||
</div>
|
||||
|
||||
<div id="subnets">
|
||||
{{ subnets_table.render }}
|
||||
</div>
|
||||
|
||||
<div id="ports">
|
||||
{{ ports_table.render }}
|
||||
</div>
|
||||
{% endblock %}
|
@ -0,0 +1,25 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form_id %}create_port_form{% endblock %}
|
||||
{% block form_action %}{% url horizon:syspanel:networks:addport network.id %}
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-header %}{% trans "Create Port" %}{% 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 can create a port for the network. If you specify device ID to be attached, the device specified will be attached to the port created."%}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Create Port" %}" />
|
||||
<a href="{% url horizon:syspanel:networks:detail network.id %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
@ -0,0 +1,29 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form_id %}update_port_form{% endblock %}
|
||||
{% block form_action %}{% url horizon:syspanel:networks:editport network_id port_id %}{% endblock %}
|
||||
|
||||
{% block modal-header %}{% trans "Edit Port" %}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="left">
|
||||
<dl>
|
||||
<dt>{% trans "ID" %}</dt>
|
||||
<dd>{{ port_id }}</dd>
|
||||
</dl>
|
||||
<hr>
|
||||
<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 port here." %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Save Changes" %}" />
|
||||
<a href="{% url horizon:syspanel:networks:detail network_id %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
@ -0,0 +1,11 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Create Port" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Create Port") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include "syspanel/networks/ports/_create.html" %}
|
||||
{% endblock %}
|
@ -0,0 +1,11 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Update Port" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Update Port") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'syspanel/networks/ports/_update.html' %}
|
||||
{% endblock %}
|
@ -0,0 +1,25 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form_id %}create_subnet_form{% endblock %}
|
||||
{% block form_action %}{% url horizon:syspanel:networks:addsubnet network.id %}
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-header %}{% trans "Create Subnet" %}{% 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 can create a subnet for the network. Any network address can be specified unless the network address does not overlap other subnets in the network." %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Create Subnet" %}" />
|
||||
<a href="{% url horizon:syspanel:networks:detail network.id %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
@ -0,0 +1,33 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form_id %}update_subnet_form{% endblock %}
|
||||
{% block form_action %}{% url horizon:syspanel:networks:editsubnet network_id subnet_id %}{% endblock %}
|
||||
|
||||
{% block modal-header %}{% trans "Edit Subnet" %}{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="left">
|
||||
<dl>
|
||||
<dt>{% trans "ID" %}</dt>
|
||||
<dd>{{ subnet_id }}</dd>
|
||||
<dt>{% trans "Network Address" %}</dt>
|
||||
<dd>{{ cidr }}</dd>
|
||||
<dt>{% trans "IP version" %}</dt>
|
||||
<dd>{{ ip_version }}</dd>
|
||||
</dl>
|
||||
<hr>
|
||||
<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 subnet here." %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Save Changes" %}" />
|
||||
<a href="{% url horizon:syspanel:networks:detail network_id %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
@ -0,0 +1,11 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Create Subnet" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Create Subnet") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include "syspanel/networks/subnets/_create.html" %}
|
||||
{% endblock %}
|
@ -0,0 +1,11 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Network Detail" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Network Detail") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
@ -0,0 +1,11 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Update Subnet" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Update Subnet") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'syspanel/networks/subnets/_update.html' %}
|
||||
{% endblock %}
|
@ -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") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'syspanel/networks/_update.html' %}
|
||||
{% endblock %}
|
801
horizon/dashboards/syspanel/networks/tests.py
Normal file
801
horizon/dashboards/syspanel/networks/tests.py
Normal file
@ -0,0 +1,801 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
from django import http
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.html import escape
|
||||
from mox import IsA
|
||||
|
||||
from horizon import api
|
||||
from horizon import test
|
||||
|
||||
|
||||
INDEX_URL = reverse('horizon:syspanel:networks:index')
|
||||
|
||||
|
||||
class NetworkTests(test.BaseAdminViewTests):
|
||||
@test.create_stubs({api.quantum: ('network_list',),
|
||||
api.keystone: ('tenant_list',)})
|
||||
def test_index(self):
|
||||
tenants = self.tenants.list()
|
||||
api.quantum.network_list(IsA(http.HttpRequest)) \
|
||||
.AndReturn(self.networks.list())
|
||||
api.keystone.tenant_list(IsA(http.HttpRequest), admin=True)\
|
||||
.AndReturn(tenants)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(INDEX_URL)
|
||||
|
||||
self.assertTemplateUsed(res, 'syspanel/networks/index.html')
|
||||
networks = res.context['networks_table'].data
|
||||
self.assertItemsEqual(networks, self.networks.list())
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_list',)})
|
||||
def test_index_network_list_exception(self):
|
||||
api.quantum.network_list(IsA(http.HttpRequest)) \
|
||||
.AndRaise(self.exceptions.quantum)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(INDEX_URL)
|
||||
|
||||
self.assertTemplateUsed(res, 'syspanel/networks/index.html')
|
||||
self.assertEqual(len(res.context['networks_table'].data), 0)
|
||||
self.assertMessageCount(res, error=1)
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_get',
|
||||
'subnet_list',
|
||||
'port_list',)})
|
||||
def test_network_detail(self):
|
||||
network_id = self.networks.first().id
|
||||
api.quantum.network_get(IsA(http.HttpRequest), network_id)\
|
||||
.AndReturn(self.networks.first())
|
||||
api.quantum.subnet_list(IsA(http.HttpRequest), network_id=network_id)\
|
||||
.AndReturn([self.subnets.first()])
|
||||
api.quantum.port_list(IsA(http.HttpRequest), network_id=network_id)\
|
||||
.AndReturn([self.ports.first()])
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('horizon:syspanel:networks:detail',
|
||||
args=[network_id]))
|
||||
|
||||
self.assertTemplateUsed(res, 'nova/networks/detail.html')
|
||||
subnets = res.context['subnets_table'].data
|
||||
ports = res.context['ports_table'].data
|
||||
self.assertItemsEqual(subnets, [self.subnets.first()])
|
||||
self.assertItemsEqual(ports, [self.ports.first()])
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_get',
|
||||
'subnet_list',
|
||||
'port_list',)})
|
||||
def test_network_detail_network_exception(self):
|
||||
network_id = self.networks.first().id
|
||||
api.quantum.network_get(IsA(http.HttpRequest), network_id)\
|
||||
.AndRaise(self.exceptions.quantum)
|
||||
api.quantum.subnet_list(IsA(http.HttpRequest), network_id=network_id)\
|
||||
.AndReturn([self.subnets.first()])
|
||||
api.quantum.port_list(IsA(http.HttpRequest), network_id=network_id)\
|
||||
.AndReturn([self.ports.first()])
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('horizon:syspanel:networks:detail', args=[network_id])
|
||||
res = self.client.get(url)
|
||||
|
||||
redir_url = INDEX_URL
|
||||
self.assertRedirectsNoFollow(res, redir_url)
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_get',
|
||||
'subnet_list',
|
||||
'port_list',)})
|
||||
def test_network_detail_subnet_exception(self):
|
||||
network_id = self.networks.first().id
|
||||
api.quantum.network_get(IsA(http.HttpRequest), network_id).\
|
||||
AndReturn(self.networks.first())
|
||||
api.quantum.subnet_list(IsA(http.HttpRequest), network_id=network_id).\
|
||||
AndRaise(self.exceptions.quantum)
|
||||
api.quantum.port_list(IsA(http.HttpRequest), network_id=network_id).\
|
||||
AndReturn([self.ports.first()])
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('horizon:syspanel:networks:detail',
|
||||
args=[network_id]))
|
||||
|
||||
self.assertTemplateUsed(res, 'nova/networks/detail.html')
|
||||
subnets = res.context['subnets_table'].data
|
||||
ports = res.context['ports_table'].data
|
||||
self.assertEqual(len(subnets), 0)
|
||||
self.assertItemsEqual(ports, [self.ports.first()])
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_get',
|
||||
'subnet_list',
|
||||
'port_list',)})
|
||||
def test_network_detail_port_exception(self):
|
||||
network_id = self.networks.first().id
|
||||
api.quantum.network_get(IsA(http.HttpRequest), network_id).\
|
||||
AndReturn(self.networks.first())
|
||||
api.quantum.subnet_list(IsA(http.HttpRequest), network_id=network_id).\
|
||||
AndReturn([self.subnets.first()])
|
||||
api.quantum.port_list(IsA(http.HttpRequest), network_id=network_id).\
|
||||
AndRaise(self.exceptions.quantum)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('horizon:syspanel:networks:detail',
|
||||
args=[network_id]))
|
||||
|
||||
self.assertTemplateUsed(res, 'nova/networks/detail.html')
|
||||
subnets = res.context['subnets_table'].data
|
||||
ports = res.context['ports_table'].data
|
||||
self.assertItemsEqual(subnets, [self.subnets.first()])
|
||||
self.assertEqual(len(ports), 0)
|
||||
|
||||
@test.create_stubs({api.keystone: ('tenant_list',)})
|
||||
def test_network_create_get(self):
|
||||
tenants = self.tenants.list()
|
||||
api.keystone.tenant_list(IsA(http.HttpRequest), admin=True)\
|
||||
.AndReturn(tenants)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('horizon:syspanel:networks:create')
|
||||
res = self.client.get(url)
|
||||
|
||||
self.assertTemplateUsed(res, 'syspanel/networks/create.html')
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_create',),
|
||||
api.keystone: ('tenant_list',)})
|
||||
def test_network_create_post(self):
|
||||
tenants = self.tenants.list()
|
||||
tenant_id = self.tenants.first().id
|
||||
network = self.networks.first()
|
||||
api.keystone.tenant_list(IsA(http.HttpRequest), admin=True)\
|
||||
.AndReturn(tenants)
|
||||
api.quantum.network_create(IsA(http.HttpRequest), name=network.name,
|
||||
tenant_id=tenant_id)\
|
||||
.AndReturn(network)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'tenant_id': tenant_id,
|
||||
'name': network.name}
|
||||
url = reverse('horizon:syspanel:networks:create')
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_create',),
|
||||
api.keystone: ('tenant_list',)})
|
||||
def test_network_create_post_network_exception(self):
|
||||
tenants = self.tenants.list()
|
||||
tenant_id = self.tenants.first().id
|
||||
network = self.networks.first()
|
||||
api.keystone.tenant_list(IsA(http.HttpRequest), admin=True)\
|
||||
.AndReturn(tenants)
|
||||
api.quantum.network_create(IsA(http.HttpRequest), name=network.name,
|
||||
tenant_id=tenant_id)\
|
||||
.AndRaise(self.exceptions.quantum)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'tenant_id': tenant_id,
|
||||
'name': network.name}
|
||||
url = reverse('horizon:syspanel:networks:create')
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
self.assertNoFormErrors(res)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_get',)})
|
||||
def test_network_update_get(self):
|
||||
network = self.networks.first()
|
||||
api.quantum.network_get(IsA(http.HttpRequest), network.id)\
|
||||
.AndReturn(network)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('horizon:syspanel:networks:update', args=[network.id])
|
||||
res = self.client.get(url)
|
||||
|
||||
self.assertTemplateUsed(res, 'syspanel/networks/update.html')
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_get',)})
|
||||
def test_network_update_get_exception(self):
|
||||
network = self.networks.first()
|
||||
api.quantum.network_get(IsA(http.HttpRequest), network.id)\
|
||||
.AndRaise(self.exceptions.quantum)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('horizon:syspanel:networks:update', args=[network.id])
|
||||
res = self.client.get(url)
|
||||
|
||||
redir_url = INDEX_URL
|
||||
self.assertRedirectsNoFollow(res, redir_url)
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_modify',
|
||||
'network_get',)})
|
||||
def test_network_update_post(self):
|
||||
network = self.networks.first()
|
||||
api.quantum.network_modify(IsA(http.HttpRequest), network.id,
|
||||
name=network.name)\
|
||||
.AndReturn(network)
|
||||
api.quantum.network_get(IsA(http.HttpRequest), network.id)\
|
||||
.AndReturn(network)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
formData = {'network_id': network.id,
|
||||
'name': network.name,
|
||||
'tenant_id': network.tenant_id}
|
||||
url = reverse('horizon:syspanel:networks:update', args=[network.id])
|
||||
res = self.client.post(url, formData)
|
||||
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_modify',
|
||||
'network_get',)})
|
||||
def test_network_update_post_exception(self):
|
||||
network = self.networks.first()
|
||||
api.quantum.network_modify(IsA(http.HttpRequest), network.id,
|
||||
name=network.name)\
|
||||
.AndRaise(self.exceptions.quantum)
|
||||
api.quantum.network_get(IsA(http.HttpRequest), network.id)\
|
||||
.AndReturn(network)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'network_id': network.id,
|
||||
'name': network.name,
|
||||
'tenant_id': network.tenant_id}
|
||||
url = reverse('horizon:syspanel:networks:update', args=[network.id])
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_list',
|
||||
'network_delete'),
|
||||
api.keystone: ('tenant_list',)})
|
||||
def test_delete_network(self):
|
||||
tenants = self.tenants.list()
|
||||
network = self.networks.first()
|
||||
api.keystone.tenant_list(IsA(http.HttpRequest), admin=True)\
|
||||
.AndReturn(tenants)
|
||||
api.quantum.network_list(IsA(http.HttpRequest))\
|
||||
.AndReturn([network])
|
||||
api.quantum.network_delete(IsA(http.HttpRequest), network.id)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'action': 'networks__delete__%s' % network.id}
|
||||
res = self.client.post(INDEX_URL, form_data)
|
||||
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_list',
|
||||
'network_delete'),
|
||||
api.keystone: ('tenant_list',)})
|
||||
def test_delete_network_exception(self):
|
||||
tenants = self.tenants.list()
|
||||
network = self.networks.first()
|
||||
api.keystone.tenant_list(IsA(http.HttpRequest), admin=True)\
|
||||
.AndReturn(tenants)
|
||||
api.quantum.network_list(IsA(http.HttpRequest))\
|
||||
.AndReturn([network])
|
||||
api.quantum.network_delete(IsA(http.HttpRequest), network.id)\
|
||||
.AndRaise(self.exceptions.quantum)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'action': 'networks__delete__%s' % network.id}
|
||||
res = self.client.post(INDEX_URL, form_data)
|
||||
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@test.create_stubs({api.quantum: ('subnet_get',)})
|
||||
def test_subnet_detail(self):
|
||||
subnet = self.subnets.first()
|
||||
api.quantum.subnet_get(IsA(http.HttpRequest), subnet.id)\
|
||||
.AndReturn(self.subnets.first())
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('horizon:syspanel:networks:subnets:detail',
|
||||
args=[subnet.id])
|
||||
res = self.client.get(url)
|
||||
|
||||
self.assertTemplateUsed(res, 'nova/networks/subnets/detail.html')
|
||||
self.assertEqual(res.context['subnet'].id, subnet.id)
|
||||
|
||||
@test.create_stubs({api.quantum: ('subnet_get',)})
|
||||
def test_subnet_detail_exception(self):
|
||||
subnet = self.subnets.first()
|
||||
api.quantum.subnet_get(IsA(http.HttpRequest), subnet.id)\
|
||||
.AndRaise(self.exceptions.quantum)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('horizon:syspanel:networks:subnets:detail',
|
||||
args=[subnet.id])
|
||||
res = self.client.get(url)
|
||||
|
||||
# syspanel DetailView is shared with userpanel one, so
|
||||
# redirection URL on error is userpanel index.
|
||||
redir_url = reverse('horizon:nova:networks:index')
|
||||
self.assertRedirectsNoFollow(res, redir_url)
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_get',)})
|
||||
def test_subnet_create_get(self):
|
||||
network = self.networks.first()
|
||||
api.quantum.network_get(IsA(http.HttpRequest),
|
||||
network.id)\
|
||||
.AndReturn(self.networks.first())
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('horizon:syspanel:networks:addsubnet',
|
||||
args=[network.id])
|
||||
res = self.client.get(url)
|
||||
|
||||
self.assertTemplateUsed(res, 'syspanel/networks/subnets/create.html')
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_get',
|
||||
'subnet_create',)})
|
||||
def test_subnet_create_post(self):
|
||||
network = self.networks.first()
|
||||
subnet = self.subnets.first()
|
||||
api.quantum.network_get(IsA(http.HttpRequest),
|
||||
network.id)\
|
||||
.AndReturn(self.networks.first())
|
||||
api.quantum.network_get(IsA(http.HttpRequest),
|
||||
network.id)\
|
||||
.AndReturn(self.networks.first())
|
||||
api.quantum.subnet_create(IsA(http.HttpRequest),
|
||||
network_id=network.id,
|
||||
network_name=network.name,
|
||||
name=subnet.name,
|
||||
cidr=subnet.cidr,
|
||||
ip_version=subnet.ip_version,
|
||||
gateway_ip=subnet.gateway_ip,
|
||||
tenant_id=subnet.tenant_id)\
|
||||
.AndReturn(subnet)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'network_id': subnet.network_id,
|
||||
'network_name': network.name,
|
||||
'name': subnet.name,
|
||||
'cidr': subnet.cidr,
|
||||
'ip_version': subnet.ip_version,
|
||||
'gateway_ip': subnet.gateway_ip}
|
||||
url = reverse('horizon:syspanel:networks:addsubnet',
|
||||
args=[subnet.network_id])
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
self.assertNoFormErrors(res)
|
||||
redir_url = reverse('horizon:syspanel:networks:detail',
|
||||
args=[subnet.network_id])
|
||||
self.assertRedirectsNoFollow(res, redir_url)
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_get',
|
||||
'subnet_create',)})
|
||||
def test_subnet_create_post_network_exception(self):
|
||||
network = self.networks.first()
|
||||
subnet = self.subnets.first()
|
||||
api.quantum.network_get(IsA(http.HttpRequest),
|
||||
network.id)\
|
||||
.AndRaise(self.exceptions.quantum)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'network_id': subnet.network_id,
|
||||
'network_name': network.name,
|
||||
'name': subnet.name,
|
||||
'cidr': subnet.cidr,
|
||||
'ip_version': subnet.ip_version,
|
||||
'gateway_ip': subnet.gateway_ip}
|
||||
url = reverse('horizon:syspanel:networks:addsubnet',
|
||||
args=[subnet.network_id])
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
self.assertNoFormErrors(res)
|
||||
# syspanel DetailView is shared with userpanel one, so
|
||||
# redirection URL on error is userpanel index.
|
||||
redir_url = reverse('horizon:nova:networks:index')
|
||||
self.assertRedirectsNoFollow(res, redir_url)
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_get',
|
||||
'subnet_create',)})
|
||||
def test_subnet_create_post_subnet_exception(self):
|
||||
network = self.networks.first()
|
||||
subnet = self.subnets.first()
|
||||
api.quantum.network_get(IsA(http.HttpRequest),
|
||||
network.id)\
|
||||
.AndReturn(self.networks.first())
|
||||
api.quantum.network_get(IsA(http.HttpRequest),
|
||||
network.id)\
|
||||
.AndReturn(self.networks.first())
|
||||
api.quantum.subnet_create(IsA(http.HttpRequest),
|
||||
network_id=network.id,
|
||||
network_name=network.name,
|
||||
name=subnet.name,
|
||||
cidr=subnet.cidr,
|
||||
ip_version=subnet.ip_version,
|
||||
gateway_ip=subnet.gateway_ip,
|
||||
tenant_id=subnet.tenant_id)\
|
||||
.AndRaise(self.exceptions.quantum)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'network_id': subnet.network_id,
|
||||
'network_name': network.name,
|
||||
'name': subnet.name,
|
||||
'cidr': subnet.cidr,
|
||||
'ip_version': subnet.ip_version,
|
||||
'gateway_ip': subnet.gateway_ip}
|
||||
url = reverse('horizon:syspanel:networks:addsubnet',
|
||||
args=[subnet.network_id])
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
redir_url = reverse('horizon:syspanel:networks:detail',
|
||||
args=[subnet.network_id])
|
||||
self.assertRedirectsNoFollow(res, redir_url)
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_get',)})
|
||||
def test_subnet_create_post_cidr_inconsistent(self):
|
||||
network = self.networks.first()
|
||||
subnet = self.subnets.first()
|
||||
api.quantum.network_get(IsA(http.HttpRequest),
|
||||
network.id)\
|
||||
.AndReturn(self.networks.first())
|
||||
self.mox.ReplayAll()
|
||||
|
||||
# dummy IPv6 address
|
||||
cidr = '2001:0DB8:0:CD30:123:4567:89AB:CDEF/60'
|
||||
form_data = {'network_id': subnet.network_id,
|
||||
'network_name': network.name,
|
||||
'name': subnet.name,
|
||||
'cidr': cidr,
|
||||
'ip_version': subnet.ip_version,
|
||||
'gateway_ip': subnet.gateway_ip}
|
||||
url = reverse('horizon:syspanel:networks:addsubnet',
|
||||
args=[subnet.network_id])
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
expected_msg = 'Network Address and IP version are inconsistent.'
|
||||
self.assertContains(res, expected_msg)
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_get',)})
|
||||
def test_subnet_create_post_gw_inconsistent(self):
|
||||
network = self.networks.first()
|
||||
subnet = self.subnets.first()
|
||||
api.quantum.network_get(IsA(http.HttpRequest),
|
||||
network.id)\
|
||||
.AndReturn(self.networks.first())
|
||||
self.mox.ReplayAll()
|
||||
|
||||
# dummy IPv6 address
|
||||
gateway_ip = '2001:0DB8:0:CD30:123:4567:89AB:CDEF'
|
||||
form_data = {'network_id': subnet.network_id,
|
||||
'network_name': network.name,
|
||||
'name': subnet.name,
|
||||
'cidr': subnet.cidr,
|
||||
'ip_version': subnet.ip_version,
|
||||
'gateway_ip': gateway_ip}
|
||||
url = reverse('horizon:syspanel:networks:addsubnet',
|
||||
args=[subnet.network_id])
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
self.assertContains(res, 'Gateway IP and IP version are inconsistent.')
|
||||
|
||||
@test.create_stubs({api.quantum: ('subnet_modify',
|
||||
'subnet_get',)})
|
||||
def test_subnet_update_post(self):
|
||||
subnet = self.subnets.first()
|
||||
api.quantum.subnet_get(IsA(http.HttpRequest), subnet.id)\
|
||||
.AndReturn(subnet)
|
||||
api.quantum.subnet_modify(IsA(http.HttpRequest), subnet.id,
|
||||
name=subnet.name,
|
||||
gateway_ip=subnet.gateway_ip)\
|
||||
.AndReturn(subnet)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
formData = {'network_id': subnet.network_id,
|
||||
'tenant_id': subnet.tenant_id,
|
||||
'subnet_id': subnet.id,
|
||||
'name': subnet.name,
|
||||
'cidr': subnet.cidr,
|
||||
'ip_version': subnet.ip_version,
|
||||
'gateway_ip': subnet.gateway_ip}
|
||||
url = reverse('horizon:syspanel:networks:editsubnet',
|
||||
args=[subnet.network_id, subnet.id])
|
||||
res = self.client.post(url, formData)
|
||||
|
||||
redir_url = reverse('horizon:syspanel:networks:detail',
|
||||
args=[subnet.network_id])
|
||||
self.assertRedirectsNoFollow(res, redir_url)
|
||||
|
||||
@test.create_stubs({api.quantum: ('subnet_modify',
|
||||
'subnet_get',)})
|
||||
def test_subnet_update_post_gw_inconsistent(self):
|
||||
subnet = self.subnets.first()
|
||||
api.quantum.subnet_get(IsA(http.HttpRequest), subnet.id)\
|
||||
.AndReturn(subnet)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
# dummy IPv6 address
|
||||
gateway_ip = '2001:0DB8:0:CD30:123:4567:89AB:CDEF'
|
||||
formData = {'network_id': subnet.network_id,
|
||||
'tenant_id': subnet.tenant_id,
|
||||
'subnet_id': subnet.id,
|
||||
'name': subnet.name,
|
||||
'cidr': subnet.cidr,
|
||||
'ip_version': subnet.ip_version,
|
||||
'gateway_ip': gateway_ip}
|
||||
url = reverse('horizon:syspanel:networks:editsubnet',
|
||||
args=[subnet.network_id, subnet.id])
|
||||
res = self.client.post(url, formData)
|
||||
|
||||
self.assertContains(res, 'Gateway IP and IP version are inconsistent.')
|
||||
|
||||
@test.create_stubs({api.quantum: ('subnet_delete',
|
||||
'subnet_list',
|
||||
'port_list',)})
|
||||
def test_subnet_delete(self):
|
||||
subnet = self.subnets.first()
|
||||
network_id = subnet.network_id
|
||||
api.quantum.subnet_delete(IsA(http.HttpRequest), subnet.id)
|
||||
api.quantum.subnet_list(IsA(http.HttpRequest), network_id=network_id)\
|
||||
.AndReturn([self.subnets.first()])
|
||||
api.quantum.port_list(IsA(http.HttpRequest), network_id=network_id)\
|
||||
.AndReturn([self.ports.first()])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
formData = {'action': 'subnets__delete__%s' % subnet.id}
|
||||
url = reverse('horizon:syspanel:networks:detail',
|
||||
args=[network_id])
|
||||
res = self.client.post(url, formData)
|
||||
|
||||
self.assertRedirectsNoFollow(res, url)
|
||||
|
||||
@test.create_stubs({api.quantum: ('subnet_delete',
|
||||
'subnet_list',
|
||||
'port_list',)})
|
||||
def test_subnet_delete_exception(self):
|
||||
subnet = self.subnets.first()
|
||||
network_id = subnet.network_id
|
||||
api.quantum.subnet_delete(IsA(http.HttpRequest), subnet.id)\
|
||||
.AndRaise(self.exceptions.quantum)
|
||||
api.quantum.subnet_list(IsA(http.HttpRequest), network_id=network_id)\
|
||||
.AndReturn([self.subnets.first()])
|
||||
api.quantum.port_list(IsA(http.HttpRequest), network_id=network_id)\
|
||||
.AndReturn([self.ports.first()])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
formData = {'action': 'subnets__delete__%s' % subnet.id}
|
||||
url = reverse('horizon:syspanel:networks:detail',
|
||||
args=[network_id])
|
||||
res = self.client.post(url, formData)
|
||||
|
||||
self.assertRedirectsNoFollow(res, url)
|
||||
|
||||
@test.create_stubs({api.quantum: ('port_get',)})
|
||||
def test_port_detail(self):
|
||||
port = self.ports.first()
|
||||
api.quantum.port_get(IsA(http.HttpRequest), port.id)\
|
||||
.AndReturn(self.ports.first())
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('horizon:syspanel:networks:ports:detail',
|
||||
args=[port.id]))
|
||||
|
||||
self.assertTemplateUsed(res, 'nova/networks/ports/detail.html')
|
||||
self.assertEqual(res.context['port'].id, port.id)
|
||||
|
||||
@test.create_stubs({api.quantum: ('port_get',)})
|
||||
def test_port_detail_exception(self):
|
||||
port = self.ports.first()
|
||||
api.quantum.port_get(IsA(http.HttpRequest), port.id)\
|
||||
.AndRaise(self.exceptions.quantum)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('horizon:syspanel:networks:ports:detail',
|
||||
args=[port.id]))
|
||||
|
||||
# syspanel DetailView is shared with userpanel one, so
|
||||
# redirection URL on error is userpanel index.
|
||||
redir_url = reverse('horizon:nova:networks:index')
|
||||
self.assertRedirectsNoFollow(res, redir_url)
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_get',)})
|
||||
def test_port_create_get(self):
|
||||
network = self.networks.first()
|
||||
api.quantum.network_get(IsA(http.HttpRequest),
|
||||
network.id)\
|
||||
.AndReturn(self.networks.first())
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('horizon:syspanel:networks:addport',
|
||||
args=[network.id])
|
||||
res = self.client.get(url)
|
||||
|
||||
self.assertTemplateUsed(res, 'syspanel/networks/ports/create.html')
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_get',
|
||||
'port_create')})
|
||||
def test_port_create_post(self):
|
||||
network = self.networks.first()
|
||||
port = self.ports.first()
|
||||
api.quantum.network_get(IsA(http.HttpRequest),
|
||||
network.id)\
|
||||
.AndReturn(self.networks.first())
|
||||
api.quantum.network_get(IsA(http.HttpRequest),
|
||||
network.id)\
|
||||
.AndReturn(self.networks.first())
|
||||
api.quantum.port_create(IsA(http.HttpRequest),
|
||||
tenant_id=network.tenant_id,
|
||||
network_id=network.id,
|
||||
network_name=network.name,
|
||||
name=port.name,
|
||||
device_id=port.device_id)\
|
||||
.AndReturn(port)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'network_id': port.network_id,
|
||||
'network_name': network.name,
|
||||
'name': port.name,
|
||||
'device_id': port.device_id}
|
||||
url = reverse('horizon:syspanel:networks:addport',
|
||||
args=[port.network_id])
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
self.assertNoFormErrors(res)
|
||||
redir_url = reverse('horizon:syspanel:networks:detail',
|
||||
args=[port.network_id])
|
||||
self.assertRedirectsNoFollow(res, redir_url)
|
||||
|
||||
@test.create_stubs({api.quantum: ('network_get',
|
||||
'port_create')})
|
||||
def test_port_create_post_exception(self):
|
||||
network = self.networks.first()
|
||||
port = self.ports.first()
|
||||
api.quantum.network_get(IsA(http.HttpRequest),
|
||||
network.id)\
|
||||
.AndReturn(self.networks.first())
|
||||
api.quantum.network_get(IsA(http.HttpRequest),
|
||||
network.id)\
|
||||
.AndReturn(self.networks.first())
|
||||
api.quantum.port_create(IsA(http.HttpRequest),
|
||||
tenant_id=network.tenant_id,
|
||||
network_id=network.id,
|
||||
network_name=network.name,
|
||||
name=port.name,
|
||||
device_id=port.device_id)\
|
||||
.AndRaise(self.exceptions.quantum)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
form_data = {'network_id': port.network_id,
|
||||
'network_name': network.name,
|
||||
'name': port.name,
|
||||
'device_id': port.device_id}
|
||||
url = reverse('horizon:syspanel:networks:addport',
|
||||
args=[port.network_id])
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
self.assertNoFormErrors(res)
|
||||
redir_url = reverse('horizon:syspanel:networks:detail',
|
||||
args=[port.network_id])
|
||||
self.assertRedirectsNoFollow(res, redir_url)
|
||||
|
||||
@test.create_stubs({api.quantum: ('port_get',)})
|
||||
def test_port_update_get(self):
|
||||
port = self.ports.first()
|
||||
api.quantum.port_get(IsA(http.HttpRequest),
|
||||
port.id)\
|
||||
.AndReturn(port)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('horizon:syspanel:networks:editport',
|
||||
args=[port.network_id, port.id])
|
||||
res = self.client.get(url)
|
||||
|
||||
self.assertTemplateUsed(res, 'syspanel/networks/ports/update.html')
|
||||
|
||||
@test.create_stubs({api.quantum: ('port_get',
|
||||
'port_modify')})
|
||||
def test_port_update_post(self):
|
||||
port = self.ports.first()
|
||||
api.quantum.port_get(IsA(http.HttpRequest), port.id)\
|
||||
.AndReturn(port)
|
||||
api.quantum.port_modify(IsA(http.HttpRequest), port.id,
|
||||
name=port.name, device_id=port.device_id)\
|
||||
.AndReturn(port)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
formData = {'tenant_id': port.tenant_id,
|
||||
'network_id': port.network_id,
|
||||
'port_id': port.id,
|
||||
'name': port.name,
|
||||
'device_id': port.device_id}
|
||||
url = reverse('horizon:syspanel:networks:editport',
|
||||
args=[port.network_id, port.id])
|
||||
res = self.client.post(url, formData)
|
||||
|
||||
redir_url = reverse('horizon:syspanel:networks:detail',
|
||||
args=[port.network_id])
|
||||
self.assertRedirectsNoFollow(res, redir_url)
|
||||
|
||||
@test.create_stubs({api.quantum: ('port_get',
|
||||
'port_modify')})
|
||||
def test_port_update_post_exception(self):
|
||||
port = self.ports.first()
|
||||
api.quantum.port_get(IsA(http.HttpRequest), port.id)\
|
||||
.AndReturn(port)
|
||||
api.quantum.port_modify(IsA(http.HttpRequest), port.id,
|
||||
name=port.name, device_id=port.device_id)\
|
||||
.AndRaise(self.exceptions.quantum)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
formData = {'tenant_id': port.tenant_id,
|
||||
'network_id': port.network_id,
|
||||
'port_id': port.id,
|
||||
'name': port.name,
|
||||
'device_id': port.device_id}
|
||||
url = reverse('horizon:syspanel:networks:editport',
|
||||
args=[port.network_id, port.id])
|
||||
res = self.client.post(url, formData)
|
||||
|
||||
redir_url = reverse('horizon:syspanel:networks:detail',
|
||||
args=[port.network_id])
|
||||
self.assertRedirectsNoFollow(res, redir_url)
|
||||
|
||||
@test.create_stubs({api.quantum: ('port_delete',
|
||||
'subnet_list',
|
||||
'port_list',)})
|
||||
def test_port_delete(self):
|
||||
port = self.ports.first()
|
||||
network_id = port.network_id
|
||||
api.quantum.port_delete(IsA(http.HttpRequest), port.id)
|
||||
api.quantum.subnet_list(IsA(http.HttpRequest), network_id=network_id)\
|
||||
.AndReturn([self.subnets.first()])
|
||||
api.quantum.port_list(IsA(http.HttpRequest), network_id=network_id)\
|
||||
.AndReturn([self.ports.first()])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
formData = {'action': 'ports__delete__%s' % port.id}
|
||||
url = reverse('horizon:syspanel:networks:detail',
|
||||
args=[network_id])
|
||||
res = self.client.post(url, formData)
|
||||
|
||||
self.assertRedirectsNoFollow(res, url)
|
||||
|
||||
@test.create_stubs({api.quantum: ('port_delete',
|
||||
'subnet_list',
|
||||
'port_list',)})
|
||||
def test_port_delete_exception(self):
|
||||
port = self.ports.first()
|
||||
network_id = port.network_id
|
||||
api.quantum.port_delete(IsA(http.HttpRequest), port.id)\
|
||||
.AndRaise(self.exceptions.quantum)
|
||||
api.quantum.subnet_list(IsA(http.HttpRequest), network_id=network_id)\
|
||||
.AndReturn([self.subnets.first()])
|
||||
api.quantum.port_list(IsA(http.HttpRequest), network_id=network_id)\
|
||||
.AndReturn([self.ports.first()])
|
||||
self.mox.ReplayAll()
|
||||
|
||||
formData = {'action': 'ports__delete__%s' % port.id}
|
||||
url = reverse('horizon:syspanel:networks:detail',
|
||||
args=[network_id])
|
||||
res = self.client.post(url, formData)
|
||||
|
||||
self.assertRedirectsNoFollow(res, url)
|
45
horizon/dashboards/syspanel/networks/urls.py
Normal file
45
horizon/dashboards/syspanel/networks/urls.py
Normal file
@ -0,0 +1,45 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
from django.conf.urls.defaults import patterns, url, include
|
||||
from .views import IndexView, CreateView, DetailView, UpdateView
|
||||
|
||||
from .subnets.views import CreateView as AddSubnetView
|
||||
from .subnets.views import UpdateView as EditSubnetView
|
||||
from .ports.views import CreateView as AddPortView
|
||||
from .ports.views import UpdateView as EditPortView
|
||||
|
||||
from .subnets import urls as subnet_urls
|
||||
from .ports import urls as port_urls
|
||||
|
||||
NETWORKS = r'^(?P<network_id>[^/]+)/%s$'
|
||||
|
||||
urlpatterns = patterns('',
|
||||
url(r'^$', IndexView.as_view(), name='index'),
|
||||
url(r'^create/$', CreateView.as_view(), name='create'),
|
||||
url(NETWORKS % 'update', UpdateView.as_view(), name='update'),
|
||||
# for detail view
|
||||
url(NETWORKS % 'detail', DetailView.as_view(), name='detail'),
|
||||
url(NETWORKS % 'subnets/create', AddSubnetView.as_view(),
|
||||
name='addsubnet'),
|
||||
url(NETWORKS % 'ports/create', AddPortView.as_view(), name='addport'),
|
||||
url(r'^(?P<network_id>[^/]+)/subnets/(?P<subnet_id>[^/]+)/update$',
|
||||
EditSubnetView.as_view(), name='editsubnet'),
|
||||
url(r'^(?P<network_id>[^/]+)/ports/(?P<port_id>[^/]+)/update$',
|
||||
EditPortView.as_view(), name='editport'),
|
||||
|
||||
url(r'^subnets/', include(subnet_urls, namespace='subnets')),
|
||||
url(r'^ports/', include(port_urls, namespace='ports')))
|
133
horizon/dashboards/syspanel/networks/views.py
Normal file
133
horizon/dashboards/syspanel/networks/views.py
Normal file
@ -0,0 +1,133 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.datastructures import SortedDict
|
||||
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
from horizon import tables
|
||||
|
||||
from .tables import NetworksTable
|
||||
from .subnets.tables import SubnetsTable
|
||||
from .ports.tables import PortsTable
|
||||
from .forms import CreateNetwork, UpdateNetwork
|
||||
|
||||
from horizon.dashboards.nova.networks import views as user_views
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IndexView(tables.DataTableView):
|
||||
table_class = NetworksTable
|
||||
template_name = 'syspanel/networks/index.html'
|
||||
|
||||
def _get_tenant_list(self):
|
||||
if not hasattr(self, "_tenants"):
|
||||
try:
|
||||
tenants = api.keystone.tenant_list(self.request, admin=True)
|
||||
except:
|
||||
tenants = []
|
||||
msg = _('Unable to retrieve instance tenant information.')
|
||||
exceptions.handle(self.request, msg)
|
||||
|
||||
tenant_dict = SortedDict([(t.id, t) for t in tenants])
|
||||
self._tenants = tenant_dict
|
||||
return self._tenants
|
||||
|
||||
def get_data(self):
|
||||
try:
|
||||
networks = api.quantum.network_list(self.request)
|
||||
except:
|
||||
networks = []
|
||||
msg = _('Network list can not be retrieved.')
|
||||
exceptions.handle(self.request, msg)
|
||||
if networks:
|
||||
tenant_dict = self._get_tenant_list()
|
||||
for n in networks:
|
||||
# Set tenant name
|
||||
tenant = tenant_dict.get(n.tenant_id, None)
|
||||
n.tenant_name = getattr(tenant, 'name', None)
|
||||
# If name is empty use UUID as name
|
||||
n.set_id_as_name_if_empty()
|
||||
return networks
|
||||
|
||||
|
||||
class CreateView(forms.ModalFormView):
|
||||
form_class = CreateNetwork
|
||||
template_name = 'syspanel/networks/create.html'
|
||||
success_url = reverse_lazy('horizon:syspanel:networks:index')
|
||||
|
||||
|
||||
class DetailView(tables.MultiTableView):
|
||||
table_classes = (SubnetsTable, PortsTable)
|
||||
template_name = 'nova/networks/detail.html'
|
||||
failure_url = reverse_lazy('horizon:syspanel:networks:index')
|
||||
|
||||
def get_subnets_data(self):
|
||||
try:
|
||||
network_id = self.kwargs['network_id']
|
||||
subnets = api.quantum.subnet_list(self.request,
|
||||
network_id=network_id)
|
||||
except:
|
||||
subnets = []
|
||||
msg = _('Subnet list can not be retrieved.')
|
||||
exceptions.handle(self.request, msg)
|
||||
for s in subnets:
|
||||
s.set_id_as_name_if_empty()
|
||||
return subnets
|
||||
|
||||
def get_ports_data(self):
|
||||
try:
|
||||
network_id = self.kwargs['network_id']
|
||||
ports = api.quantum.port_list(self.request, network_id=network_id)
|
||||
except:
|
||||
ports = []
|
||||
msg = _('Port list can not be retrieved.')
|
||||
exceptions.handle(self.request, msg)
|
||||
for p in ports:
|
||||
p.set_id_as_name_if_empty()
|
||||
return ports
|
||||
|
||||
def _get_data(self):
|
||||
if not hasattr(self, "_network"):
|
||||
try:
|
||||
network_id = self.kwargs['network_id']
|
||||
network = api.quantum.network_get(self.request, network_id)
|
||||
network.set_id_as_name_if_empty(length=0)
|
||||
except:
|
||||
redirect = self.failure_url
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve details for '
|
||||
'network "%s".') % network_id,
|
||||
redirect=redirect)
|
||||
self._network = network
|
||||
return self._network
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DetailView, self).get_context_data(**kwargs)
|
||||
context["network"] = self._get_data()
|
||||
return context
|
||||
|
||||
|
||||
class UpdateView(user_views.UpdateView):
|
||||
form_class = UpdateNetwork
|
||||
template_name = 'syspanel/networks/update.html'
|
||||
success_url = reverse_lazy('horizon:syspanel:networks:index')
|
@ -734,7 +734,7 @@ class DataTableOptions(object):
|
||||
self.table_actions_template = \
|
||||
'horizon/common/_data_table_table_actions.html'
|
||||
self.context_var_name = unicode(getattr(options,
|
||||
'context_var_nam',
|
||||
'context_var_name',
|
||||
'table'))
|
||||
self.actions_column = getattr(options,
|
||||
'actions_column',
|
||||
|
@ -48,7 +48,7 @@
|
||||
<a href="?{{ table.get_pagination_string }}">More »</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
{% endwith %}
|
||||
|
@ -2,12 +2,12 @@
|
||||
<table class="table-fixed">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="help_text">
|
||||
{{ step.get_help_text }}
|
||||
</td>
|
||||
<td class="actions">
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</td>
|
||||
<td class="help_text">
|
||||
{{ step.get_help_text }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -35,6 +35,7 @@ from django.utils import unittest
|
||||
import glanceclient
|
||||
from keystoneclient.v2_0 import client as keystone_client
|
||||
from novaclient.v1_1 import client as nova_client
|
||||
from quantumclient.v2_0 import client as quantum_client
|
||||
from selenium.webdriver.firefox.webdriver import WebDriver
|
||||
|
||||
import httplib2
|
||||
@ -290,17 +291,20 @@ class APITestCase(TestCase):
|
||||
self._original_glanceclient = api.glance.glanceclient
|
||||
self._original_keystoneclient = api.keystone.keystoneclient
|
||||
self._original_novaclient = api.nova.novaclient
|
||||
self._original_quantumclient = api.quantum.quantumclient
|
||||
|
||||
# Replace the clients with our stubs.
|
||||
api.glance.glanceclient = lambda request: self.stub_glanceclient()
|
||||
api.keystone.keystoneclient = fake_keystoneclient
|
||||
api.nova.novaclient = lambda request: self.stub_novaclient()
|
||||
api.quantum.quantumclient = lambda request: self.stub_quantumclient()
|
||||
|
||||
def tearDown(self):
|
||||
super(APITestCase, self).tearDown()
|
||||
api.glance.glanceclient = self._original_glanceclient
|
||||
api.nova.novaclient = self._original_novaclient
|
||||
api.keystone.keystoneclient = self._original_keystoneclient
|
||||
api.quantum.quantumclient = self._original_quantumclient
|
||||
|
||||
def stub_novaclient(self):
|
||||
if not hasattr(self, "novaclient"):
|
||||
@ -320,6 +324,12 @@ class APITestCase(TestCase):
|
||||
self.glanceclient = self.mox.CreateMock(glanceclient.Client)
|
||||
return self.glanceclient
|
||||
|
||||
def stub_quantumclient(self):
|
||||
if not hasattr(self, "quantumclient"):
|
||||
self.mox.StubOutWithMock(quantum_client, 'Client')
|
||||
self.quantumclient = self.mox.CreateMock(quantum_client.Client)
|
||||
return self.quantumclient
|
||||
|
||||
def stub_swiftclient(self, expected_calls=1):
|
||||
if not hasattr(self, "swiftclient"):
|
||||
self.mox.StubOutWithMock(swift_client, 'Connection')
|
||||
|
205
horizon/tests/api_tests/quantum_tests.py
Normal file
205
horizon/tests/api_tests/quantum_tests.py
Normal file
@ -0,0 +1,205 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 NEC Corporation
|
||||
#
|
||||
# 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.
|
||||
|
||||
from horizon import api
|
||||
from horizon import test
|
||||
|
||||
|
||||
class QuantumApiTests(test.APITestCase):
|
||||
def test_network_list(self):
|
||||
networks = {'networks': self.api_networks.list()}
|
||||
subnets = {'subnets': self.api_subnets.list()}
|
||||
|
||||
quantumclient = self.stub_quantumclient()
|
||||
quantumclient.list_networks().AndReturn(networks)
|
||||
quantumclient.list_subnets().AndReturn(subnets)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
ret_val = api.quantum.network_list(self.request)
|
||||
for n in ret_val:
|
||||
self.assertIsInstance(n, api.quantum.Network)
|
||||
|
||||
def test_network_get(self):
|
||||
network = {'network': self.api_networks.first()}
|
||||
subnet = {'subnet': self.api_subnets.first()}
|
||||
network_id = self.api_networks.first()['id']
|
||||
subnet_id = self.api_networks.first()['subnets'][0]
|
||||
|
||||
quantumclient = self.stub_quantumclient()
|
||||
quantumclient.show_network(network_id).AndReturn(network)
|
||||
quantumclient.show_subnet(subnet_id).AndReturn(subnet)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
ret_val = api.quantum.network_get(self.request, network_id)
|
||||
self.assertIsInstance(ret_val, api.quantum.Network)
|
||||
|
||||
def test_network_create(self):
|
||||
network = {'network': self.api_networks.first()}
|
||||
|
||||
quantumclient = self.stub_quantumclient()
|
||||
form_data = {'network': {'name': 'net1'}}
|
||||
quantumclient.create_network(body=form_data).AndReturn(network)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
ret_val = api.quantum.network_create(self.request, name='net1')
|
||||
self.assertIsInstance(ret_val, api.quantum.Network)
|
||||
|
||||
def test_network_modify(self):
|
||||
network = {'network': self.api_networks.first()}
|
||||
network_id = self.api_networks.first()['id']
|
||||
|
||||
quantumclient = self.stub_quantumclient()
|
||||
form_data = {'network': {'name': 'net1'}}
|
||||
quantumclient.update_network(network_id, body=form_data)\
|
||||
.AndReturn(network)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
ret_val = api.quantum.network_modify(self.request, network_id,
|
||||
name='net1')
|
||||
self.assertIsInstance(ret_val, api.quantum.Network)
|
||||
|
||||
def test_network_delete(self):
|
||||
network_id = self.api_networks.first()['id']
|
||||
|
||||
quantumclient = self.stub_quantumclient()
|
||||
quantumclient.delete_network(network_id)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
api.quantum.network_delete(self.request, network_id)
|
||||
|
||||
def test_subnet_list(self):
|
||||
subnets = {'subnets': self.api_subnets.list()}
|
||||
|
||||
quantumclient = self.stub_quantumclient()
|
||||
quantumclient.list_subnets().AndReturn(subnets)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
ret_val = api.quantum.subnet_list(self.request)
|
||||
for n in ret_val:
|
||||
self.assertIsInstance(n, api.quantum.Subnet)
|
||||
|
||||
def test_subnet_get(self):
|
||||
subnet = {'subnet': self.api_subnets.first()}
|
||||
subnet_id = self.api_subnets.first()['id']
|
||||
|
||||
quantumclient = self.stub_quantumclient()
|
||||
quantumclient.show_subnet(subnet_id).AndReturn(subnet)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
ret_val = api.quantum.subnet_get(self.request, subnet_id)
|
||||
self.assertIsInstance(ret_val, api.quantum.Subnet)
|
||||
|
||||
def test_subnet_create(self):
|
||||
subnet_data = self.api_subnets.first()
|
||||
params = {'network_id': subnet_data['network_id'],
|
||||
'tenant_id': subnet_data['tenant_id'],
|
||||
'name': subnet_data['name'],
|
||||
'cidr': subnet_data['cidr'],
|
||||
'ip_version': subnet_data['ip_version'],
|
||||
'gateway_ip': subnet_data['gateway_ip']}
|
||||
|
||||
quantumclient = self.stub_quantumclient()
|
||||
quantumclient.create_subnet(body={'subnet': params})\
|
||||
.AndReturn({'subnet': subnet_data})
|
||||
self.mox.ReplayAll()
|
||||
|
||||
ret_val = api.quantum.subnet_create(self.request, **params)
|
||||
self.assertIsInstance(ret_val, api.quantum.Subnet)
|
||||
|
||||
def test_subnet_modify(self):
|
||||
subnet_data = self.api_subnets.first()
|
||||
subnet_id = subnet_data['id']
|
||||
params = {'name': subnet_data['name'],
|
||||
'gateway_ip': subnet_data['gateway_ip']}
|
||||
|
||||
quantumclient = self.stub_quantumclient()
|
||||
quantumclient.update_subnet(subnet_id, body={'subnet': params})\
|
||||
.AndReturn({'subnet': subnet_data})
|
||||
self.mox.ReplayAll()
|
||||
|
||||
ret_val = api.quantum.subnet_modify(self.request, subnet_id, **params)
|
||||
self.assertIsInstance(ret_val, api.quantum.Subnet)
|
||||
|
||||
def test_subnet_delete(self):
|
||||
subnet_id = self.api_subnets.first()['id']
|
||||
|
||||
quantumclient = self.stub_quantumclient()
|
||||
quantumclient.delete_subnet(subnet_id)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
api.quantum.subnet_delete(self.request, subnet_id)
|
||||
|
||||
def test_port_list(self):
|
||||
ports = {'ports': self.api_ports.list()}
|
||||
|
||||
quantumclient = self.stub_quantumclient()
|
||||
quantumclient.list_ports().AndReturn(ports)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
ret_val = api.quantum.port_list(self.request)
|
||||
for p in ret_val:
|
||||
self.assertIsInstance(p, api.quantum.Port)
|
||||
|
||||
def test_port_get(self):
|
||||
port = {'port': self.api_ports.first()}
|
||||
port_id = self.api_ports.first()['id']
|
||||
|
||||
quantumclient = self.stub_quantumclient()
|
||||
quantumclient.show_port(port_id).AndReturn(port)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
ret_val = api.quantum.port_get(self.request, port_id)
|
||||
self.assertIsInstance(ret_val, api.quantum.Port)
|
||||
|
||||
def test_port_create(self):
|
||||
port_data = self.api_ports.first()
|
||||
params = {'network_id': port_data['network_id'],
|
||||
'tenant_id': port_data['tenant_id'],
|
||||
'name': port_data['name'],
|
||||
'device_id': port_data['device_id']}
|
||||
|
||||
quantumclient = self.stub_quantumclient()
|
||||
quantumclient.create_port(body={'port': params})\
|
||||
.AndReturn({'port': port_data})
|
||||
self.mox.ReplayAll()
|
||||
|
||||
ret_val = api.quantum.port_create(self.request, **params)
|
||||
self.assertIsInstance(ret_val, api.quantum.Port)
|
||||
self.assertEqual(ret_val.id, api.quantum.Port(port_data).id)
|
||||
|
||||
def test_port_modify(self):
|
||||
port_data = self.api_ports.first()
|
||||
port_id = port_data['id']
|
||||
params = {'name': port_data['name'],
|
||||
'device_id': port_data['device_id']}
|
||||
|
||||
quantumclient = self.stub_quantumclient()
|
||||
quantumclient.update_port(port_id, body={'port': params})\
|
||||
.AndReturn({'port': port_data})
|
||||
self.mox.ReplayAll()
|
||||
|
||||
ret_val = api.quantum.port_modify(self.request, port_id, **params)
|
||||
self.assertIsInstance(ret_val, api.quantum.Port)
|
||||
self.assertEqual(ret_val.id, api.quantum.Port(port_data).id)
|
||||
|
||||
def test_port_delete(self):
|
||||
port_id = self.api_ports.first()['id']
|
||||
|
||||
quantumclient = self.stub_quantumclient()
|
||||
quantumclient.delete_port(port_id)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
api.quantum.port_delete(self.request, port_id)
|
@ -15,6 +15,7 @@
|
||||
import glanceclient.exc as glance_exceptions
|
||||
from keystoneclient import exceptions as keystone_exceptions
|
||||
from novaclient import exceptions as nova_exceptions
|
||||
from quantumclient.common import exceptions as quantum_exceptions
|
||||
|
||||
from .utils import TestDataContainer
|
||||
|
||||
@ -53,3 +54,6 @@ def data(TEST):
|
||||
|
||||
glance_exception = glance_exceptions.ClientException
|
||||
TEST.exceptions.glance = create_stubbed_exception(glance_exception)
|
||||
|
||||
quantum_exception = quantum_exceptions.QuantumClientException
|
||||
TEST.exceptions.quantum = create_stubbed_exception(quantum_exception)
|
||||
|
109
horizon/tests/test_data/quantum_data.py
Normal file
109
horizon/tests/test_data/quantum_data.py
Normal file
@ -0,0 +1,109 @@
|
||||
# Copyright 2012 Nebula, Inc.
|
||||
#
|
||||
# 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.
|
||||
|
||||
import copy
|
||||
|
||||
from horizon.api.quantum import Network, Subnet, Port
|
||||
|
||||
from .utils import TestDataContainer
|
||||
|
||||
|
||||
def data(TEST):
|
||||
# data returned by horizon.api.quantum wrapper
|
||||
TEST.networks = TestDataContainer()
|
||||
TEST.subnets = TestDataContainer()
|
||||
TEST.ports = TestDataContainer()
|
||||
|
||||
# data return by quantumclient
|
||||
TEST.api_networks = TestDataContainer()
|
||||
TEST.api_subnets = TestDataContainer()
|
||||
TEST.api_ports = TestDataContainer()
|
||||
|
||||
# 1st network
|
||||
network_dict = {'admin_state_up': True,
|
||||
'id': '82288d84-e0a5-42ac-95be-e6af08727e42',
|
||||
'name': 'net1',
|
||||
'status': 'ACTIVE',
|
||||
'subnets': ['e8abc972-eb0c-41f1-9edd-4bc6e3bcd8c9'],
|
||||
'tenant_id': '1'}
|
||||
subnet_dict = {'allocation_pools': [{'end': '10.0.0.254',
|
||||
'start': '10.0.0.2'}],
|
||||
'cidr': '10.0.0.0/24',
|
||||
'enable_dhcp': True,
|
||||
'gateway_ip': '10.0.0.1',
|
||||
'id': network_dict['subnets'][0],
|
||||
'ip_version': 4,
|
||||
'name': 'mysubnet1',
|
||||
'network_id': network_dict['id'],
|
||||
'tenant_id': network_dict['tenant_id']}
|
||||
port_dict = {'admin_state_up': True,
|
||||
'device_id': 'af75c8e5-a1cc-4567-8d04-44fcd6922890',
|
||||
'fixed_ips': [{'ip_address': '10.0.0.3',
|
||||
'subnet_id': subnet_dict['id']}],
|
||||
'id': '3ec7f3db-cb2f-4a34-ab6b-69a64d3f008c',
|
||||
'mac_address': 'fa:16:3e:9c:d5:7e',
|
||||
'name': '',
|
||||
'network_id': network_dict['id'],
|
||||
'status': 'ACTIVE',
|
||||
'tenant_id': network_dict['tenant_id']}
|
||||
|
||||
TEST.api_networks.add(network_dict)
|
||||
TEST.api_subnets.add(subnet_dict)
|
||||
TEST.api_ports.add(port_dict)
|
||||
|
||||
network = copy.deepcopy(network_dict)
|
||||
subnet = Subnet(subnet_dict)
|
||||
network['subnets'] = [subnet]
|
||||
TEST.networks.add(Network(network))
|
||||
TEST.subnets.add(subnet)
|
||||
TEST.ports.add(Port(port_dict))
|
||||
|
||||
# 2nd network
|
||||
network_dict = {'admin_state_up': True,
|
||||
'id': '72c3ab6c-c80f-4341-9dc5-210fa31ac6c2',
|
||||
'name': 'net2',
|
||||
'status': 'ACTIVE',
|
||||
'subnets': ['3f7c5d79-ee55-47b0-9213-8e669fb03009'],
|
||||
'tenant_id': '2'}
|
||||
subnet_dict = {'allocation_pools': [{'end': '172.16.88.254',
|
||||
'start': '172.16.88.2'}],
|
||||
'cidr': '172.16.88.0/24',
|
||||
'enable_dhcp': True,
|
||||
'gateway_ip': '172.16.88.1',
|
||||
'id': '3f7c5d79-ee55-47b0-9213-8e669fb03009',
|
||||
'ip_version': 4,
|
||||
'name': 'aaaa',
|
||||
'network_id': network_dict['id'],
|
||||
'tenant_id': network_dict['tenant_id']}
|
||||
port_dict = {'admin_state_up': True,
|
||||
'device_id': '40e536b1-b9fd-4eb7-82d6-84db5d65a2ac',
|
||||
'fixed_ips': [{'ip_address': '172.16.88.3',
|
||||
'subnet_id': subnet_dict['id']}],
|
||||
'id': '7e6ce62c-7ea2-44f8-b6b4-769af90a8406',
|
||||
'mac_address': 'fa:16:3e:56:e6:2f',
|
||||
'name': '',
|
||||
'network_id': network_dict['id'],
|
||||
'status': 'ACTIVE',
|
||||
'tenant_id': network_dict['tenant_id']}
|
||||
|
||||
TEST.api_networks.add(network_dict)
|
||||
TEST.api_subnets.add(subnet_dict)
|
||||
TEST.api_ports.add(port_dict)
|
||||
|
||||
network = copy.deepcopy(network_dict)
|
||||
subnet = Subnet(subnet_dict)
|
||||
network['subnets'] = [subnet]
|
||||
TEST.networks.add(Network(network))
|
||||
TEST.subnets.add(subnet)
|
||||
TEST.ports.add(Port(port_dict))
|
@ -18,6 +18,7 @@ def load_test_data(load_onto=None):
|
||||
from . import glance_data
|
||||
from . import keystone_data
|
||||
from . import nova_data
|
||||
from . import quantum_data
|
||||
from . import swift_data
|
||||
|
||||
# The order of these loaders matters, some depend on others.
|
||||
@ -25,6 +26,7 @@ def load_test_data(load_onto=None):
|
||||
keystone_data.data,
|
||||
glance_data.data,
|
||||
nova_data.data,
|
||||
quantum_data.data,
|
||||
swift_data.data)
|
||||
if load_onto:
|
||||
for data_func in loaders:
|
||||
|
@ -22,6 +22,7 @@ from cloudfiles import errors as swiftclient
|
||||
from glanceclient.common import exceptions as glanceclient
|
||||
from keystoneclient import exceptions as keystoneclient
|
||||
from novaclient import exceptions as novaclient
|
||||
from quantumclient.common import exceptions as quantumclient
|
||||
|
||||
|
||||
UNAUTHORIZED = (keystoneclient.Unauthorized,
|
||||
@ -29,12 +30,16 @@ UNAUTHORIZED = (keystoneclient.Unauthorized,
|
||||
novaclient.Unauthorized,
|
||||
novaclient.Forbidden,
|
||||
glanceclient.Unauthorized,
|
||||
quantumclient.Unauthorized,
|
||||
quantumclient.Forbidden,
|
||||
swiftclient.AuthenticationFailed,
|
||||
swiftclient.AuthenticationError)
|
||||
|
||||
NOT_FOUND = (keystoneclient.NotFound,
|
||||
novaclient.NotFound,
|
||||
glanceclient.NotFound,
|
||||
quantumclient.NetworkNotFoundClient,
|
||||
quantumclient.PortNotFoundClient,
|
||||
swiftclient.NoSuchContainer,
|
||||
swiftclient.NoSuchObject)
|
||||
|
||||
@ -44,4 +49,12 @@ RECOVERABLE = (keystoneclient.ClientException,
|
||||
keystoneclient.AuthorizationFailure,
|
||||
novaclient.ClientException,
|
||||
glanceclient.ClientException,
|
||||
# NOTE(amotoki): Quantum exceptions other than the first one
|
||||
# are recoverable in many cases (e.g., NetworkInUse is not
|
||||
# raised once VMs which use the network are terminated).
|
||||
quantumclient.QuantumClientException,
|
||||
quantumclient.NetworkInUseClient,
|
||||
quantumclient.PortInUseClient,
|
||||
quantumclient.AlreadyAttachedClient,
|
||||
quantumclient.StateInvalidClient,
|
||||
swiftclient.Error)
|
||||
|
@ -701,14 +701,14 @@ form.horizontal fieldset {
|
||||
.workflow td.actions {
|
||||
vertical-align: top;
|
||||
width: 308px;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.workflow td.help_text {
|
||||
vertical-align: top;
|
||||
width: 340px;
|
||||
padding-right: 10px;
|
||||
border-right: 1px solid #DDD;
|
||||
padding-left: 10px;
|
||||
border-left: 1px solid #DDD;
|
||||
}
|
||||
|
||||
.workflow fieldset > table {
|
||||
|
@ -6,6 +6,7 @@ python-cloudfiles
|
||||
python-glanceclient<2
|
||||
python-keystoneclient
|
||||
python-novaclient
|
||||
python-quantumclient
|
||||
pytz
|
||||
|
||||
# Horizon Utility Requirements
|
||||
|
Loading…
Reference in New Issue
Block a user