Merge pull request #92 from CiscoSystems/dashboard-quantum-integration

Dashboard quantum integration
This commit is contained in:
Devin Carlen 2011-08-30 14:32:55 -07:00
commit c158f6fd15
32 changed files with 1145 additions and 7 deletions

View File

@ -47,9 +47,9 @@ import openstackx.admin
import openstackx.api.exceptions as api_exceptions
import openstackx.extras
import openstackx.auth
import quantum.client
from urlparse import urlparse
LOG = logging.getLogger('django_openstack.api')
@ -162,7 +162,7 @@ class Server(APIResourceWrapper):
"""
_attrs = ['addresses', 'attrs', 'hostId', 'id', 'image', 'links',
'metadata', 'name', 'private_ip', 'public_ip', 'status', 'uuid',
'image_name']
'image_name', 'VirtualInterfaces']
def __init__(self, apiresource, request):
super(Server, self).__init__(apiresource)
@ -240,11 +240,17 @@ class SwiftAuthentication(object):
def authenticate(self):
return (self.storage_url, '', self.auth_token)
class ServiceCatalogException(api_exceptions.ApiException):
def __init__(self, service_name):
message = 'Invalid service catalog service: %s' % service_name
super(ServiceCatalogException, self).__init__(404, message)
class VirtualInterface(APIResourceWrapper):
_attrs = ['id','mac_address']
def url_for(request, service_name, admin=False):
catalog = request.user.service_catalog
try:
@ -345,6 +351,11 @@ def swift_api(request):
return cloudfiles.get_connection(auth=auth)
def quantum_api(request):
return quantum.client.Client(settings.QUANTUM_URL, settings.QUANTUM_PORT,
False, request.user.tenant, 'json')
def console_create(request, instance_id, kind='text'):
return Console(extras_api(request).consoles.create(instance_id, kind))
@ -642,6 +653,41 @@ def swift_get_object_data(request, container_name, object_name):
container = swift_api(request).get_container(container_name)
return container.get_object(object_name).stream()
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'].encode('ascii'))
# Get all instances
instances = server_list(request)
# Get virtual interface ids by instance
for instance in instances:
instance_vifs = extras_api(request).virtual_interfaces.list(instance.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
class GlobalSummary(object):
node_resources = ['vcpus', 'disk_size', 'ram_size']
unit_mem_size = {'disk_size': ['GiB', 'TiB'], 'ram_size': ['MiB', 'GiB']}

View File

@ -38,3 +38,7 @@ def tenants(request):
def swift(request):
return {'swift_configured': settings.SWIFT_ENABLED}
def quantum(request):
return {'quantum_configured': settings.QUANTUM_ENABLED}

View File

@ -26,6 +26,8 @@ KEYPAIRS = r'^(?P<tenant_id>[^/]+)/keypairs/%s$'
SNAPSHOTS = r'^(?P<tenant_id>[^/]+)/snapshots/(?P<instance_id>[^/]+)/%s$'
CONTAINERS = r'^(?P<tenant_id>[^/]+)/containers/%s$'
OBJECTS = r'^(?P<tenant_id>[^/]+)/containers/(?P<container_name>[^/]+)/%s$'
NETWORKS = r'^(?P<tenant_id>[^/]+)/networks/%s$'
PORTS = r'^(?P<tenant_id>[^/]+)/networks/(?P<network_id>[^/]+)/ports/%s$'
urlpatterns = patterns('django_openstack.dash.views.instances',
url(r'^(?P<tenant_id>[^/]+)/$', 'usage', name='dash_usage'),
@ -65,3 +67,15 @@ urlpatterns += patterns('django_openstack.dash.views.objects',
url(OBJECTS % '(?P<object_name>[^/]+)/download',
'download', name='dash_objects_download'),
)
urlpatterns += patterns('django_openstack.dash.views.networks',
url(r'^(?P<tenant_id>[^/]+)/networks/$', 'index', name='dash_networks'),
url(NETWORKS % 'create', 'create', name='dash_network_create'),
url(NETWORKS % '(?P<network_id>[^/]+)/detail', 'detail', name='dash_networks_detail'),
url(NETWORKS % '(?P<network_id>[^/]+)/rename', 'rename', name='dash_network_rename'),
)
urlpatterns += patterns('django_openstack.dash.views.ports',
url(PORTS % 'create', 'create', name='dash_ports_create'),
url(PORTS % '(?P<port_id>[^/]+)/attach', 'attach', name='dash_ports_attach'),
)

View File

@ -0,0 +1,237 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Copyright 2011 Fourth Paradigm Development, 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 api.quantum_api(request) networks.
"""
import logging
from django import http
from django import shortcuts
from django import template
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.utils import simplejson
from django.utils.translation import ugettext as _
from django_openstack import forms
from django_openstack import api
from django_openstack.dash.views.ports import DeletePort
from django_openstack.dash.views.ports import DetachPort
from django_openstack.dash.views.ports import TogglePort
import warnings
LOG = logging.getLogger('django_openstack.dash.views.networks')
class CreateNetwork(forms.SelfHandlingForm):
name = forms.CharField(required=True, label="Network Name")
def handle(self, request, data):
network_name = data['name']
try:
LOG.info('Creating network %s ' % network_name)
send_data = {'network': {'net-name': '%s' % network_name}}
api.quantum_api(request).create_network(send_data)
except Exception, e:
messages.error(request,
'Unable to create network %s: %s' %
(network_name, e.message,))
return shortcuts.redirect(request.build_absolute_uri())
else:
msg = 'Network %s has been created.' % network_name
LOG.info(msg)
messages.success(request, msg)
return shortcuts.redirect('dash_networks', tenant_id=request.user.tenant)
class DeleteNetwork(forms.SelfHandlingForm):
network = forms.CharField(widget=forms.HiddenInput())
def handle(self, request, data):
try:
LOG.info('Deleting network %s ' % data['network'])
api.quantum_api(request).delete_network(data['network'])
except Exception, e:
messages.error(request,
'Unable to delete network %s: %s' %
(data['network'], e.message,))
else:
msg = 'Network %s has been deleted.' % data['network']
LOG.info(msg)
messages.success(request, msg)
return shortcuts.redirect(request.build_absolute_uri())
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': {'net-name': '%s' % data['new_name']}}
api.quantum_api(request).update_network(data['network'], send_data)
except Exception, e:
messages.error(request,
'Unable to rename network %s: %s' %
(data['network'], e.message,))
else:
msg = 'Network %s has been renamed to %s.' % (data['network'], data['new_name'])
LOG.info(msg)
messages.success(request, msg)
return shortcuts.redirect(request.build_absolute_uri())
@login_required
def index(request, tenant_id):
delete_form, delete_handled = DeleteNetwork.maybe_handle(request)
networks = []
instances = []
try:
networks_list = api.quantum_api(request).list_networks()
details = []
for network in networks_list['networks']:
net_stats = _calc_network_stats(request, tenant_id, network['id'])
# Get network details like name and id
details = api.quantum_api(request).show_network_details(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:
messages.error(request, 'Unable to get network list: %s' % e.message)
return shortcuts.render_to_response('dash_networks.html', {
'networks': networks,
'delete_form' : delete_form,
}, context_instance=template.RequestContext(request))
@login_required
def create(request, tenant_id):
network_form, handled = CreateNetwork.maybe_handle(request)
if handled:
return shortcuts.redirect('dash_networks', request.user.tenant)
return shortcuts.render_to_response('dash_network_create.html', {
'network_form' : network_form
}, context_instance=template.RequestContext(request))
@login_required
def detail(request, tenant_id, network_id):
delete_port_form, delete_handled = DeletePort.maybe_handle(request)
detach_port_form, detach_handled = DetachPort.maybe_handle(request)
toggle_port_form, port_toggle_handled = TogglePort.maybe_handle(request)
network = {}
try:
network_details = api.quantum_api(request).show_network_details(network_id)
network['name'] = network_details['network']['name']
network['id'] = network_id
network['ports'] = _get_port_states(request, tenant_id, network_id)
except Exception, e:
messages.error(request, 'Unable to get network details: %s' % e.message)
return shortcuts.render_to_response('dash_networks_detail.html', {
'network': network,
'tenant' : tenant_id,
'delete_port_form' : delete_port_form,
'detach_port_form' : detach_port_form,
'toggle_port_form' : toggle_port_form
}, context_instance=template.RequestContext(request))
@login_required
def rename(request, tenant_id, network_id):
rename_form, handled = RenameNetwork.maybe_handle(request)
network_details = api.quantum_api(request).show_network_details(network_id)
if handled:
return shortcuts.redirect('dash_networks', request.user.tenant)
return shortcuts.render_to_response('dash_network_rename.html', {
'network' : network_details,
'rename_form' : rename_form
}, context_instance=template.RequestContext(request))
"""
Helper method to find port states for a network
"""
def _get_port_states(request, tenant_id, network_id):
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_api(request).list_ports(network_id)
for port in ports['ports']:
port_details = api.quantum_api(request).show_port_details(network_id, port['id'])
# Get port attachments
port_attachment = api.quantum_api(request).show_port_attachment(network_id, port['id'])
# Find instance the attachment belongs to
connected_instance = None
for vif in vifs:
if str(vif['id']) == str(port_attachment['attachment']):
connected_instance = vif['instance_name']
break
network_ports.append({
'id' : port_details['port']['id'],
'state' : port_details['port']['state'],
'attachment' : port_attachment['attachment'],
'instance' : connected_instance
})
return network_ports
"""
Helper method to calculate statistics for a network
"""
def _calc_network_stats(request, tenant_id, network_id):
# Get all ports statistics for the network
total = 0
available = 0
used = 0
ports = api.quantum_api(request).list_ports(network_id)
for port in ports['ports']:
total += 1
# Get port attachment
port_attachment = api.quantum_api(request).show_port_attachment(network_id, port['id'])
if port_attachment['attachment'] == None:
available += 1
else:
used += 1
return { 'total' : total, 'used' : used, 'available': available }

View File

@ -0,0 +1,192 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Copyright 2011 Fourth Paradigm Development, 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 api.quantum_api(request) network ports.
"""
import logging
from django import http
from django import shortcuts
from django import template
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.utils.translation import ugettext as _
from django_openstack import forms
from django_openstack import api
LOG = logging.getLogger('django_openstack.dash.views.ports')
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_api(request).create_port(data['network'])
except Exception, e:
messages.error(request,
'Unable to create ports on network %s: %s' %
(data['network'], e.message,))
else:
msg = '%s ports created on network %s.' % (data['ports_num'], data['network'])
LOG.info(msg)
messages.success(request, msg)
return shortcuts.redirect(request.build_absolute_uri())
class DeletePort(forms.SelfHandlingForm):
network = forms.CharField(widget=forms.HiddenInput())
port = forms.CharField(widget=forms.HiddenInput())
def handle(self, request, data):
try:
LOG.info('Deleting %s ports on network %s' % (data['port'], data['network']))
api.quantum_api(request).delete_port(data['network'], data['port'])
except Exception, e:
messages.error(request,
'Unable to delete port %s: %s' %
(data['port'], e.message,))
else:
msg = 'Port %s deleted from network %s.' % (data['port'], data['network'])
LOG.info(msg)
messages.success(request, msg)
return shortcuts.redirect(request.build_absolute_uri())
class AttachPort(forms.SelfHandlingForm):
network = forms.CharField(widget=forms.HiddenInput())
port = forms.CharField(widget=forms.HiddenInput())
vif_id = forms.CharField(widget=forms.Select(), label="Select VIF to connect")
def handle(self, request, data):
try:
LOG.info('Attaching %s port to VIF %s' % (data['port'], data['vif_id']))
body = {'port': {'attachment-id': '%s' % data['vif_id']}}
api.quantum_api(request).attach_resource(data['network'], data['port'], body)
except Exception, e:
messages.error(request,
'Unable to attach port %s to VIF %s: %s' %
(data['port'], data['vif_id'], e.message,))
else:
msg = 'Port %s connect to VIF %s.' % (data['port'], data['vif_id'])
LOG.info(msg)
messages.success(request, msg)
return shortcuts.redirect(request.build_absolute_uri())
class DetachPort(forms.SelfHandlingForm):
network = forms.CharField(widget=forms.HiddenInput())
port = forms.CharField(widget=forms.HiddenInput())
def handle(self, request, data):
try:
LOG.info('Detaching port %s' % data['port'])
api.quantum_api(request).detach_resource(data['network'], data['port'])
except Exception, e:
messages.error(request,
'Unable to detach port %s: %s' %
(data['port'], e.message,))
else:
msg = 'Port %s detached.' % (data['port'])
LOG.info(msg)
messages.success(request, msg)
return shortcuts.redirect(request.build_absolute_uri())
class TogglePort(forms.SelfHandlingForm):
network = forms.CharField(widget=forms.HiddenInput())
port = forms.CharField(widget=forms.HiddenInput())
state = forms.CharField(widget=forms.HiddenInput())
def handle(self, request, data):
try:
LOG.info('Toggling port state to %s' % data['state'])
body = {'port': {'port-state': '%s' % data['state']}}
api.quantum_api(request).set_port_state(data['network'], data['port'], body)
except Exception, e:
messages.error(request,
'Unable to set port state to %s: %s' %
(data['state'], e.message,))
else:
msg = 'Port %s state set to %s.' % (data['port'],data['state'])
LOG.info(msg)
messages.success(request, msg)
return shortcuts.redirect(request.build_absolute_uri())
@login_required
def create(request, tenant_id, network_id):
create_form, handled = CreatePort.maybe_handle(request)
if handled:
return shortcuts.redirect(
'dash_networks_detail',
tenant_id=request.user.tenant,
network_id=network_id
)
return shortcuts.render_to_response('dash_ports_create.html', {
'network_id' : network_id,
'create_form' : create_form
}, context_instance=template.RequestContext(request))
@login_required
def attach(request, tenant_id, network_id, port_id):
attach_form, handled = AttachPort.maybe_handle(request)
if handled:
return shortcuts.redirect('dash_networks_detail', request.user.tenant, network_id)
# Get all avaliable vifs
vifs = _get_available_vifs(request)
return shortcuts.render_to_response('dash_port_attach.html', {
'network' : network_id,
'port' : port_id,
'attach_form' : attach_form,
'vifs' : vifs,
}, context_instance=template.RequestContext(request))
"""
Method to get a list of available virtual interfaces
"""
def _get_available_vifs(request):
vif_choices = []
vifs = api.get_vif_ids(request)
for vif in vifs:
if vif['available']:
name = "Instance %s VIF %s" % (str(vif['instance_name']), str(vif['id']))
vif_choices.append({
'name' : str(name),
'id' : str(vif['id'])
})
return vif_choices

View File

@ -44,6 +44,9 @@ class TestCase(test.TestCase):
'region': 'RegionOne',
'internalURL': 'http://127.0.0.1:8080/v1/AUTH_1234',
'publicURL': 'http://swift.publicinternets.com/v1/AUTH_1234'}],
'quantum':
[{'adminURL': 'http://127.0.0.1:9696/v0.1',
'internalURL': 'http://127.0.0.1:9696/v0.1'}],
}
def setUp(self):

View File

@ -0,0 +1,113 @@
from django import http
from django.contrib import messages
from django.core.urlresolvers import reverse
from django_openstack import api
from django_openstack.tests.view_tests import base
from mox import IgnoreArg, IsA
class NetworkViewTests(base.BaseViewTests):
def setUp(self):
super(NetworkViewTests, self).setUp()
self.network = {}
self.network['networks'] = []
self.network['networks'].append({id : 'n1'})
self.network_details = {'network' : {'name' : 'test_network'}}
self.ports = {}
self.ports['ports'] = []
self.ports['ports'].append({'id' : 'p1'})
self.port_attachment = {}
self.port_attachment['attachment'] = 'vif1'
def test_network_index(self):
q_api = api.quantum_api(self.request)
self.mox.StubOutWithMock(q_api, 'list_networks')
q_api.list_networks()(
IsA(http.HttpRequest)).AndReturn([self.network])
self.mox.StubOutWithMock(q_api, 'show_network_details')
q_api.show_network_details("n1")(
IsA(http.HttpRequest)).AndReturn([self.network_details])
self.mox.StubOutWithMock(q_api, 'list_ports')
q_api.list_ports("n1")(
IsA(http.HttpRequest)).AndReturn([self.ports])
self.mox.StubOutWithMock(q_api, 'show_port_attachment')
q_api.show_port_attachment("p1")(
IsA(http.HttpRequest)).AndReturn([self.port_attachment])
self.mox.ReplayAll()
res = self.client.get(reverse('dash_networks', args=['tenant']))
self.assertTemplateUsed(res, 'dash_networks.html')
self.assertIn('networks', res.context)
networks = res.context['networks']
self.assertEqual(len(networks), 1)
self.assertEqual(networks[0].name, 'test')
self.assertEqual(networks[0].id, 'n1')
self.assertEqual(networks[0].id, 'n1')
self.assertEqual(networks[0].total, 1)
self.assertEqual(networks[0].used, 1)
self.assertEqual(networks[0].available, 0)
self.mox.VerifyAll()
def test_network_create(self):
q_api = api.quantum_api(self.request)
formData = {'name': 'Test',
'method': 'CreateNetwork'}
self.mox.StubOutWithMock(q_api, 'create_network')
q_api.create_network(
IsA(http.HttpRequest), 'CreateNetwork')
self.mox.StubOutWithMock(messages, 'success')
messages.success(IgnoreArg(), IsA(str))
res = self.client.post(reverse('dash_network_create',
args=[self.request.user.tenant]),
formData)
self.assertRedirectsNoFollow(res, reverse('dash_networks',
args=[self.request.user.tenant]))
def test_network_delete(self):
q_api = api.quantum_api(self.request)
formData = {'id': 'n1',
'method': 'DeleteNetwork'}
self.mox.StubOutWithMock(q_api, 'delete_network')
q_api.delete_network(
IsA(http.HttpRequest), 'DeleteNetwork')
self.mox.StubOutWithMock(messages, 'success')
messages.success(IgnoreArg(), IsA(str))
res = self.client.post(reverse('dash_networks',
args=[self.request.user.tenant]),
formData)
self.assertRedirectsNoFollow(res, reverse('dash_networks',
args=[self.request.user.tenant]))
def test_network_rename(self):
q_api = api.quantum_api(self.request)
formData = {'new_name' : 'Test1',
'method': 'DeleteNetwork'}
self.mox.StubOutWithMock(q_api, 'update_network')
q_api.update_network("n1")(
IsA(http.HttpRequest), 'DeleteNetwork')
self.mox.StubOutWithMock(messages, 'success')
messages.success(IgnoreArg(), IsA(str))
res = self.client.post(reverse('dash_network_rename',
args=[self.request.user.tenant, "n1"]),
formData)
self.assertRedirectsNoFollow(res, reverse('dash_networks',
args=[self.request.user.tenant]))

View File

@ -0,0 +1,92 @@
from django import http
from django.contrib import messages
from django.core.urlresolvers import reverse
from django_openstack import api
from django_openstack.tests.view_tests import base
from mox import IgnoreArg, IsA
class PortViewTests(base.BaseViewTests):
def setUp(self):
super(PortViewTests, self).setUp()
def test_port_create(self):
q_api = api.quantum_api(self.request)
formData = {'ports_num' : 1,
'network' : 'n1',
'method': 'CreatePort'}
self.mox.StubOutWithMock(q_api, 'create_port')
q_api.create_port(self.request.user.tenant, "n1")(
IsA(http.HttpRequest), 'CreatePort')
self.mox.StubOutWithMock(messages, 'success')
messages.success(IgnoreArg(), IsA(str))
res = self.client.post(reverse('dash_ports_create',
args=[self.request.user.tenant, "n1"]),
formData)
self.assertRedirectsNoFollow(res, reverse('dash_networks_detail',
args=[self.request.user.tenant, "n1"]))
def test_port_delete(self):
q_api = api.quantum_api(self.request)
formData = {'port' : 'p1',
'network' : 'n1',
'method': 'DeletePort'}
self.mox.StubOutWithMock(q_api, 'delete_port')
q_api.delete_port(self.request.user.tenant, "n1", "p1")(
IsA(http.HttpRequest), 'DeletePort')
self.mox.StubOutWithMock(messages, 'success')
messages.success(IgnoreArg(), IsA(str))
res = self.client.post(reverse('dash_networks_detail',
args=[self.request.user.tenant, "n1"]),
formData)
self.assertRedirectsNoFollow(res, reverse('dash_networks_detail',
args=[self.request.user.tenant, "n1"]))
def test_port_attach(self):
q_api = api.quantum_api(self.request)
formData = {'port' : 'p1',
'network' : 'n1',
'vif_id' : 'v1',
'method': 'AttachPort'}
self.mox.StubOutWithMock(q_api, 'attach_resource')
q_api.attach_resource(self.request.user.tenant, "n1", "p1")(
IsA(http.HttpRequest), 'AttachPort')
self.mox.StubOutWithMock(messages, 'success')
messages.success(IgnoreArg(), IsA(str))
res = self.client.post(reverse('dash_ports_attach',
args=[self.request.user.tenant, "n1", "p1"]),
formData)
self.assertRedirectsNoFollow(res, reverse('dash_networks_detail',
args=[self.request.user.tenant, "n1"]))
def test_port_detach(self):
q_api = api.quantum_api(self.request)
formData = {'port' : 'p1',
'network' : 'n1',
'method': 'DetachPort'}
self.mox.StubOutWithMock(q_api, 'detach_resource')
q_api.detach_resource(self.request.user.tenant, "n1", "p1")(
IsA(http.HttpRequest), 'DetachPort')
self.mox.StubOutWithMock(messages, 'success')
messages.success(IgnoreArg(), IsA(str))
res = self.client.post(reverse('dash_networks_detail',
args=[self.request.user.tenant, "n1"]),
formData)
self.assertRedirectsNoFollow(res, reverse('dash_networks_detail',
args=[self.request.user.tenant, "n1"]))

View File

@ -64,6 +64,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
'django.contrib.messages.context_processors.messages',
'django_openstack.context_processors.swift',
'django_openstack.context_processors.tenants',
'django_openstack.context_processors.quantum',
)
TEMPLATE_LOADERS = (

View File

@ -0,0 +1,12 @@
<form id="attach_port_form_{{port.id}}" class="form-attach" method="post">
{% csrf_token %}
{% for hidden in form.hidden_fields %}{{hidden}}{% endfor %}
{% for field in form.visible_fields %}
{{ field.label_tag }}
{{ field.errors }}
{{ field }}
{% endfor %}
<input name="network" type="hidden" value="{{network}}" />
<input name="port" type="hidden" value="{{port}}" />
<input type="submit" value="Attach" class="large-rounded" />
</form>

View File

@ -0,0 +1,11 @@
<form id="create_port_form" method="post">
{% csrf_token %}
{% for hidden in form.hidden_fields %}{{ hidden }}{% endfor %}
{% for field in form.visible_fields %}
{{ field.label_tag }}
{{ field.errors }}
{{ field }}
{% endfor %}
<input name="network" type="hidden" value="{{network_id}}" />
<input type="submit" value="Create Ports" class="large-rounded" />
</form>

View File

@ -8,6 +8,9 @@
<li><a {% if current_sidebar == "images" %} class="active" {% endif %} href="{% url dash_images request.user.tenant %}">Images</a></li>
<li><a {% if current_sidebar == "snapshots" %} class="active" {% endif %} href="{% url dash_snapshots request.user.tenant %}">Snapshots</a></li>
<li><a {% if current_sidebar == "keypairs" %} class="active" {% endif %} href="{% url dash_keypairs request.user.tenant %}">Keypairs</a></li>
{% if quantum_configured %}
<li><a {% if current_sidebar == "networks" %} class="active" {% endif %} href="{% url dash_networks request.user.tenant %}">Networks</a></li>
{% endif %}
</ul>
{% if swift_configured %}
<h3>Manage Object Store</h3>

View File

@ -0,0 +1,8 @@
<form id="delete_form" class="form-delete" method="post">
{% csrf_token %}
{% for hidden in form.hidden_fields %}
{{hidden}}
{% endfor %}
<input name="network" type="hidden" value="{{network.id}}" />
<input title="Network" id="delete_{{network.id}}" class="delete" type="submit" value="Delete" />
</form>

View File

@ -0,0 +1,9 @@
<form id="delete_port_form" class="form-delete" method="post">
{% csrf_token %}
{% for hidden in form.hidden_fields %}
{{hidden}}
{% endfor %}
<input name="network" type="hidden" value="{{network.id}}" />
<input name="port" type="hidden" value="{{port.id}}" />
<input title="Delete this port" id="delete_{{port.id}}" class="delete" type="submit" value="Delete" />
</form>

View File

@ -0,0 +1,9 @@
<form id="detach_port_form" class="form-detach" method="post">
{% csrf_token %}
{% for hidden in form.hidden_fields %}
{{hidden}}
{% endfor %}
<input name="network" type="hidden" value="{{network.id}}" />
<input name="port" type="hidden" value="{{port.id}}" />
<input title="Detach port from instance" id="detach_{{port.id}}" class="detach" type="submit" value="Detach" />
</form>

View File

@ -0,0 +1,49 @@
<table id='Ports' class="wide">
<tr id='headings'>
<th>ID</th>
<th>State</th>
<th>Attachment</th>
<th>Actions</th>
<th>Extensions</th>
</tr>
<tbody class='main'>
{% for port in network.ports %}
<tr class="{% cycle 'odd' 'even' %}">
<td>{{port.id}}</td>
<td class="{{port.state}}">{{port.state}}</td>
<td>
{% if port.attachment == None %}
--
{% else %}
<table class="attachmend_details">
<tr>
<td> Instance </td>
<td> VIF Id </td>
</tr>
<tr>
<td> {{port.instance}} </td>
<td> {{port.attachment}} </td>
</tr>
</table>
{% endif %}
</td>
<td id="actions">
<ul>
{% if port.attachment == None %}
<li><a href='{% url dash_ports_attach request.user.tenant network.id port.id %}'>Attach</a></li>
{% else %}
<li class="form">{% include "_detach_port.html" with form=detach_port_form %}</li>
{% endif %}
<li class="form">{% include "_delete_port.html" with form=delete_port_form %}</li>
<li class="form">{% include "_toggle_port.html" with form=toggle_port_form %}</li>
</ul>
</td>
<td id="extensions">
<ul>
</ul>
</td>
</tr>
{% endfor %}
</tbody>
</table>

View File

@ -0,0 +1,10 @@
<form id="network_form" method="post">
{% csrf_token %}
{% for hidden in form.hidden_fields %}{{ hidden }}{% endfor %}
{% for field in form.visible_fields %}
{{ field.label_tag }}
{{ field.errors }}
{{ field }}
{% endfor %}
<input type="submit" value="Create Network" class="large-rounded" />
</form>

View File

@ -0,0 +1,27 @@
<table id='networks' class="wide">
<tr id='headings'>
<th>ID</th>
<th>Name</th>
<th>Ports</th>
<th>Available</th>
<th>Used</th>
<th>Action</th>
</tr>
<tbody class='main'>
{% for network in networks %}
<tr class="{% cycle 'odd' 'even' %}" id="{{network.id}}">
<td><a href='{% url dash_networks_detail request.user.tenant network.id %}'>{{network.id}}</a></td>
<td class="name">{{network.name}}</td>
<td>{{network.total}}</td>
<td>{{network.available}}</td>
<td>{{network.used}}</td>
<td id="actions">
<ul>
<li class="form">{% include "_delete_network.html" with form=delete_form %}</li>
<li><a href='{% url dash_network_rename request.user.tenant network.id %}'>Rename</a></li>
</ul>
</td>
</tr>
{% endfor %}
</tbody>
</table>

View File

@ -1,6 +1,8 @@
{% block page_header %}
<div id='page_header'>
<h2>{{title}}</h2>
{% block breadcrumbs %}
{% endblock %}
{% if searchable %}
<div class="right">
<div class='search'>

View File

@ -0,0 +1,11 @@
<form id="rename_form" method="post">
{% csrf_token %}
{% for hidden in form.hidden_fields %}{{ hidden }}{% endfor %}
{% for field in form.visible_fields %}
{{ field.label_tag }}
{{ field.errors }}
{{ field }}
{% endfor %}
<input type="hidden" name="network" value="{{network.network.id}}"/>
<input type="submit" value="Rename Network" class="large-rounded" />
</form>

View File

@ -0,0 +1,17 @@
<form id="form_rename_{{network.id}}" class="form-rename" method="post">
{% csrf_token %}
{% for hidden in form.hidden_fields %}
{{hidden}}
{% endfor %}
<input name="network" type="hidden" value="{{network.id}}" />
<input id="new_name_{{network.id}}" name="new_name" type="hidden" value="{{network.name}}" />
<input id="rename_{{network.id}}" class="rename" type="submit" value="Rename" />
<div class="network_rename_div" id="rename_div_{{network.id}}">
<input id="change_to_{{network.id}}" name="change_to" class="change_to ui-corner-all ui-state-highlight" size="20" value="{{network.name}}"/><br/><br/>
<button id="{{network.id}}" class="dialog_rename">Rename</button>
</div>
</form>

View File

@ -0,0 +1,15 @@
<form id="port_toggle_form" class="form-toggle" method="post">
{% csrf_token %}
{% for hidden in form.hidden_fields %}
{{hidden}}
{% endfor %}
<input name="network" type="hidden" value="{{network.id}}" />
<input name="port" type="hidden" value="{{port.id}}" />
{% if port.state == 'DOWN' %}
<input name="state" type="hidden" value="ACTIVE" />
<input title="Turn port UP" id="port_up_{{port.id}}" class="port_up" type="submit" value="Port UP" />
{% else %}
<input name="state" type="hidden" value="DOWN" />
<input title="Turn port DOWN" id="port_up_{{port.id}}" class="port_down" type="submit" value="Port DOWN" />
{% endif %}
</form>

View File

@ -0,0 +1,28 @@
{% extends 'dash_base.html' %}
{% block sidebar %}
{% with current_sidebar="networks" %}
{{block.super}}
{% endwith %}
{% endblock %}
{% block page_header %}
{# to make searchable false, just remove it from the include statement #}
{% include "_page_header.html" with title="Create Network" %}
{% endblock page_header %}
{% block dash_main %}
<div class="dash_block">
<div class="left">
{% include '_network_form.html' with form=network_form %}
<h3><a href="{% url dash_networks request.user.tenant %}"><< Return to networks list</a></h3>
</div>
<div class="right">
<h3>Description:</h3>
<p>Networks provide layer 2 connectivity to your instances.</p>
</div>
<div class="clear">&nbsp;</div>
</div>
{% endblock %}

View File

@ -0,0 +1,36 @@
{% extends 'dash_base.html' %}
{% block sidebar %}
{% with current_sidebar="networks" %}
{{block.super}}
{% endwith %}
{% endblock %}
{% block page_header %}
{# to make searchable false, just remove it from the include statement #}
{% include "_page_header.html" with title="Rename Network" %}
{% endblock page_header %}
{% block headerjs %}
<script type="text/javascript">
$(document).ready(function() {
$('input#id_new_name').val('{{network.network.name}}');
});
</script>
{% endblock headerjs %}
{% block dash_main %}
<div class="dash_block">
<div class="left">
{% include '_rename_form.html' with form=rename_form %}
<h3><a href="{% url dash_networks request.user.tenant %}"><< Return to networks list</a></h3>
</div>
<div class="right">
<h3>Rename:</h3>
<p>Enter a new name for your network.</p>
</div>
<div class="clear">&nbsp;</div>
</div>
{% endblock %}

View File

@ -0,0 +1,26 @@
{% extends 'dash_base.html' %}
{% block sidebar %}
{% with current_sidebar="networks" %}
{{block.super}}
{% endwith %}
{% endblock %}
{% block page_header %}
{% url dash_networks request.user.tenant as refresh_link %}
{# to make searchable false, just remove it from the include statement #}
{% include "_page_header.html" with title="Networks" refresh_link=refresh_link searchable="true" %}
{% endblock page_header %}
{% block dash_main %}
{% if networks %}
{% include '_network_list.html' %}
<a id="network_create_link" class="action_link large-rounded" href="{% url dash_network_create request.user.tenant %}">Create New Network</a>
{% else %}
<div class="message_box info">
<h2>Info</h2>
<p>There are currently no networks. <a href='{% url dash_network_create request.user.tenant %}'>Create A Network &gt;&gt;</a></p>
</div>
{% endif %}
</div>
{% endblock %}

View File

@ -0,0 +1,30 @@
{% extends 'dash_base.html' %}
{% block sidebar %}
{% with current_sidebar="networks" %}
{{block.super}}
{% endwith %}
{% endblock %}
{% block page_header %}
{% url dash_networks_detail request.user.tenant network.id as refresh_link %}
{# to make searchable false, just remove it from the include statement #}
{% include "_page_header.html" with title=network.name refresh_link=refresh_link searchable="true" %}
{% endblock page_header %}
{% block breadcrumbs %}
<a href="{% url dash_networks tenant %}">Networks</a>&nbsp;&raquo;&nbsp;
<a href="{% url dash_networks_detail tenant network.id %}">{{network.name}}</a>
{% endblock %}
{% block dash_main %}
{% if network.ports %}
{% include '_network_detail.html' %}
<a id="network_create_link" class="action_link large-rounded" href="{% url dash_ports_create request.user.tenant network.id %}">Create Ports</a>
{% else %}
<div class="message_box info">
<h2>Info</h2>
<p>There are currently no ports in this network. <a href="{% url dash_ports_create request.user.tenant network.id %}">Create Ports &gt;&gt;</a></p>
</div>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,48 @@
{% extends 'dash_base.html' %}
{% block sidebar %}
{% with current_sidebar="networks" %}
{{block.super}}
{% endwith %}
{% endblock %}
{% block page_header %}
{# to make searchable false, just remove it from the include statement #}
{% include "_page_header.html" with title="Attach Port" %}
{% endblock page_header %}
{% block headerjs %}
<script type="text/javascript">
var VIF_OPTIONS = {
{% for vif in vifs %}
'{{vif.id}}' : '{{vif.name}}',
{% endfor %}
};
$(document).ready(function() {
$.each(VIF_OPTIONS, function(val, text) {
$('select#id_vif_id')
.append($("<option></option>")
.attr("value", val)
.text(text));
});
});
</script>
{% endblock headerjs %}
{% block dash_main %}
<div class="dash_block">
<div class="left">
{% include '_attach_port.html' with form=attach_form %}
<h3><a href="{% url dash_networks_detail request.user.tenant network %}"><< Return to network detail</a></h3>
</div>
<div class="right">
<p>Select an interface from the list on the left to attach it to this port.</p>
<p>Only interfaces that are not connected to any existing port are shown</p>
<p>If you want to reconnect a connected interface, please detach it first</p>
</div>
<div class="clear">&nbsp;</div>
</div>
{% endblock %}

View File

@ -0,0 +1,28 @@
{% extends 'dash_base.html' %}
{% block sidebar %}
{% with current_sidebar="networks" %}
{{block.super}}
{% endwith %}
{% endblock %}
{% block page_header %}
{# to make searchable false, just remove it from the include statement #}
{% include "_page_header.html" with title="Create Network" %}
{% endblock page_header %}
{% block dash_main %}
<div class="dash_block">
<div class="left">
{% include '_create_port.html' with form=create_form %}
<h3><a href="{% url dash_networks_detail request.user.tenant network_id %}"><< Return to network detail</a></h3>
</div>
<div class="right">
<h3>Description:</h3>
<p>You can plug virtual interfaces from your instances to ports created in the network</p>
</div>
<div class="clear">&nbsp;</div>
</div>
{% endblock %}

View File

@ -39,6 +39,13 @@ OPENSTACK_KEYSTONE_DEFAULT_ROLE = "Member"
# catalog in Keystone.
SWIFT_ENABLED = False
# Configure quantum connection details for networking
QUANTUM_ENABLED = True
QUANTUM_URL = '127.0.0.1'
QUANTUM_PORT = '9696'
QUANTUM_TENANT = '1234'
QUANTUM_CLIENT_VERSION='0.1'
# If you have external monitoring links
EXTERNAL_MONITORING = [
['Nagios','http://foo.com'],

View File

@ -907,3 +907,48 @@ h3 span, .left h3 { color: #6a6a6a; }
margin: 25px 0 0 0;
float: left;
}
/* Network details page */
td.ACTIVE {
color:#6EAF6E;
}
td.DOWN {
color:#CC3333;
}
.vif-list {
font: 12px/21px arial,sans-serif;
font-style: italic;
}
table.attachment_table {
margin-top: 10px;
border: none;
display: none;
}
table.attachment_table td {
padding: 5px;
font-size: 8pt;
}
table.attachment_table td.row_head {
font-weight: bold;
}
table.attachment_table td.row_val {
font-style: italic;
}
div.error_block {
padding: 5px;
font-size: 8pt;
}
span.attachment_value {
font-weight:bold;
display:inline-block;
font-style:italic;
color:#6EAF6E;
}

View File

@ -45,5 +45,9 @@ $(function(){
});
return true;
});
})
$(".detach").click(function(e){
var response = confirm('Are you sure you want to detach the '+$(this).attr('title')+" ?");
return response;
})
})

View File

@ -3,6 +3,7 @@ Django==1.3
django-nose==0.1.2
django-mailer
django-registration==0.7
kombu
nova-adminclient
python-cloudfiles
python-dateutil
@ -14,9 +15,9 @@ PasteDeploy
sqlalchemy-migrate
eventlet
xattr
kombu
bzr+https://launchpad.net/glance#egg=glance
-e git://github.com/jacobian/openstack.compute.git#egg=openstack
-e git://github.com/cloudbuilders/openstackx.git#egg=openstackx
bzr+https://launchpad.net/quantum#egg=quantum
-e git+https://github.com/jacobian/openstack.compute.git#egg=openstack
-e git+https://github.com/cloudbuilders/openstackx.git#egg=openstackx