Removed "networks" panel from Horizon.
While this functionality will eventually be restored to Horizon, when it does it will be in a significantly revamped and improved form. For the Essex release, the amount of work required to get it where it needs to be is too great and so it has regrettably been cut from the current lineup of panels. :-( Change-Id: I9c8b6e31303461a35270bea8d1ed30ad0ae6ed93
This commit is contained in:
parent
ecbd98a0f4
commit
009a5de287
@ -36,4 +36,3 @@ 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 *
|
||||
|
@ -1,132 +0,0 @@
|
||||
# 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 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.
|
||||
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
import urlparse
|
||||
|
||||
from quantum import client as quantum_client
|
||||
|
||||
from horizon.api.base import url_for
|
||||
from horizon.api import nova
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# FIXME(gabriel): Add object wrappers for Quantum client. The quantum client
|
||||
# returns plain dicts (a la Glance) which we should wrap.
|
||||
|
||||
def quantum_api(request):
|
||||
tenant = request.user.tenant_id
|
||||
url = urlparse.urlparse(url_for(request, 'network'))
|
||||
return quantum_client.Client(url.hostname, url.port, False, tenant, 'json')
|
||||
|
||||
|
||||
def quantum_list_networks(request):
|
||||
return quantum_api(request).list_networks()
|
||||
|
||||
|
||||
def quantum_network_details(request, network_id):
|
||||
return quantum_api(request).show_network_details(network_id)
|
||||
|
||||
|
||||
def quantum_list_ports(request, network_id):
|
||||
return quantum_api(request).list_ports(network_id)
|
||||
|
||||
|
||||
def quantum_port_details(request, network_id, port_id):
|
||||
return quantum_api(request).show_port_details(network_id, port_id)
|
||||
|
||||
|
||||
def quantum_create_network(request, data):
|
||||
return quantum_api(request).create_network(data)
|
||||
|
||||
|
||||
def quantum_delete_network(request, network_id):
|
||||
return quantum_api(request).delete_network(network_id)
|
||||
|
||||
|
||||
def quantum_update_network(request, network_id, data):
|
||||
return quantum_api(request).update_network(network_id, data)
|
||||
|
||||
|
||||
def quantum_create_port(request, network_id):
|
||||
return quantum_api(request).create_port(network_id)
|
||||
|
||||
|
||||
def quantum_delete_port(request, network_id, port_id):
|
||||
return quantum_api(request).delete_port(network_id, port_id)
|
||||
|
||||
|
||||
def quantum_attach_port(request, network_id, port_id, data):
|
||||
return quantum_api(request).attach_resource(network_id, port_id, data)
|
||||
|
||||
|
||||
def quantum_detach_port(request, network_id, port_id):
|
||||
return quantum_api(request).detach_resource(network_id, port_id)
|
||||
|
||||
|
||||
def quantum_set_port_state(request, network_id, port_id, data):
|
||||
return quantum_api(request).update_port(network_id, port_id, data)
|
||||
|
||||
|
||||
def quantum_port_attachment(request, network_id, port_id):
|
||||
return quantum_api(request).show_port_attachment(network_id, port_id)
|
||||
|
||||
|
||||
def get_vif_ids(request):
|
||||
vifs = []
|
||||
attached_vifs = []
|
||||
# Get a list of all networks
|
||||
networks_list = quantum_api(request).list_networks()
|
||||
for network in networks_list['networks']:
|
||||
ports = quantum_api(request).list_ports(network['id'])
|
||||
# Get port attachments
|
||||
for port in ports['ports']:
|
||||
port_attachment = quantum_api(request).show_port_attachment(
|
||||
network['id'],
|
||||
port['id'])
|
||||
if port_attachment['attachment']:
|
||||
attached_vifs.append(
|
||||
port_attachment['attachment']['id'].encode('ascii'))
|
||||
# Get all instances
|
||||
instances = nova.server_list(request)
|
||||
# Get virtual interface ids by instance
|
||||
for instance in instances:
|
||||
id = instance.id
|
||||
instance_vifs = nova.virtual_interfaces_list(request, id)
|
||||
for vif in instance_vifs:
|
||||
# Check if this VIF is already connected to any port
|
||||
if str(vif.id) in attached_vifs:
|
||||
vifs.append({
|
||||
'id': vif.id,
|
||||
'instance': instance.id,
|
||||
'instance_name': instance.name,
|
||||
'available': False})
|
||||
else:
|
||||
vifs.append({
|
||||
'id': vif.id,
|
||||
'instance': instance.id,
|
||||
'instance_name': instance.name,
|
||||
'available': True})
|
||||
return vifs
|
@ -26,7 +26,6 @@ class Nova(horizon.Dashboard):
|
||||
'instances_and_volumes',
|
||||
'access_and_security',
|
||||
'images_and_snapshots'),
|
||||
_("Network"): ('networks',),
|
||||
_("Object Store"): ('containers',)}
|
||||
default_panel = 'overview'
|
||||
supports_tenants = True
|
||||
|
@ -1,131 +0,0 @@
|
||||
# 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 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 logging
|
||||
|
||||
from django import shortcuts
|
||||
from django.contrib import messages
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import exceptions
|
||||
from horizon import forms
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CreateNetwork(forms.SelfHandlingForm):
|
||||
name = forms.CharField(required=True, label=_("Network Name"))
|
||||
|
||||
def handle(self, request, data):
|
||||
network_name = data['name']
|
||||
try:
|
||||
data = {'network': {'name': network_name}}
|
||||
api.quantum.quantum_create_network(request, data)
|
||||
messages.success(request,
|
||||
_('Network %s has been created.') % network_name)
|
||||
except:
|
||||
exceptions.handle(request, _("Unable to create network."))
|
||||
return shortcuts.redirect('horizon:nova:networks:index')
|
||||
|
||||
|
||||
class RenameNetwork(forms.SelfHandlingForm):
|
||||
network = forms.CharField(widget=forms.HiddenInput())
|
||||
new_name = forms.CharField(required=True)
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
LOG.info('Renaming network %s to %s' %
|
||||
(data['network'], data['new_name']))
|
||||
send_data = {'network': {'name': '%s' % data['new_name']}}
|
||||
api.quantum_update_network(request, data['network'], send_data)
|
||||
except Exception, e:
|
||||
if not hasattr(e, 'message'):
|
||||
e.message = str(e)
|
||||
messages.error(request,
|
||||
_('Unable to rename network %(network)s: %(msg)s') %
|
||||
{"network": data['network'], "msg": e.message})
|
||||
else:
|
||||
msg = _('Network %(net)s has been renamed to %(new_name)s.') % {
|
||||
"net": data['network'], "new_name": data['new_name']}
|
||||
LOG.info(msg)
|
||||
messages.success(request, msg)
|
||||
|
||||
return shortcuts.redirect('horizon:nova:networks:index')
|
||||
|
||||
|
||||
class CreatePort(forms.SelfHandlingForm):
|
||||
network = forms.CharField(widget=forms.HiddenInput())
|
||||
ports_num = forms.IntegerField(required=True, label=_("Number of Ports"))
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
LOG.info('Creating %s ports on network %s' %
|
||||
(data['ports_num'], data['network']))
|
||||
for i in range(0, data['ports_num']):
|
||||
api.quantum_create_port(request, data['network'])
|
||||
except Exception, e:
|
||||
if not hasattr(e, 'message'):
|
||||
e.message = str(e)
|
||||
messages.error(request,
|
||||
_('Unable to create ports on network %(network)s: %(msg)s') %
|
||||
{"network": data['network'], "msg": e.message})
|
||||
else:
|
||||
msg = _('%(num_ports)s ports created on network %(network)s.') % {
|
||||
"num_ports": data['ports_num'], "network": data['network']}
|
||||
LOG.info(msg)
|
||||
messages.success(request, msg)
|
||||
|
||||
return shortcuts.redirect('horizon:nova:networks:detail',
|
||||
network_id=data['network'])
|
||||
|
||||
|
||||
class AttachPort(forms.SelfHandlingForm):
|
||||
network = forms.CharField(widget=forms.HiddenInput())
|
||||
port = forms.CharField(widget=forms.HiddenInput())
|
||||
vif_id = forms.ChoiceField(label=_("Select VIF to connect"))
|
||||
|
||||
def __init__(self, request, *args, **kwargs):
|
||||
super(AttachPort, self).__init__(*args, **kwargs)
|
||||
# Populate VIF choices
|
||||
vif_choices = [('', "Select a VIF")]
|
||||
for vif in api.get_vif_ids(request):
|
||||
if vif['available']:
|
||||
name = "Instance %s VIF %s" % (vif['instance_name'], vif['id'])
|
||||
vif_choices.append((vif['id'], name,))
|
||||
self.fields['vif_id'].choices = vif_choices
|
||||
|
||||
@classmethod
|
||||
def _instantiate(cls, request, *args, **kwargs):
|
||||
return cls(request, *args, **kwargs)
|
||||
|
||||
def handle(self, request, data):
|
||||
try:
|
||||
body = {'attachment': {'id': '%s' % data['vif_id']}}
|
||||
api.quantum_attach_port(request,
|
||||
data['network'],
|
||||
data['port'],
|
||||
body)
|
||||
messages.success(request, _("Port attached."))
|
||||
except:
|
||||
exceptions.handle(request, _('Unable to attach port.'))
|
||||
return shortcuts.redirect("horizon:nova:networks:detail",
|
||||
data['network'])
|
@ -1,31 +0,0 @@
|
||||
# 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 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 horizon
|
||||
from horizon.dashboards.nova import dashboard
|
||||
|
||||
|
||||
class Networks(horizon.Panel):
|
||||
name = "Networks"
|
||||
slug = 'networks'
|
||||
services = ("network",)
|
||||
|
||||
|
||||
dashboard.Nova.register(Networks)
|
@ -1,118 +0,0 @@
|
||||
import logging
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import api
|
||||
from horizon import tables
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RenameNetworkLink(tables.LinkAction):
|
||||
name = "rename_network"
|
||||
verbose_name = _("Rename Network")
|
||||
url = "horizon:nova:networks:rename"
|
||||
attrs = {"class": "ajax-modal"}
|
||||
|
||||
|
||||
class CreateNetworkLink(tables.LinkAction):
|
||||
name = "create_network"
|
||||
verbose_name = _("Create New Network")
|
||||
url = "horizon:nova:networks:create"
|
||||
classes = ("ajax-modal",)
|
||||
|
||||
|
||||
class DeleteNetworkAction(tables.DeleteAction):
|
||||
data_type_singular = _("Network")
|
||||
data_type_plural = _("Networks")
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
api.quantum_delete_network(request, obj_id)
|
||||
|
||||
|
||||
class NetworksTable(tables.DataTable):
|
||||
id = tables.Column('id', verbose_name=_('Network Id'),
|
||||
link="horizon:nova:networks:detail")
|
||||
name = tables.Column('name', verbose_name=_('Name'))
|
||||
used = tables.Column('used', verbose_name=_('Used'))
|
||||
available = tables.Column('available', verbose_name=_('Available'))
|
||||
total = tables.Column('total', verbose_name=_('Total'))
|
||||
#tenant = tables.Column('tenant', verbose_name=_('Project'))
|
||||
|
||||
def get_object_id(self, datum):
|
||||
return datum['id']
|
||||
|
||||
def get_object_display(self, obj):
|
||||
return obj['name']
|
||||
|
||||
class Meta:
|
||||
name = "networks"
|
||||
verbose_name = _("Networks")
|
||||
row_actions = (DeleteNetworkAction, RenameNetworkLink,)
|
||||
table_actions = (CreateNetworkLink, DeleteNetworkAction,)
|
||||
|
||||
|
||||
class CreatePortLink(tables.LinkAction):
|
||||
name = "create_port"
|
||||
verbose_name = _("Create Ports")
|
||||
url = "horizon:nova:networks:port_create"
|
||||
classes = ("ajax-modal",)
|
||||
|
||||
def get_link_url(self, datum=None):
|
||||
network_id = self.table.kwargs['network_id']
|
||||
return reverse(self.url, args=(network_id,))
|
||||
|
||||
|
||||
class DeletePortAction(tables.DeleteAction):
|
||||
data_type_singular = _("Port")
|
||||
data_type_plural = _("Ports")
|
||||
|
||||
def delete(self, request, obj_id):
|
||||
api.quantum_delete_port(request,
|
||||
self.table.kwargs['network_id'],
|
||||
obj_id)
|
||||
|
||||
|
||||
class DetachPortAction(tables.BatchAction):
|
||||
name = "detach_port"
|
||||
action_present = _("Detach")
|
||||
action_past = _("Detached")
|
||||
data_type_singular = _("Port")
|
||||
data_type_plural = _("Ports")
|
||||
|
||||
def action(self, request, datum_id):
|
||||
body = {'port': {'state': 'DOWN'}}
|
||||
api.quantum_set_port_state(request,
|
||||
self.table.kwargs['network_id'],
|
||||
datum_id, body)
|
||||
|
||||
|
||||
class AttachPortAction(tables.LinkAction):
|
||||
name = "attach_port"
|
||||
verbose_name = _("Attach Port")
|
||||
url = "horizon:nova:networks:port_attach"
|
||||
attrs = {"class": "ajax-modal"}
|
||||
|
||||
def get_link_url(self, datum=None):
|
||||
network_id = self.table.kwargs['network_id']
|
||||
return reverse(self.url, args=(network_id, datum['id']))
|
||||
|
||||
|
||||
class NetworkDetailsTable(tables.DataTable):
|
||||
id = tables.Column('id', verbose_name=_('Port Id'))
|
||||
state = tables.Column('state', verbose_name=_('State'))
|
||||
attachment = tables.Column('attachment', verbose_name=_('Attachment'))
|
||||
|
||||
def get_object_id(self, datum):
|
||||
return datum['id']
|
||||
|
||||
def get_object_display(self, obj):
|
||||
return obj['id']
|
||||
|
||||
class Meta:
|
||||
name = "network_details"
|
||||
verbose_name = _("Network Port Details")
|
||||
row_actions = (DeletePortAction, AttachPortAction, DetachPortAction)
|
||||
table_actions = (CreatePortLink, DeletePortAction,)
|
@ -1,295 +0,0 @@
|
||||
# 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 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.
|
||||
|
||||
from django import http
|
||||
from django.core.urlresolvers import reverse
|
||||
from mox import IsA
|
||||
|
||||
from horizon import api
|
||||
from horizon import test
|
||||
|
||||
|
||||
class NetworkViewTests(test.TestCase):
|
||||
def setUp(self):
|
||||
super(NetworkViewTests, self).setUp()
|
||||
# TODO(gabriel): Move this to horizon.tests.test_data.quantum_data
|
||||
# after the wrapper classes are added for Quantum.
|
||||
self.network = {}
|
||||
self.network['networks'] = []
|
||||
self.network['networks'].append({'id': 'n1'})
|
||||
self.network_details = {'network': {'id': '1', 'name': 'test_network'}}
|
||||
self.ports = {}
|
||||
self.ports['ports'] = []
|
||||
self.ports['ports'].append({'id': 'p1'})
|
||||
self.port_details = {
|
||||
'port': {
|
||||
'id': 'p1',
|
||||
'state': 'DOWN'}}
|
||||
self.port_attachment = {
|
||||
'attachment': {
|
||||
'id': 'vif1'}}
|
||||
self.vifs = [{'id': 'vif1'}]
|
||||
|
||||
def test_network_index(self):
|
||||
self.mox.StubOutWithMock(api, 'quantum_list_networks')
|
||||
api.quantum_list_networks(IsA(http.HttpRequest)).\
|
||||
AndReturn(self.network)
|
||||
|
||||
self.mox.StubOutWithMock(api, 'quantum_network_details')
|
||||
api.quantum_network_details(IsA(http.HttpRequest),
|
||||
'n1').AndReturn(self.network_details)
|
||||
|
||||
self.mox.StubOutWithMock(api, 'quantum_list_ports')
|
||||
api.quantum_list_ports(IsA(http.HttpRequest),
|
||||
'n1').AndReturn(self.ports)
|
||||
|
||||
self.mox.StubOutWithMock(api, 'quantum_port_attachment')
|
||||
api.quantum_port_attachment(IsA(http.HttpRequest),
|
||||
'n1', 'p1').AndReturn(self.port_attachment)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('horizon:nova:networks:index'))
|
||||
|
||||
self.assertTemplateUsed(res, 'nova/networks/index.html')
|
||||
networks = res.context['table'].data
|
||||
|
||||
self.assertEqual(len(networks), 1)
|
||||
self.assertEqual(networks[0]['name'], 'test_network')
|
||||
self.assertEqual(networks[0]['id'], 'n1')
|
||||
self.assertEqual(networks[0]['total'], 1)
|
||||
self.assertEqual(networks[0]['used'], 1)
|
||||
self.assertEqual(networks[0]['available'], 0)
|
||||
|
||||
def test_network_create(self):
|
||||
self.mox.StubOutWithMock(api.quantum, "quantum_create_network")
|
||||
api.quantum.quantum_create_network(IsA(http.HttpRequest),
|
||||
IsA(dict)).AndReturn(True)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
formData = {'name': 'Test',
|
||||
'method': 'CreateNetwork'}
|
||||
|
||||
res = self.client.post(reverse('horizon:nova:networks:create'),
|
||||
formData)
|
||||
|
||||
self.assertRedirectsNoFollow(res,
|
||||
reverse('horizon:nova:networks:index'))
|
||||
|
||||
def test_network_delete(self):
|
||||
self.mox.StubOutWithMock(api, "quantum_delete_network")
|
||||
api.quantum_delete_network(IsA(http.HttpRequest), 'n1').AndReturn(True)
|
||||
|
||||
self.mox.StubOutWithMock(api, 'quantum_list_networks')
|
||||
api.quantum_list_networks(IsA(http.HttpRequest)).\
|
||||
AndReturn(self.network)
|
||||
|
||||
self.mox.StubOutWithMock(api, 'quantum_network_details')
|
||||
api.quantum_network_details(IsA(http.HttpRequest),
|
||||
'n1').AndReturn(self.network_details)
|
||||
|
||||
self.mox.StubOutWithMock(api, 'quantum_list_ports')
|
||||
api.quantum_list_ports(IsA(http.HttpRequest),
|
||||
'n1').AndReturn(self.ports)
|
||||
|
||||
self.mox.StubOutWithMock(api, 'quantum_port_attachment')
|
||||
api.quantum_port_attachment(IsA(http.HttpRequest),
|
||||
'n1', 'p1').AndReturn(self.port_attachment)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
formData = {'action': 'networks__delete__n1'}
|
||||
|
||||
self.client.post(reverse('horizon:nova:networks:index'), formData)
|
||||
|
||||
def test_network_rename(self):
|
||||
self.mox.StubOutWithMock(api, 'quantum_network_details')
|
||||
api.quantum_network_details(IsA(http.HttpRequest),
|
||||
'n1').AndReturn(self.network_details)
|
||||
|
||||
self.mox.StubOutWithMock(api, 'quantum_update_network')
|
||||
api.quantum_update_network(IsA(http.HttpRequest), 'n1',
|
||||
{'network': {'name': "Test1"}})
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
formData = {'network': 'n1',
|
||||
'new_name': 'Test1',
|
||||
'method': 'RenameNetwork'}
|
||||
|
||||
res = self.client.post(reverse('horizon:nova:networks:rename',
|
||||
args=["n1"]),
|
||||
formData)
|
||||
|
||||
self.assertRedirectsNoFollow(res,
|
||||
reverse('horizon:nova:networks:index'))
|
||||
|
||||
def test_network_details(self):
|
||||
self.mox.StubOutWithMock(api, 'quantum_network_details')
|
||||
api.quantum_network_details(IsA(http.HttpRequest),
|
||||
'n1').AndReturn(self.network_details)
|
||||
|
||||
self.mox.StubOutWithMock(api, 'quantum_list_ports')
|
||||
api.quantum_list_ports(IsA(http.HttpRequest),
|
||||
'n1').AndReturn(self.ports)
|
||||
|
||||
self.mox.StubOutWithMock(api, 'quantum_port_attachment')
|
||||
api.quantum_port_attachment(IsA(http.HttpRequest),
|
||||
'n1', 'p1').AndReturn(self.port_attachment)
|
||||
|
||||
self.mox.StubOutWithMock(api, 'quantum_port_details')
|
||||
api.quantum_port_details(IsA(http.HttpRequest),
|
||||
'n1', 'p1').AndReturn(self.port_details)
|
||||
|
||||
self.mox.StubOutWithMock(api, 'get_vif_ids')
|
||||
api.get_vif_ids(IsA(http.HttpRequest)).AndReturn(self.vifs)
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.get(reverse('horizon:nova:networks:detail',
|
||||
args=['n1']))
|
||||
|
||||
self.assertTemplateUsed(res, 'nova/networks/detail.html')
|
||||
self.assertIn('network', res.context)
|
||||
|
||||
network = res.context['network']
|
||||
|
||||
self.assertEqual(network['name'], 'test_network')
|
||||
self.assertEqual(network['id'], 'n1')
|
||||
|
||||
def test_port_create(self):
|
||||
self.mox.StubOutWithMock(api, "quantum_network_details")
|
||||
self.mox.StubOutWithMock(api, "quantum_create_port")
|
||||
network_details = {'network': {'id': 'n1'}}
|
||||
api.quantum_network_details(IsA(http.HttpRequest),
|
||||
'n1').AndReturn(network_details)
|
||||
api.quantum_create_port(IsA(http.HttpRequest), 'n1').AndReturn(True)
|
||||
|
||||
formData = {'ports_num': 1,
|
||||
'network': 'n1',
|
||||
'method': 'CreatePort'}
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.post(reverse('horizon:nova:networks:port_create',
|
||||
args=["n1"]),
|
||||
formData)
|
||||
|
||||
self.assertRedirectsNoFollow(res,
|
||||
reverse('horizon:nova:networks:detail',
|
||||
args=["n1"]))
|
||||
|
||||
def test_port_delete(self):
|
||||
self.mox.StubOutWithMock(api, 'quantum_network_details')
|
||||
self.mox.StubOutWithMock(api, 'quantum_list_ports')
|
||||
self.mox.StubOutWithMock(api, 'quantum_port_attachment')
|
||||
self.mox.StubOutWithMock(api, 'quantum_port_details')
|
||||
self.mox.StubOutWithMock(api, 'get_vif_ids')
|
||||
self.mox.StubOutWithMock(api, "quantum_delete_port")
|
||||
network_details = {'network': {'id': 'n1', 'name': 'network1'}}
|
||||
api.quantum_network_details(IsA(http.HttpRequest),
|
||||
'n1').AndReturn(network_details)
|
||||
|
||||
api.quantum_list_ports(IsA(http.HttpRequest),
|
||||
'n1').AndReturn(self.ports)
|
||||
|
||||
api.quantum_port_attachment(IsA(http.HttpRequest),
|
||||
'n1', 'p1').AndReturn(self.port_attachment)
|
||||
|
||||
api.quantum_port_details(IsA(http.HttpRequest),
|
||||
'n1', 'p1').AndReturn(self.port_details)
|
||||
|
||||
api.get_vif_ids(IsA(http.HttpRequest)).AndReturn(self.vifs)
|
||||
|
||||
api.quantum_delete_port(IsA(http.HttpRequest),
|
||||
'n1', 'p1').AndReturn(True)
|
||||
|
||||
formData = {'action': 'network_details__delete__p1'}
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
detail_url = reverse('horizon:nova:networks:detail', args=["n1"])
|
||||
self.client.post(detail_url, formData)
|
||||
|
||||
def test_port_attach(self):
|
||||
self.mox.StubOutWithMock(api, "quantum_network_details")
|
||||
self.mox.StubOutWithMock(api, "quantum_attach_port")
|
||||
self.mox.StubOutWithMock(api, "get_vif_ids")
|
||||
network_details = {'network': {'id': 'n1'}}
|
||||
api.quantum_network_details(IsA(http.HttpRequest),
|
||||
'n1').AndReturn(network_details)
|
||||
api.quantum_attach_port(IsA(http.HttpRequest),
|
||||
'n1', 'p1', IsA(dict)).AndReturn(True)
|
||||
api.get_vif_ids(IsA(http.HttpRequest)).AndReturn([{
|
||||
'id': 'v1',
|
||||
'instance_name': 'instance1',
|
||||
'available': True}])
|
||||
|
||||
formData = {'port': 'p1',
|
||||
'network': 'n1',
|
||||
'vif_id': 'v1',
|
||||
'method': 'AttachPort'}
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
res = self.client.post(reverse('horizon:nova:networks:port_attach',
|
||||
args=["n1", "p1"]),
|
||||
formData)
|
||||
|
||||
self.assertRedirectsNoFollow(res,
|
||||
reverse('horizon:nova:networks:detail',
|
||||
args=["n1"]))
|
||||
|
||||
def test_port_detach(self):
|
||||
self.mox.StubOutWithMock(api, 'quantum_network_details')
|
||||
self.mox.StubOutWithMock(api, 'quantum_list_ports')
|
||||
self.mox.StubOutWithMock(api, 'quantum_port_attachment')
|
||||
self.mox.StubOutWithMock(api, 'quantum_port_details')
|
||||
self.mox.StubOutWithMock(api, 'get_vif_ids')
|
||||
self.mox.StubOutWithMock(api, "quantum_set_port_state")
|
||||
network_details = {'network': {'id': 'n1', 'name': 'network1'}}
|
||||
api.quantum_network_details(IsA(http.HttpRequest),
|
||||
'n1').AndReturn(network_details)
|
||||
|
||||
api.quantum_list_ports(IsA(http.HttpRequest),
|
||||
'n1').AndReturn(self.ports)
|
||||
|
||||
api.quantum_port_attachment(IsA(http.HttpRequest),
|
||||
'n1', 'p1').AndReturn(self.port_attachment)
|
||||
|
||||
api.quantum_port_details(IsA(http.HttpRequest),
|
||||
'n1', 'p1').AndReturn(self.port_details)
|
||||
|
||||
api.get_vif_ids(IsA(http.HttpRequest)).AndReturn(self.vifs)
|
||||
|
||||
api.quantum_set_port_state(IsA(http.HttpRequest),
|
||||
'n1',
|
||||
'p1',
|
||||
{'port': {'state': 'DOWN'}}).AndReturn(True)
|
||||
|
||||
formData = {'action': "network_details__detach_port__p1"}
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
detail_url = reverse('horizon:nova:networks:detail', args=["n1"])
|
||||
res = self.client.post(detail_url, formData)
|
||||
|
||||
self.assertRedirectsNoFollow(res, detail_url)
|
@ -1,36 +0,0 @@
|
||||
# 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 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.
|
||||
|
||||
from django.conf.urls.defaults import patterns, url
|
||||
|
||||
from .views import (IndexView, CreateView, RenameView,
|
||||
DetailView, CreatePortView, AttachPortView)
|
||||
|
||||
urlpatterns = patterns('horizon.dashboards.nova.networks.views',
|
||||
url(r'^$', IndexView.as_view(), name='index'),
|
||||
url(r'^create/$', CreateView.as_view(), name='create'),
|
||||
url(r'^(?P<network_id>[^/]+)/detail/$', DetailView.as_view(),
|
||||
name='detail'),
|
||||
url(r'^(?P<network_id>[^/]+)/rename/$', RenameView.as_view(),
|
||||
name='rename'),
|
||||
url(r'^(?P<network_id>[^/]+)/ports/create/$', CreatePortView.as_view(),
|
||||
name='port_create'),
|
||||
url(r'^(?P<network_id>[^/]+)/ports/(?P<port_id>[^/]+)/attach/$',
|
||||
AttachPortView.as_view(), name='port_attach'))
|
@ -1,212 +0,0 @@
|
||||
# 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 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.
|
||||
|
||||
"""
|
||||
Views for managing Quantum networks.
|
||||
"""
|
||||
|
||||
import logging
|
||||
|
||||
from django import http
|
||||
from django.contrib import messages
|
||||
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 tables
|
||||
from horizon.dashboards.nova.networks.forms import (CreateNetwork,
|
||||
RenameNetwork, AttachPort, CreatePort)
|
||||
from .tables import NetworksTable, NetworkDetailsTable
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IndexView(tables.DataTableView):
|
||||
table_class = NetworksTable
|
||||
template_name = 'nova/networks/index.html'
|
||||
|
||||
def get_data(self):
|
||||
tenant_id = self.request.user.tenant_id
|
||||
networks = []
|
||||
|
||||
try:
|
||||
networks_list = api.quantum_list_networks(self.request)
|
||||
details = []
|
||||
for network in networks_list['networks']:
|
||||
net_stats = _calc_network_stats(self.request, network['id'])
|
||||
# Get network details like name and id
|
||||
details = api.quantum_network_details(self.request,
|
||||
network['id'])
|
||||
networks.append({
|
||||
'name': details['network']['name'],
|
||||
'id': network['id'],
|
||||
'total': net_stats['total'],
|
||||
'available': net_stats['available'],
|
||||
'used': net_stats['used'],
|
||||
'tenant': tenant_id})
|
||||
except Exception, e:
|
||||
LOG.exception("Unable to get network list.")
|
||||
if not hasattr(e, 'message'):
|
||||
e.message = str(e)
|
||||
messages.error(self.request,
|
||||
_('Unable to get network list: %s') % e.message)
|
||||
return networks
|
||||
|
||||
|
||||
class CreateView(forms.ModalFormView):
|
||||
form_class = CreateNetwork
|
||||
template_name = 'nova/networks/create.html'
|
||||
|
||||
|
||||
class RenameView(forms.ModalFormView):
|
||||
form_class = RenameNetwork
|
||||
template_name = 'nova/networks/rename.html'
|
||||
context_object_name = 'network'
|
||||
|
||||
def get_object(self, *args, **kwargs):
|
||||
network_id = kwargs['network_id']
|
||||
try:
|
||||
return api.quantum_network_details(self.request,
|
||||
network_id)['network']
|
||||
except:
|
||||
redirect = reverse("horizon:nova:networks:detail",
|
||||
args=(network_id,))
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve network information.'),
|
||||
redirect=redirect)
|
||||
|
||||
def get_initial(self):
|
||||
return {'network': self.object['id']}
|
||||
|
||||
|
||||
class DetailView(tables.DataTableView):
|
||||
table_class = NetworkDetailsTable
|
||||
template_name = 'nova/networks/detail.html'
|
||||
|
||||
def get_data(self):
|
||||
network_id = self.kwargs['network_id']
|
||||
network_details = api.quantum_network_details(self.request, network_id)
|
||||
self.network = {'id': network_id,
|
||||
'name': network_details['network']['name'],
|
||||
'ports': _get_port_states(self.request, network_id)}
|
||||
return self.network['ports']
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(DetailView, self).get_context_data(**kwargs)
|
||||
context['network'] = self.network
|
||||
return context
|
||||
|
||||
|
||||
def _get_port_states(request, network_id):
|
||||
"""
|
||||
Helper method to find port states for a network
|
||||
"""
|
||||
network_ports = []
|
||||
# Get all vifs for comparison with port attachments
|
||||
vifs = api.get_vif_ids(request)
|
||||
|
||||
# Get all ports on this network
|
||||
ports = api.quantum_list_ports(request, network_id)
|
||||
for port in ports['ports']:
|
||||
port_details = api.quantum_port_details(request,
|
||||
network_id, port['id'])
|
||||
# Get port attachments
|
||||
port_attachment = api.quantum_port_attachment(request,
|
||||
network_id, port['id'])
|
||||
# Find instance the attachment belongs to
|
||||
connected_instance = None
|
||||
if port_attachment['attachment']:
|
||||
for vif in vifs:
|
||||
if str(vif['id']) == str(port_attachment['attachment']['id']):
|
||||
connected_instance = vif['id']
|
||||
break
|
||||
network_ports.append({
|
||||
'id': port_details['port']['id'],
|
||||
'state': port_details['port']['state'],
|
||||
'attachment': port_attachment['attachment'],
|
||||
'instance': connected_instance})
|
||||
return network_ports
|
||||
|
||||
|
||||
def _calc_network_stats(request, network_id):
|
||||
"""
|
||||
Helper method to calculate statistics for a network
|
||||
"""
|
||||
# Get all ports statistics for the network
|
||||
total = 0
|
||||
available = 0
|
||||
used = 0
|
||||
ports = api.quantum_list_ports(request, network_id)
|
||||
for port in ports['ports']:
|
||||
total += 1
|
||||
# Get port attachment
|
||||
port_attachment = api.quantum_port_attachment(request,
|
||||
network_id, port['id'])
|
||||
if port_attachment['attachment']:
|
||||
used += 1
|
||||
else:
|
||||
available += 1
|
||||
|
||||
return {'total': total, 'used': used, 'available': available}
|
||||
|
||||
|
||||
class CreatePortView(forms.ModalFormView):
|
||||
form_class = CreatePort
|
||||
template_name = 'nova/networks/ports/create.html'
|
||||
context_object_name = 'port'
|
||||
|
||||
def get_object(self, *args, **kwargs):
|
||||
network_id = kwargs['network_id']
|
||||
try:
|
||||
return api.quantum_network_details(self.request,
|
||||
network_id)['network']
|
||||
except:
|
||||
redirect = reverse("horizon:nova:networks:detail",
|
||||
args=(network_id,))
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve network information.'),
|
||||
redirect=redirect)
|
||||
|
||||
def get_initial(self):
|
||||
return {'network': self.object['id']}
|
||||
|
||||
|
||||
class AttachPortView(forms.ModalFormView):
|
||||
form_class = AttachPort
|
||||
template_name = 'nova/networks/ports/attach.html'
|
||||
context_object_name = 'network'
|
||||
|
||||
def get_object(self, *args, **kwargs):
|
||||
network_id = kwargs['network_id']
|
||||
try:
|
||||
return api.quantum_network_details(self.request,
|
||||
network_id)['network']
|
||||
except:
|
||||
redirect = reverse("horizon:nova:networks:detail",
|
||||
args=(network_id,))
|
||||
exceptions.handle(self.request,
|
||||
_('Unable to attach port.'),
|
||||
redirect=redirect)
|
||||
|
||||
def get_initial(self):
|
||||
return {'network': self.object['id']}
|
@ -1,24 +0,0 @@
|
||||
{% 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 %}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 "Networks provide layer 2 connectivity to your instances." %}</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 %}
|
@ -1,24 +0,0 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block form_id %}form_rename_{{ network.id }}{% endblock %}
|
||||
{% block form_action %}{% url horizon:nova:networks:rename network.id %}{% endblock %}
|
||||
|
||||
{% block modal-header %}Rename Network{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="left">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="right">
|
||||
<h3>{% trans "Rename" %}:</h3>
|
||||
<p>{% trans "Enter a new name for your network." %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Rename Network" %}" />
|
||||
<a href="{% url horizon:nova:networks:index %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
@ -1,13 +0,0 @@
|
||||
{% extends 'nova/base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}Create Network{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{# to make searchable false, just remove it from the include statement #}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Create Network") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block dash_main %}
|
||||
{% include 'nova/networks/_create.html' with form=network_form %}
|
||||
{% endblock %}
|
||||
|
@ -1,11 +0,0 @@
|
||||
{% extends 'nova/base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Network Detail" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=network.name %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block dash_main %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
@ -1,13 +0,0 @@
|
||||
{% extends 'nova/base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}Networks{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% url horizon:nova:networks:index as refresh_link %}
|
||||
{# to make searchable false, just remove it from the include statement #}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Networks") refresh_link=refresh_link searchable="true" %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block dash_main %}
|
||||
{{ table.render }}
|
||||
{% endblock %}
|
@ -1,13 +0,0 @@
|
||||
{% extends 'nova/base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}Rename Network{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{# to make searchable false, just remove it from the include statement #}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Rename Network") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block dash_main %}
|
||||
{% include 'nova/networks/_rename.html' with form=rename_form %}
|
||||
{% endblock %}
|
||||
|
@ -1,22 +0,0 @@
|
||||
# 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.
|
||||
|
||||
from .utils import TestDataContainer
|
||||
|
||||
|
||||
def data(TEST):
|
||||
TEST.networks = TestDataContainer()
|
||||
TEST.ports = TestDataContainer()
|
||||
# TODO(gabriel): Move quantum test data into this module after it
|
||||
# has been refactored with object wrappers (a la Glance).
|
@ -20,5 +20,4 @@ iso8601
|
||||
# Horizon Non-pip Requirements
|
||||
-e git+https://github.com/openstack/python-novaclient.git#egg=python-novaclient
|
||||
-e git+https://github.com/openstack/python-keystoneclient.git#egg=python-keystoneclient
|
||||
-e git+https://github.com/openstack/python-quantumclient.git#egg=python-quantumclient
|
||||
-e git+https://github.com/openstack/glance.git#egg=glance
|
||||
|
Loading…
Reference in New Issue
Block a user