big ugly merge

This commit is contained in:
termie 2011-07-13 13:24:43 -07:00
commit dda978611d
82 changed files with 4742 additions and 249 deletions

8
.gitignore vendored
View File

@ -1,12 +1,16 @@
*.pyc
*.swp
django-openstack/.coverage
django-openstack/.installed.cfg
django-openstack/bin
django-openstack/develop-eggs/
django-openstack/downloads/
django-openstack/eggs/
django-openstack/htmlcov
django-openstack/launchpad
django-openstack/parts/
django-openstack/src/django_nova.egg-info
django-openstack/src/django_openstack.egg-info
django-openstack/django_nova.egg-info
django-openstack/django_openstack.egg-info
django-nova-syspanel/src/django_nova_syspanel.egg-info
openstack-dashboard/.dashboard-venv
openstack-dashboard/local/dashboard_openstack.sqlite3

View File

@ -22,7 +22,13 @@ Getting Started
---------------
Django-Nova uses Buildout (http://www.buildout.org/) to manage local
development. To configure your local Buildout environment:
development. To configure your local Buildout environment first install the following
system-level dependencies:
* python-dev
* git
* bzr
Then instantiate buildout with
$ python bootstrap.py
$ bin/buildout
@ -37,6 +43,14 @@ scripts in the bin/ directory:
You should now be able to run unit tests as follows:
$ bin/django test
or
$ bin/test
You can run unit tests with code coverage on django_openstack by setting
NOSE_WITH_COVERAGE:
$ NOSE_WITH_COVERAGE=true bin/test
Get even better coverage info by running coverage directly:
$ coverage run --branch --source django_openstack bin/django test django_openstack && coverage html

View File

@ -1,22 +1,108 @@
[buildout]
parts = python django
parts =
django
launchpad
openstack-compute
openstackx
develop = .
eggs = django-openstack
versions = versions
[versions]
django = 1.3
# the following are for glance-dependencies
eventlet = 0.9.12
greenlet = 0.3.1
pep8 = 0.5.0
sqlalchemy = 0.6.3
sqlalchemy-migrate = 0.6
webob = 1.0.8
[python]
[dependencies]
# dependencies that are found locally ${buildout:directory}/module
# or can be fetched from pypi
recipe = zc.recipe.egg
eggs =
django-mailer
httplib2
python-cloudfiles
interpreter = python
eggs = ${buildout:eggs}
# glance doesn't have a client, and installing
# from bzr doesn't install deps
[glance-dependencies]
recipe = zc.recipe.egg
eggs =
PasteDeploy
anyjson
argparse
eventlet
greenlet
paste
pep8
routes
sqlalchemy
sqlalchemy-migrate
webob
interpreter = python
[django-openstack]
recipe = zc.recipe.egg
eggs = django-openstack
interpreter = python
[django]
# defines settings for django
# any dependencies that cannot be satisifed via the dependencies
# recipe above will need to be added to the extra-paths here.
# IE, dependencies fetch from a git repo will not auto-populate
# like the zc.recipe.egg ones will
recipe = djangorecipe
project = django_openstack
projectegg = django_openstack
settings = tests.testsettings
settings = tests
test = django_openstack
eggs = ${buildout:eggs}
eggs =
${dependencies:eggs}
${django-openstack:eggs}
${glance-dependencies:eggs}
extra-paths =
${buildout:directory}/launchpad/glance
${buildout:directory}/parts/openstack-compute
${buildout:directory}/parts/openstackx
## Dependencies fetch from git
# git dependencies end up as a subdirectory of ${buildout:directory}/parts/
[openstack-compute]
recipe = zerokspot.recipe.git
repository = git://github.com/jacobian/openstack.compute.git
as_egg = True
[openstackx]
recipe = zerokspot.recipe.git
repository = git://github.com/cloudbuilders/openstackx.git
as_egg = True
## Dependencies fetched from launchpad
# launchpad dependencies will appear as subfolders of
# ${buildout:directory}/launchpad/
# multiple urls can be specified, format is
# branch_url subfolder_name
# don't forget to add directory to extra_paths in [django]
[launchpad]
recipe = bazaarrecipe
urls =
https://launchpad.net/~hudson-openstack/glance/trunk/ glance
## Dependencies fetch from other bzr locations
#[bzrdeps]
#recipe = bazaarrecipe
#urls =
# https://launchpad.net/~hudson-openstack/glance/trunk/ glance

View File

@ -1,93 +1,344 @@
# 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.
"""
Methods and interface objects used to interact with external apis.
API method calls return objects that are in many cases objects with
attributes that are direct maps to the data returned from the API http call.
Unfortunately, these objects are also often constructed dynamically, making
it difficult to know what data is available from the API object. Because of
this, all API calls should wrap their returned object in one defined here,
using only explicitly defined atributes and/or methods.
In other words, django_openstack developers not working on django_openstack.api
shouldn't need to understand the finer details of APIs for Nova/Glance/Swift et
al.
"""
from django.conf import settings
import logging
import cloudfiles
import glance.client
import httplib
import json
import logging
import openstack.compute
import openstackx.admin
import openstackx.api.exceptions as api_exceptions
import openstackx.extras
import openstackx.auth
from urlparse import urlparse
import json
LOG = logging.getLogger('django_openstack.api')
class APIResourceWrapper(object):
""" Simple wrapper for api objects
Define _attrs on the child class and pass in the
api object as the only argument to the constructor
"""
_attrs = []
def __init__(self, apiresource):
self._apiresource = apiresource
def __getattr__(self, attr):
if attr in self._attrs:
# __getattr__ won't find properties
return self._apiresource.__getattribute__(attr)
else:
LOG.debug('Attempted to access unknown attribute "%s" on'
' APIResource object of type "%s" wrapping resource of'
' type "%s"' % (attr, self.__class__,
self._apiresource.__class__))
raise AttributeError(attr)
class APIDictWrapper(object):
""" Simple wrapper for api dictionaries
Some api calls return dictionaries. This class provides identical
behavior as APIResourceWrapper, except that it will also behave as a
dictionary, in addition to attribute accesses.
Attribute access is the preferred method of access, to be
consistent with api resource objects from openstackx
"""
def __init__(self, apidict):
self._apidict = apidict
def __getattr__(self, attr):
if attr in self._attrs:
try:
return self._apidict[attr]
except KeyError, e:
raise AttributeError(e)
else:
LOG.debug('Attempted to access unknown item "%s" on'
'APIResource object of type "%s"'
% (attr, self.__class__))
raise AttributeError(attr)
def __getitem__(self, item):
try:
return self.__getattr__(item)
except AttributeError, e:
# caller is expecting a KeyError
raise KeyError(e)
def get(self, item, default=None):
try:
return self.__getattr__(item)
except AttributeError:
return default
class Container(APIResourceWrapper):
"""Simple wrapper around cloudfiles.container.Container"""
_attrs = ['name']
class Console(APIResourceWrapper):
"""Simple wrapper around openstackx.extras.consoles.Console"""
_attrs = ['id', 'output', 'type']
class Flavor(APIResourceWrapper):
"""Simple wrapper around openstackx.admin.flavors.Flavor"""
_attrs = ['disk', 'id', 'links', 'name', 'ram', 'vcpus']
class Image(APIDictWrapper):
"""Simple wrapper around glance image dictionary"""
_attrs = ['checksum', 'container_format', 'created_at', 'deleted',
'deleted_at', 'disk_format', 'id', 'is_public', 'location',
'name', 'properties', 'size', 'status', 'updated_at']
def __getattr__(self, attrname):
if attrname == "properties":
return ImageProperties(super(Image, self).__getattr__(attrname))
else:
return super(Image, self).__getattr__(attrname)
class ImageProperties(APIDictWrapper):
"""Simple wrapper around glance image properties dictionary"""
_attrs = ['architecture', 'image_location', 'image_state', 'kernel_id',
'project_id', 'ramdisk_id']
class KeyPair(APIResourceWrapper):
"""Simple wrapper around openstackx.extras.keypairs.Keypair"""
_attrs = ['fingerprint', 'key_name', 'private_key']
class Server(APIResourceWrapper):
"""Simple wrapper around openstackx.extras.server.Server
Preserves the request info so image name can later be retrieved
"""
_attrs = ['addresses', 'attrs', 'hostId', 'id', 'imageRef', 'links',
'metadata', 'name', 'private_ip', 'public_ip', 'status', 'uuid',
'image_name']
def __init__(self, apiresource, request):
super(Server, self).__init__(apiresource)
self.request = request
def __getattr__(self, attr):
if attr == "attrs":
return ServerAttributes(super(Server, self).__getattr__(attr))
else:
return super(Server, self).__getattr__(attr)
@property
def image_name(self):
image = image_get(self.request, self.imageRef)
return image.name
class ServerAttributes(APIDictWrapper):
"""Simple wrapper around openstackx.extras.server.Server attributes
Preserves the request info so image name can later be retrieved
"""
_attrs = ['description', 'disk_gb', 'host', 'image_ref', 'kernel_id',
'key_name', 'launched_at', 'mac_address', 'memory_mb', 'name',
'os_type', 'project_id', 'ramdisk_id', 'scheduled_at',
'terminated_at', 'user_data', 'user_id', 'vcpus', 'hostname']
class Services(APIResourceWrapper):
_attrs = ['disabled', 'host', 'id', 'last_update', 'stats', 'type', 'up',
'zone']
class SwiftObject(APIResourceWrapper):
_attrs = ['name']
class Tenant(APIResourceWrapper):
"""Simple wrapper around openstackx.auth.tokens.Tenant"""
_attrs = ['id', 'description', 'enabled']
class Token(APIResourceWrapper):
"""Simple wrapper around openstackx.auth.tokens.Token"""
_attrs = ['id', 'serviceCatalog', 'tenant_id', 'username']
class Usage(APIResourceWrapper):
"""Simple wrapper around openstackx.extras.usage.Usage"""
_attrs = ['begin', 'instances', 'stop', 'tenant_id',
'total_active_disk_size', 'total_active_instances',
'total_active_ram_size', 'total_active_vcpus', 'total_cpu_usage',
'total_disk_usage', 'total_hours', 'total_ram_usage']
class User(APIResourceWrapper):
"""Simple wrapper around openstackx.extras.users.User"""
_attrs = ['email', 'enabled', 'id', 'tenantId']
def url_for(request, service_name, admin=False):
catalog = request.session['serviceCatalog']
if admin:
rv = catalog[service_name][0]['adminURL']
rv = catalog[service_name][0]['adminURL']
else:
rv = catalog[service_name][0]['internalURL']
return rv
def check_openstackx(f):
"""Decorator that adds extra info to api exceptions
The dashboard currently depends on openstackx extensions being present
in nova. Error messages depending for views depending on these
extensions do not lead to the conclusion that nova is missing
extensions.
This decorator should be dropped and removed after keystone and
dashboard more gracefully handle extensions and openstackx extensions
aren't required by the dashboard in nova.
"""
def inner(*args, **kwargs):
try:
return f(*args, **kwargs)
except api_exceptions.NotFound, e:
e.message = e.details or ''
e.message += ' This error may be caused by missing openstackx' \
' extensions in nova. See the dashboard README.'
raise
return inner
def compute_api(request):
compute = openstack.compute.Compute(auth_token=request.session['token'],
management_url=url_for(request, 'nova'))
compute = openstack.compute.Compute(
auth_token=request.session['token'],
management_url=url_for(request, 'nova'))
# this below hack is necessary to make the jacobian compute client work
# TODO(mgius): It looks like this is unused now?
compute.client.auth_token = request.session['token']
compute.client.management_url = url_for(request, 'nova')
LOG.debug('compute_api connection created using token "%s"'
' and url "%s"' %
(request.session['token'], url_for(request, 'nova')))
return compute
def account_api(request):
return openstackx.extras.Account(auth_token=request.session['token'],
management_url=url_for(request, 'keystone', True))
LOG.debug('account_api connection created using token "%s"'
' and url "%s"' %
(request.session['token'],
url_for(request, 'identity', True)))
return openstackx.extras.Account(
auth_token=request.session['token'],
management_url=url_for(request, 'identity', True))
def glance_api(request):
o = urlparse(url_for(request, 'glance'))
LOG.debug('glance_api connection created for host "%s:%d"' %
(o.hostname, o.port))
return glance.client.Client(o.hostname, o.port)
def admin_api(request):
LOG.debug('admin_api connection created using token "%s"'
' and url "%s"' %
(request.session['token'], url_for(request, 'nova', True)))
return openstackx.admin.Admin(auth_token=request.session['token'],
management_url=url_for(request, 'nova', True))
def extras_api(request):
LOG.debug('extras_api connection created using token "%s"'
' and url "%s"' %
(request.session['token'], url_for(request, 'nova')))
return openstackx.extras.Extras(auth_token=request.session['token'],
management_url=url_for(request, 'nova'))
def auth_api():
return openstackx.auth.Auth(management_url=\
settings.OPENSTACK_KEYSTONE_URL)
LOG.debug('auth_api connection created using url "%s"' %
settings.OPENSTACK_KEYSTONE_URL)
return openstackx.auth.Auth(
management_url=settings.OPENSTACK_KEYSTONE_URL)
def swift_api():
return cloudfiles.get_connection(
settings.SWIFT_ACCOUNT + ":" + settings.SWIFT_USER,
settings.SWIFT_PASS,
authurl=settings.SWIFT_AUTHURL)
def console_create(request, instance_id, kind='text'):
return extras_api(request).consoles.create(instance_id, kind)
return Console(extras_api(request).consoles.create(instance_id, kind))
def flavor_create(request, name, memory, vcpu, disk, flavor_id):
return admin_api(request).flavors.create(
name, int(memory), int(vcpu), int(disk), flavor_id)
return Flavor(admin_api(request).flavors.create(
name, int(memory), int(vcpu), int(disk), flavor_id))
def flavor_delete(request, flavor_id, purge=False):
return admin_api(request).flavors.delete(flavor_id, purge)
admin_api(request).flavors.delete(flavor_id, purge)
def flavor_get(request, flavor_id):
return compute_api(request).flavors.get(flavor_id)
return Flavor(compute_api(request).flavors.get(flavor_id))
@check_openstackx
def flavor_list(request):
return extras_api(request).flavors.list()
def flavor_list_admin(request):
return extras_api(request).flavors.list()
def image_all_metadata(request):
images = glance_api(request).get_images_detailed()
image_dict = {}
for image in images:
image_dict[image['id']] = image
return image_dict
return [Flavor(f) for f in extras_api(request).flavors.list()]
def image_create(request, image_meta, image_file):
return glance_api(request).add_image(image_meta, image_file)
return Image(glance_api(request).add_image(image_meta, image_file))
def image_delete(request, image_id):
@ -95,52 +346,56 @@ def image_delete(request, image_id):
def image_get(request, image_id):
return glance_api(request).get_image(image_id)[0]
return Image(glance_api(request).get_image(image_id)[0])
def image_list_detailed(request):
return glance_api(request).get_images_detailed()
return [Image(i) for i in glance_api(request).get_images_detailed()]
def image_update(request, image_id, image_meta=None):
image_meta = image_meta and image_meta or {}
return glance_api(request).update_image(image_id, image_meta=image_meta)
return Image(glance_api(request).update_image(image_id,
image_meta=image_meta))
def keypair_create(request, name):
return extras_api(request).keypairs.create(name)
return KeyPair(extras_api(request).keypairs.create(name))
def keypair_delete(request, keypair_id):
return extras_api(request).keypairs.delete(keypair_id)
extras_api(request).keypairs.delete(keypair_id)
@check_openstackx
def keypair_list(request):
return extras_api(request).keypairs.list()
return [KeyPair(key) for key in extras_api(request).keypairs.list()]
def server_create(request, name, image, flavor,
key_name, user_data, security_groups):
return extras_api(request).servers.create(
name, image, flavor, None, None, None,
key_name, user_data, security_groups)
def server_create(request, name, image, flavor, user_data, key_name):
return Server(extras_api(request).servers.create(
name, image, flavor, user_data=user_data, key_name=key_name),
request)
def server_delete(request, instance):
return compute_api(request).servers.delete(instance)
compute_api(request).servers.delete(instance)
def server_get(request, instance_id):
return extras_api(request).servers.get(instance_id)
return Server(compute_api(request).servers.get(instance_id), request)
@check_openstackx
def server_list(request):
return extras_api(request).servers.list()
return [Server(s, request) for s in extras_api(request).servers.list()]
def server_reboot(request, instance_id, hardness=openstack.compute.servers.REBOOT_HARD):
def server_reboot(request,
instance_id,
hardness=openstack.compute.servers.REBOOT_HARD):
server = server_get(request, instance_id)
return server.reboot(hardness)
server.reboot(hardness)
def server_update(request, instance_id, name, description):
@ -150,56 +405,68 @@ def server_update(request, instance_id, name, description):
def service_get(request, name):
return admin_api(request).services.get(name)
return Services(admin_api(request).services.get(name))
@check_openstackx
def service_list(request):
return admin_api(request).services.list()
return [Services(s) for s in admin_api(request).services.list()]
def service_update(request, name, enabled):
return admin_api(request).services.update(name, enabled)
return Services(admin_api(request).services.update(name, enabled))
def token_get_tenant(request, tenant_id):
tenants = auth_api().tenants.for_token(request.session['token'])
for t in tenants:
if str(t.id) == str(tenant_id):
return t
return Tenant(t)
LOG.warning('Unknown tenant id "%s" requested' % tenant_id)
def token_list_tenants(request, token):
return auth_api().tenants.for_token(token)
return [Tenant(t) for t in auth_api().tenants.for_token(token)]
def tenant_create(request, tenant_id, description, enabled):
return account_api(request).tenants.create(tenant_id, description, enabled)
return Tenant(account_api(request).tenants.create(tenant_id,
description,
enabled))
def tenant_get(request, tenant_id):
return account_api(request).tenants.get(tenant_id)
return Tenant(account_api(request).tenants.get(tenant_id))
@check_openstackx
def tenant_list(request):
return account_api(request).tenants.list()
return [Tenant(t) for t in account_api(request).tenants.list()]
def tenant_update(request, tenant_id, description, enabled):
return account_api(request).tenants.update(tenant_id, description, enabled)
return Tenant(account_api(request).tenants.update(tenant_id,
description,
enabled))
def token_create(request, tenant, username, password):
return auth_api().tokens.create(tenant, username, password)
return Token(auth_api().tokens.create(tenant, username, password))
def tenant_quota_get(request, tenant):
return admin_api(request).quota_sets.get(tenant)
def token_info(request, token):
# TODO(mgius): This function doesn't make a whole lot of sense to me. The
# information being gathered here really aught to be attached to Token() as
# part of token_create. May require modification of openstackx so that the
# token_create call returns this information as well
hdrs = {"Content-type": "application/json",
"X_AUTH_TOKEN": settings.OPENSTACK_ADMIN_TOKEN,
"Accept": "text/json"}
o = urlparse(token.serviceCatalog['keystone'][0]['adminURL'])
o = urlparse(token.serviceCatalog['identity'][0]['adminURL'])
conn = httplib.HTTPConnection(o.hostname, o.port)
conn.request("GET", "/v2.0/tokens/%s" % token.id, headers=hdrs)
response = conn.getresponse()
@ -215,38 +482,108 @@ def token_info(request, token):
'admin': admin}
@check_openstackx
def usage_get(request, tenant_id, start, end):
return extras_api(request).usage.get(tenant_id, start, end)
return Usage(extras_api(request).usage.get(tenant_id, start, end))
@check_openstackx
def usage_list(request, start, end):
return extras_api(request).usage.list(start, end)
return [Usage(u) for u in extras_api(request).usage.list(start, end)]
def user_create(request, user_id, email, password, tenant_id, enabled):
return account_api(request).users.create(
user_id, email, password, tenant_id, enabled)
return User(account_api(request).users.create(
user_id, email, password, tenant_id, enabled))
def user_delete(request, user_id):
return account_api(request).users.delete(user_id)
account_api(request).users.delete(user_id)
def user_get(request, user_id):
return account_api(request).users.get(user_id)
return User(account_api(request).users.get(user_id))
@check_openstackx
def user_list(request):
return account_api(request).users.list()
return [User(u) for u in account_api(request).users.list()]
def user_update_email(request, user_id, email):
return account_api(request).users.update_email(user_id, email)
return User(account_api(request).users.update_email(user_id, email))
def user_update_password(request, user_id, password):
return account_api(request).users.update_password(user_id, password)
return User(account_api(request).users.update_password(user_id, password))
def user_update_tenant(request, user_id, tenant_id):
return account_api(request).users.update_tenant(user_id, tenant_id)
return User(account_api(request).users.update_tenant(user_id, tenant_id))
def swift_container_exists(container_name):
try:
swift_api().get_container(container_name)
return True
except cloudfiles.errors.NoSuchContainer:
return False
def swift_object_exists(container_name, object_name):
container = swift_api().get_container(container_name)
try:
container.get_object(object_name)
return True
except cloudfiles.errors.NoSuchObject:
return False
def swift_get_containers():
return [Container(c) for c in swift_api().get_all_containers()]
def swift_create_container(name):
if swift_container_exists(name):
raise Exception('Container with name %s already exists.' % (name))
return Container(swift_api().create_container(name))
def swift_delete_container(name):
swift_api().delete_container(name)
def swift_get_objects(container_name, prefix=None):
container = swift_api().get_container(container_name)
return [SwiftObject(o) for o in container.get_objects(prefix=prefix)]
def swift_copy_object(orig_container_name, orig_object_name,
new_container_name, new_object_name):
container = swift_api().get_container(orig_container_name)
if swift_object_exists(new_container_name, new_object_name) == True:
raise Exception('Object with name %s already exists in container %s'
% (new_object_name, new_container_name))
orig_obj = container.get_object(orig_object_name)
return orig_obj.copy_to(new_container_name, new_object_name)
def swift_upload_object(container_name, object_name, object_data):
container = swift_api().get_container(container_name)
obj = container.create_object(object_name)
obj.write(object_data)
def swift_delete_object(container_name, object_name):
container = swift_api().get_container(container_name)
container.delete_object(object_name)
def swift_get_object_data(container_name, object_name):
container = swift_api().get_container(container_name)
return container.get_object(object_name).stream()

View File

@ -1,5 +1,23 @@
# 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.
from django.conf.urls.defaults import *
from django.conf import settings

View File

@ -1,8 +1,25 @@
# 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.
import logging
from django import http
from django import template
from django import shortcuts
from django.contrib import messages
@ -12,10 +29,13 @@ from django_openstack import forms
from openstackx.api import exceptions as api_exceptions
LOG = logging.getLogger('django_openstack.auth')
class Login(forms.SelfHandlingForm):
username = forms.CharField(max_length="20", label="User Name")
password = forms.CharField(max_length="20", label="Password",
widget=forms.PasswordInput())
widget=forms.PasswordInput(render_value=False))
def handle(self, request, data):
try:
@ -30,12 +50,15 @@ class Login(forms.SelfHandlingForm):
request.session['tenant'] = data.get('tenant', info['tenant'])
request.session['admin'] = info['admin']
request.session['serviceCatalog'] = token.serviceCatalog
logging.info(token.serviceCatalog)
LOG.info('Login form for user "%s". Service Catalog data:\n%s' %
(data['username'], token.serviceCatalog))
return shortcuts.redirect('dash_overview')
except api_exceptions.Unauthorized as e:
messages.error(request, 'Error authenticating: %s' % e.message)
msg = 'Error authenticating: %s' % e.message
LOG.error(msg, exc_info=True)
messages.error(request, msg)
except api_exceptions.ApiException as e:
messages.error(request, 'Error authenticating with keystone: %s' %
e.message)
@ -65,7 +88,8 @@ def login(request):
def switch_tenants(request, tenant_id):
form, handled = LoginWithTenant.maybe_handle(
request, initial={'tenant': tenant_id, 'username': request.user.username})
request, initial={'tenant': tenant_id,
'username': request.user.username})
if handled:
return handled

View File

@ -1,3 +1,24 @@
# 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.
from django.conf import settings
from django_openstack import api
from django.contrib import messages
from openstackx.api import exceptions as api_exceptions
@ -13,3 +34,7 @@ def tenants(request):
messages.error(request, "Unable to retrieve tenant list from\
keystone: %s" % e.message)
return {'tenants': []}
def swift(request):
return {'swift_configured': hasattr(settings, "SWIFT_AUTHURL")}

View File

@ -1,11 +1,30 @@
# 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.
from django.conf.urls.defaults import *
from django.conf import settings
INSTANCES = r'^(?P<tenant_id>[^/]+)/instances/(?P<instance_id>[^/]+)/%s$'
IMAGES = r'^(?P<tenant_id>[^/]+)/images/(?P<image_id>[^/]+)/%s$'
KEYPAIRS = r'^(?P<tenant_id>[^/]+)/keypairs/%s$'
CONTAINERS = r'^(?P<tenant_id>[^/]+)/containers/%s$'
OBJECTS = r'^(?P<tenant_id>[^/]+)/containers/(?P<container_name>[^/]+)/%s$'
urlpatterns = patterns('django_openstack.dash.views.instances',
url(r'^(?P<tenant_id>[^/]+)/$', 'usage', name='dash_usage'),
@ -25,3 +44,18 @@ urlpatterns += patterns('django_openstack.dash.views.keypairs',
url(r'^(?P<tenant_id>[^/]+)/keypairs/$', 'index', name='dash_keypairs'),
url(KEYPAIRS % 'create', 'create', name='dash_keypairs_create'),
)
# Swift containers and objects.
urlpatterns += patterns('django_openstack.dash.views.containers',
url(CONTAINERS % '', 'index', name='dash_containers'),
url(CONTAINERS % 'create', 'create', name='dash_containers_create'),
)
urlpatterns += patterns('django_openstack.dash.views.objects',
url(OBJECTS % '', 'index', name='dash_objects'),
url(OBJECTS % 'upload', 'upload', name='dash_objects_upload'),
url(OBJECTS % '(?P<object_name>[^/]+)/copy',
'copy', name='dash_object_copy'),
url(OBJECTS % '(?P<object_name>[^/]+)/download',
'download', name='dash_objects_download'),
)

View File

@ -0,0 +1,90 @@
# 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 Swift containers.
"""
import logging
from django import template
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django import shortcuts
from django_openstack import api
from django_openstack import forms
from cloudfiles.errors import ContainerNotEmpty
LOG = logging.getLogger('django_openstack.dash')
class DeleteContainer(forms.SelfHandlingForm):
container_name = forms.CharField(widget=forms.HiddenInput())
def handle(self, request, data):
try:
api.swift_delete_container(data['container_name'])
except ContainerNotEmpty, e:
messages.error(request,
'Unable to delete non-empty container: %s' % \
data['container_name'])
LOG.error('Unable to delete container "%s". Exception: "%s"' %
(data['container_name'], str(e)))
else:
messages.info(request,
'Successfully deleted container: %s' % \
data['container_name'])
return shortcuts.redirect(request.build_absolute_uri())
class CreateContainer(forms.SelfHandlingForm):
name = forms.CharField(max_length="255", label="Container Name")
def handle(self, request, data):
api.swift_create_container(data['name'])
messages.success(request, "Container was successfully created.")
return shortcuts.redirect(request.build_absolute_uri())
@login_required
def index(request, tenant_id):
delete_form, handled = DeleteContainer.maybe_handle(request)
if handled:
return handled
containers = api.swift_get_containers()
return shortcuts.render_to_response('dash_containers.html', {
'containers': containers,
'delete_form': delete_form,
}, context_instance=template.RequestContext(request))
@login_required
def create(request, tenant_id):
form, handled = CreateContainer.maybe_handle(request)
if handled:
return handled
return shortcuts.render_to_response('dash_containers_create.html', {
'create_form': form,
}, context_instance=template.RequestContext(request))

View File

@ -1,9 +1,11 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# 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
@ -39,7 +41,7 @@ from openstackx.api import exceptions as api_exceptions
from glance.common import exception as glance_exception
LOG = logging.getLogger('django_openstack.dash')
LOG = logging.getLogger('django_openstack.dash.views.images')
from django.core import validators
import re
@ -59,8 +61,6 @@ class LaunchForm(forms.SelfHandlingForm):
required=False)
name = forms.CharField(max_length=80, label="Server Name")
security_groups = forms.CharField(max_length=255, validators=[validators.RegexValidator(regex=re.compile(r'^[0-9A-Za-z,\.\-_]*$'))], required=False)
# make the dropdown populate when the form is loaded not when django is
# started
def __init__(self, *args, **kwargs):
@ -82,7 +82,6 @@ class LaunchForm(forms.SelfHandlingForm):
field_list = (
'name',
'user_data',
'security_groups',
'flavor',
'key_name')
for field in field_list[::-1]:
@ -100,14 +99,16 @@ class LaunchForm(forms.SelfHandlingForm):
image,
flavor,
data.get('key_name'),
data.get('user_data'),
data.get('security_groups').split(','))
data.get('user_data'))
messages.success(request, "Instance was successfully\
launched.")
msg = 'Instance was successfully launched'
LOG.info(msg)
messages.success(request, msg)
return redirect('dash_instances', tenant_id)
except api_exceptions.ApiException, e:
LOG.error('ApiException while creating instances of image "%s"' %
image_id, exc_info=True)
messages.error(request,
'Unable to launch instance: %s' % e.message)
@ -127,24 +128,15 @@ def index(request, tenant_id):
all_images = api.image_list_detailed(request)
if not all_images:
messages.info(request, "There are currently no images.")
except GlanceClientConnectionError, e:
messages.error(request, "Error connecting to glance: %s" % e.message)
except glance_exception.ClientConnectionError, e:
LOG.error("Error connecting to glance", exc_info=True)
messages.error(request, "Error connecting to glance: %s" % str(e))
except glance_exception.Error, e:
messages.error(request, "Error retrieving image list: %s" % e.message)
LOG.error("Error retrieving image list", exc_info=True)
messages.error(request, "Error retrieving image list: %s" % str(e))
images = []
def convert_time(tstr):
if tstr:
return datetime.datetime.strptime(tstr, "%Y-%m-%dT%H:%M:%S.%f")
else:
return ''
for im in all_images:
im['created'] = convert_time(im['created_at'])
im['updated'] = convert_time(im['updated_at'])
if im['container_format'] not in ['aki', 'ari']:
images.append(im)
images = [im for im in all_images
if im['container_format'] not in ['aki', 'ari']]
return render_to_response('dash_images.html', {
'tenant': tenant,
@ -162,7 +154,9 @@ def launch(request, tenant_id, image_id):
sel = [(f.id, '%s (%svcpu / %sGB Disk / %sMB Ram )' %
(f.name, f.vcpus, f.disk, f.ram)) for f in fl]
return sorted(sel)
except:
except api_exceptions.ApiException:
LOG.error('Unable to retrieve list of instance types',
exc_info=True)
return [(1, 'm1.tiny')]
def keynamelist():
@ -170,15 +164,17 @@ def launch(request, tenant_id, image_id):
fl = api.keypair_list(request)
sel = [(f.key_name, f.key_name) for f in fl]
return sel
except:
except api_exceptions.ApiException:
LOG.error('Unable to retrieve list of keypairs', exc_info=True)
return []
# TODO(mgius): Any reason why these can't be after the launchform logic?
# If The form is valid, we've just wasted these two api calls
try:
image = api.image_get(request, image_id)
tenant = api.token_get_tenant(request, request.user.tenant)
quotas = api.tenant_quota_get(request, request.user.tenant)
quotas.ram = int(quotas.ram)/100
except Exception, e:
messages.error(request, 'Unable to retrieve image %s: %s' %
(image_id, e.message))

View File

@ -1,9 +1,11 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# 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
@ -23,15 +25,16 @@ import datetime
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.shortcuts import redirect, render_to_response
from django.utils.translation import ugettext as _
from django_openstack import api
from django_openstack import forms
from django_openstack import utils
import openstack.compute.servers
import openstackx.api.exceptions as api_exceptions
@ -49,14 +52,17 @@ class TerminateInstance(forms.SelfHandlingForm):
try:
api.server_delete(request, instance)
except api_exceptions.ApiException, e:
LOG.error('ApiException while terminating instance "%s"' %
instance_id, exc_info=True)
messages.error(request,
'Unable to terminate %s: %s' %
(instance_id, e.message,))
else:
messages.success(request,
'Instance %s has been terminated.' % instance_id)
msg = 'Instance %s has been terminated.' % instance_id
LOG.info(msg)
messages.success(request, msg)
return redirect(request.build_absolute_uri())
return shortcuts.redirect(request.build_absolute_uri())
class RebootInstance(forms.SelfHandlingForm):
@ -68,10 +74,17 @@ class RebootInstance(forms.SelfHandlingForm):
server = api.server_reboot(request, instance_id)
messages.success(request, "Instance rebooting")
except api_exceptions.ApiException, e:
LOG.error('ApiException while rebooting instance "%s"' %
instance_id, exc_info=True)
messages.error(request,
'Unable to reboot instance: %s' % e.message)
return redirect(request.build_absolute_uri())
else:
msg = 'Instance %s has been rebooted.' % instance_id
LOG.info(msg)
messages.success(request, msg)
return shortcuts.redirect(request.build_absolute_uri())
class UpdateInstance(forms.Form):
@ -89,13 +102,13 @@ def index(request, tenant_id):
return handled
instances = []
try:
image_dict = api.image_all_metadata(request)
instances = api.server_list(request)
for instance in instances:
# FIXME - ported this over, but it is hacky
instance.attrs['image_name'] =\
image_dict.get(int(instance.attrs['image_ref']),{}).get('name')
except Exception as e:
except api_exceptions.ApiException as e:
LOG.error('Exception in instance index', exc_info=True)
messages.error(request, 'Unable to get instance list: %s' % e.message)
# We don't have any way of showing errors for these, so don't bother
@ -103,7 +116,7 @@ def index(request, tenant_id):
terminate_form = TerminateInstance()
reboot_form = RebootInstance()
return render_to_response('dash_instances.html', {
return shortcuts.render_to_response('dash_instances.html', {
'instances': instances,
'terminate_form': terminate_form,
'reboot_form': reboot_form,
@ -139,10 +152,10 @@ def refresh(request, tenant_id):
@login_required
def usage(request, tenant_id=None):
today = datetime.date.today()
today = utils.today()
date_start = datetime.date(today.year, today.month, 1)
datetime_start = datetime.datetime.combine(date_start, datetime.time())
datetime_end = datetime.datetime.utcnow()
datetime_start = datetime.datetime.combine(date_start, utils.time())
datetime_end = utils.utcnow()
show_terminated = request.GET.get('show_terminated', False)
@ -153,6 +166,8 @@ def usage(request, tenant_id=None):
try:
usage = api.usage_get(request, tenant_id, datetime_start, datetime_end)
except api_exceptions.ApiException, e:
LOG.error('ApiException in instance usage', exc_info=True)
messages.error(request, 'Unable to get usage info: %s' % e.message)
ram_unit = "MB"
@ -180,7 +195,7 @@ def usage(request, tenant_id=None):
if show_terminated:
instances += terminated_instances
return render_to_response('dash_usage.html', {
return shortcuts.render_to_response('dash_usage.html', {
'usage': usage,
'ram_unit': ram_unit,
'total_ram': total_ram,
@ -198,10 +213,13 @@ def console(request, tenant_id, instance_id):
response.flush()
return response
except api_exceptions.ApiException, e:
LOG.error('ApiException while fetching instance console',
exc_info=True)
messages.error(request,
'Unable to get log for instance %s: %s' %
(instance_id, e.message))
return redirect('dash_instances', tenant_id)
return shortcuts.redirect('dash_instances', tenant_id)
@login_required
@ -209,12 +227,15 @@ def vnc(request, tenant_id, instance_id):
try:
console = api.console_create(request, instance_id, 'vnc')
instance = api.server_get(request, instance_id)
return redirect(console.output + ("&title=%s(%s)" % (instance.name, instance_id)))
return shortcuts.redirect(console.output + ("&title=%s(%s)" % (instance.name, instance_id)))
except api_exceptions.ApiException, e:
LOG.error('ApiException while fetching instance vnc connection',
exc_info=True)
messages.error(request,
'Unable to get vnc console for instance %s: %s' %
(instance_id, e.message))
return redirect('dash_instances', tenant_id)
return shortcuts.redirect('dash_instances', tenant_id)
@login_required

View File

@ -1,9 +1,11 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# 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
@ -19,7 +21,6 @@
"""
Views for managing Nova instances.
"""
import datetime
import logging
from django import http
@ -34,11 +35,10 @@ from django.utils.translation import ugettext as _
from django_openstack import api
from django_openstack import forms
import openstack.compute.servers
import openstackx.api.exceptions as api_exceptions
LOG = logging.getLogger('django_openstack.dash')
LOG = logging.getLogger('django_openstack.dash.views.keypairs')
class DeleteKeypair(forms.SelfHandlingForm):
@ -46,18 +46,22 @@ class DeleteKeypair(forms.SelfHandlingForm):
def handle(self, request, data):
try:
LOG.info('Deleting keypair "%s"' % data['keypair_id'])
keypair = api.keypair_delete(request, data['keypair_id'])
messages.info(request, 'Successfully deleted keypair: %s' \
% data['keypair_id'])
except api_exceptions.ApiException, e:
LOG.error("ApiException in DeleteKeypair", exc_info=True)
messages.error(request, 'Error deleting keypair: %s' % e.message)
return shortcuts.redirect(request.build_absolute_uri())
class CreateKeypair(forms.SelfHandlingForm):
name = forms.CharField(max_length="20", label="Keypair Name")
def handle(self, request, data):
try:
LOG.info('Creating keypair "%s"' % data['name'])
keypair = api.keypair_create(request, slugify(data['name']))
response = http.HttpResponse(mimetype='application/binary')
response['Content-Disposition'] = \
@ -66,9 +70,11 @@ class CreateKeypair(forms.SelfHandlingForm):
response.write(keypair.private_key)
return response
except api_exceptions.ApiException, e:
LOG.error("ApiException in CreateKeyPair", exc_info=True)
messages.error(request, 'Error Creating Keypair: %s' % e.message)
return shortcuts.redirect(request.build_absolute_uri())
@login_required
def index(request, tenant_id):
delete_form, handled = DeleteKeypair.maybe_handle(request)
@ -77,19 +83,21 @@ def index(request, tenant_id):
keypairs = api.keypair_list(request)
except api_exceptions.ApiException, e:
keypairs = []
messages.error(request, 'Error featching keypairs: %s' % e.message)
LOG.error("ApiException in keypair index", exc_info=True)
messages.error(request, 'Error fetching keypairs: %s' % e.message)
return render_to_response('dash_keypairs.html', {
return shortcuts.render_to_response('dash_keypairs.html', {
'keypairs': keypairs,
'delete_form': delete_form,
}, context_instance=template.RequestContext(request))
@login_required
def create(request, tenant_id):
form, handled = CreateKeypair.maybe_handle(request)
if handled:
return handled
return render_to_response('dash_keypairs_create.html', {
return shortcuts.render_to_response('dash_keypairs_create.html', {
'create_form': form,
}, context_instance=template.RequestContext(request))

View File

@ -0,0 +1,176 @@
# 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 Swift containers.
"""
import logging
from django import http
from django import template
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django import shortcuts
from django.shortcuts import render_to_response
from django_openstack import api
from django_openstack import forms
LOG = logging.getLogger('django_openstack.dash')
class FilterObjects(forms.SelfHandlingForm):
container_name = forms.CharField(widget=forms.HiddenInput())
object_prefix = forms.CharField(required=False)
def handle(self, request, data):
object_prefix = data['object_prefix'] or None
objects = api.swift_get_objects(data['container_name'],
prefix=object_prefix)
return objects
class DeleteObject(forms.SelfHandlingForm):
object_name = forms.CharField(widget=forms.HiddenInput())
container_name = forms.CharField(widget=forms.HiddenInput())
def handle(self, request, data):
api.swift_delete_object(
data['container_name'],
data['object_name'])
messages.info(request,
'Successfully deleted object: %s' % \
data['object_name'])
return shortcuts.redirect(request.build_absolute_uri())
class UploadObject(forms.SelfHandlingForm):
name = forms.CharField(max_length="255", label="Object Name")
object_file = forms.FileField(label="File")
container_name = forms.CharField(widget=forms.HiddenInput())
def handle(self, request, data):
api.swift_upload_object(
data['container_name'],
data['name'],
self.files['object_file'].read())
messages.success(request, "Object was successfully uploaded.")
return shortcuts.redirect(request.build_absolute_uri())
class CopyObject(forms.SelfHandlingForm):
new_container_name = forms.ChoiceField(
label="Container to store object in")
new_object_name = forms.CharField(max_length="255",
label="New object name")
orig_container_name = forms.CharField(widget=forms.HiddenInput())
orig_object_name = forms.CharField(widget=forms.HiddenInput())
def __init__(self, *args, **kwargs):
super(CopyObject, self).__init__(*args, **kwargs)
container_choices = \
[(c.name, c.name) for c in api.swift_get_containers()]
self.fields['new_container_name'].choices = container_choices
def handle(self, request, data):
orig_container_name = data['orig_container_name']
orig_object_name = data['orig_object_name']
new_container_name = data['new_container_name']
new_object_name = data['new_object_name']
api.swift_copy_object(orig_container_name, orig_object_name,
new_container_name, new_object_name)
messages.success(request,
'Object was successfully copied to %s\%s' %
(new_container_name, new_object_name))
return shortcuts.redirect(request.build_absolute_uri())
@login_required
def index(request, tenant_id, container_name):
delete_form, handled = DeleteObject.maybe_handle(request)
if handled:
return handled
filter_form, objects = FilterObjects.maybe_handle(request)
if not objects:
filter_form.fields['container_name'].initial = container_name
objects = api.swift_get_objects(container_name)
delete_form.fields['container_name'].initial = container_name
return render_to_response('dash_objects.html', {
'container_name': container_name,
'objects': objects,
'delete_form': delete_form,
'filter_form': filter_form,
}, context_instance=template.RequestContext(request))
@login_required
def upload(request, tenant_id, container_name):
form, handled = UploadObject.maybe_handle(request)
if handled:
return handled
form.fields['container_name'].initial = container_name
return render_to_response('dash_objects_upload.html', {
'container_name': container_name,
'upload_form': form,
}, context_instance=template.RequestContext(request))
@login_required
def download(request, tenant_id, container_name, object_name):
object_data = api.swift_get_object_data(
container_name, object_name)
response = http.HttpResponse()
response['Content-Disposition'] = 'attachment; filename=%s' % \
object_name
for data in object_data:
response.write(data)
return response
@login_required
def copy(request, tenant_id, container_name, object_name):
form, handled = CopyObject.maybe_handle(request)
if handled:
return handled
form.fields['new_container_name'].initial = container_name
form.fields['orig_container_name'].initial = container_name
form.fields['orig_object_name'].initial = object_name
return render_to_response(
'dash_object_copy.html',
{'container_name': container_name,
'object_name': object_name,
'copy_form': form},
context_instance=template.RequestContext(request))

View File

@ -1,5 +1,23 @@
# 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.
import datetime
import logging
import re
@ -14,7 +32,7 @@ from django.utils import formats
from django.forms import *
LOG = logging.getLogger('django_openstack.forms')
RE_DATE = re.compile(r'(\d{4})-(\d\d?)-(\d\d?)$')
@ -132,14 +150,16 @@ class SelfHandlingForm(Form):
kwargs['initial'] = initial
super(SelfHandlingForm, self).__init__(*args, **kwargs)
@classmethod
def maybe_handle(cls, request, *args, **kwargs):
if cls.__name__ != request.POST.get('method'):
return cls(*args, **kwargs), None
try:
form = cls(request.POST, *args, **kwargs)
if request.FILES:
form = cls(request.POST, request.FILES, *args, **kwargs)
else:
form = cls(request.POST, *args, **kwargs)
if not form.is_valid():
return form, None
@ -148,7 +168,7 @@ class SelfHandlingForm(Form):
return form, form.handle(request, data)
except Exception as e:
logging.exception('Error while handling form.')
LOG.error('Nonspecific error while handling form', exc_info=True)
messages.error(request, 'Unexpected error: %s' % e.message)
return form, None

View File

@ -1,4 +1,23 @@
# 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.
from django.contrib import messages
from django import shortcuts
import openstackx

View File

@ -1,3 +1,23 @@
'''
# 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.
"""
Stub file to work around django bug: https://code.djangoproject.com/ticket/7198
'''
"""

View File

@ -1,3 +1,23 @@
# 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.
from django import forms

View File

@ -1,3 +1,23 @@
# 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.
from django.conf.urls.defaults import *
from django.conf import settings

View File

@ -1,5 +1,25 @@
# 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.
import logging
from operator import itemgetter
from django import template
@ -15,6 +35,8 @@ from openstackx.api import exceptions as api_exceptions
from django_openstack import api
from django_openstack import forms
LOG = logging.getLogger('django_openstack.syspanel.views.flavors')
class CreateFlavor(forms.SelfHandlingForm):
flavorid = forms.CharField(max_length="10", label="Flavor ID")
@ -30,8 +52,9 @@ class CreateFlavor(forms.SelfHandlingForm):
int(data['vcpus']),
int(data['disk_gb']),
int(data['flavorid']))
messages.success(request,
'%s was successfully added to flavors.' % data['name'])
msg = '%s was successfully added to flavors.' % data['name']
LOG.info(msg)
messages.success(request, msg)
return redirect('syspanel_flavors')
@ -42,6 +65,7 @@ class DeleteFlavor(forms.SelfHandlingForm):
try:
flavor_id = data['flavorid']
flavor = api.flavor_get(request, flavor_id)
LOG.info('Deleting flavor with id "%s"' % flavor_id)
api.flavor_delete(request, flavor_id, False)
messages.info(request, 'Successfully deleted flavor: %s' %
flavor.name)
@ -61,8 +85,9 @@ def index(request):
flavors = []
try:
flavors = api.flavor_list_admin(request)
flavors = api.flavor_list(request)
except api_exceptions.ApiException, e:
LOG.error('ApiException while fetching usage info', exc_info=True)
messages.error(request, 'Unable to get usage info: %s' % e.message)
flavors.sort(key=lambda x: x.id, reverse=True)

View File

@ -1,18 +1,40 @@
# 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.
import logging
from django import template
from django import http
from django.conf import settings
from django.contrib import messages
from django.shortcuts import redirect
from django.shortcuts import render_to_response
from django.contrib.auth.decorators import login_required
from glance.common import exception as glance_exception
from django_openstack import api
from django_openstack import forms
LOG = logging.getLogger('django_openstack.sysadmin.views.images')
class DeleteImage(forms.SelfHandlingForm):
image_id = forms.CharField(required=True)
@ -21,8 +43,12 @@ class DeleteImage(forms.SelfHandlingForm):
try:
api.image_delete(request, image_id)
except glance_exception.ClientConnectionError, e:
messages.error(request, "Error connecting to glance: %s" % e.message)
LOG.error("Error connecting to glance", exc_info=True)
messages.error(request,
"Error connecting to glance: %s" % e.message)
except glance_exception.Error, e:
LOG.error('Error deleting image with id "%s"' % image_id,
exc_info=True)
messages.error(request, "Error deleting image: %s" % e.message)
return redirect(request.build_absolute_uri())
@ -35,8 +61,12 @@ class ToggleImage(forms.SelfHandlingForm):
try:
api.image_update(request, image_id, image_meta={'is_public': False})
except glance_exception.ClientConnectionError, e:
messages.error(request, "Error connecting to glance: %s" % e.message)
LOG.error("Error connecting to glance", exc_info=True)
messages.error(request,
"Error connecting to glance: %s" % e.message)
except glance_exception.Error, e:
LOG.error('Error updating image with id "%s"' % image_id,
exc_info=True)
messages.error(request, "Error updating image: %s" % e.message)
return redirect(request.build_absolute_uri())
@ -50,7 +80,6 @@ class UpdateImageForm(forms.Form):
disk_format = forms.CharField(label="Disk Format")
#is_public = forms.BooleanField(label="Publicly Available", required=False)
@login_required
def index(request):
for f in (DeleteImage, ToggleImage):
@ -69,8 +98,10 @@ def index(request):
if not images:
messages.info(request, "There are currently no images.")
except glance_exception.ClientConnectionError, e:
LOG.error("Error connecting to glance", exc_info=True)
messages.error(request, "Error connecting to glance: %s" % e.message)
except glance_exception.Error, e:
LOG.error("Error retrieving image list", exc_info=True)
messages.error(request, "Error retrieving image list: %s" % e.message)
return render_to_response('syspanel_images.html', {
@ -85,9 +116,13 @@ def update(request, image_id):
try:
image = api.image_get(request, image_id)
except glance_exception.ClientConnectionError, e:
LOG.error("Error connecting to glance", exc_info=True)
messages.error(request, "Error connecting to glance: %s" % e.message)
except glance_exception.Error, e:
messages.error(request, "Error retrieving image %s: %s" % (image_id, e.message))
LOG.error('Error retrieving image with id "%s"' % image_id,
exc_info=True)
messages.error(request,
"Error retrieving image %s: %s" % (image_id, e.message))
if request.method == "POST":
form = UpdateImageForm(request.POST)
@ -111,12 +146,24 @@ def update(request, image_id):
api.image_update(request, image_id, metadata)
messages.success(request, "Image was successfully updated.")
except glance_exception.ClientConnectionError, e:
messages.error(request, "Error connecting to glance: %s" % e.message)
LOG.error("Error connecting to glance", exc_info=True)
messages.error(request,
"Error connecting to glance: %s" % e.message)
except glance_exception.Error, e:
LOG.error('Error updating image with id "%s"' % image_id,
exc_info=True)
messages.error(request, "Error updating image: %s" % e.message)
return redirect("syspanel_images")
except:
LOG.error('Unspecified Exception in image update',
exc_info=True)
messages.error(request,
"Image could not be updated, please try again.")
else:
messages.error(request, "Image could not be updated, please try agian.")
LOG.error('Image "%s" failed to update' % image['name'],
exc_info=True)
messages.error(request,
"Image could not be uploaded, please try agian.")
form = UpdateImageForm(request.POST)
return render_to_response('syspanel_image_update.html',{
'image': image,
@ -141,3 +188,47 @@ def update(request, image_id):
'form': form,
}, context_instance = template.RequestContext(request))
@login_required
def upload(request):
if request.method == "POST":
form = UploadImageForm(request.POST)
if form.is_valid():
image = form.clean()
metadata = {'is_public': image['is_public'],
'disk_format': 'ami',
'container_format': 'ami',
'name': image['name']}
try:
messages.success(request, "Image was successfully uploaded.")
except:
# TODO add better error management
messages.error(request, "Image could not be uploaded, please try again.")
try:
api.image_create(request, metadata, image['image_file'])
except glance_exception.ClientConnectionError, e:
LOG.error('Error connecting to glance while trying to upload'
' image', exc_info=True)
messages.error(request,
"Error connecting to glance: %s" % e.message)
except glance_exception.Error, e:
LOG.error('Glance exception while uploading image',
exc_info=True)
messages.error(request, "Error adding image: %s" % e.message)
else:
LOG.error('Image "%s" failed to upload' % image['name'],
exc_info=True)
messages.error(request,
"Image could not be uploaded, please try agian.")
form = UploadImageForm(request.POST)
return render_to_response('django_nova_syspanel/images/image_upload.html',{
'form': form,
}, context_instance = template.RequestContext(request))
return redirect('syspanel_images')
else:
form = UploadImageForm()
return render_to_response('django_nova_syspanel/images/image_upload.html',{
'form': form,
}, context_instance = template.RequestContext(request))

View File

@ -1,5 +1,23 @@
# 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.
from django import template
from django import http
from django.conf import settings
@ -21,6 +39,8 @@ from openstackx.api import exceptions as api_exceptions
TerminateInstance = dash_instances.TerminateInstance
RebootInstance = dash_instances.RebootInstance
LOG = logging.getLogger('django_openstack.syspanel.views.instances')
def _next_month(date_start):
y = date_start.year + (date_start.month + 1)/13
@ -67,7 +87,10 @@ def usage(request):
try:
service_list = api.service_list(request)
except api_exceptions.ApiException, e:
messages.error(request, 'Unable to get service info: %s' % e.message)
LOG.error('ApiException fetching service list in instance usage',
exc_info=True)
messages.error(request,
'Unable to get service info: %s' % e.message)
for service in service_list:
if service.type == 'nova-compute':
@ -78,6 +101,10 @@ def usage(request):
try:
usage_list = api.usage_list(request, datetime_start, datetime_end)
except api_exceptions.ApiException, e:
LOG.error('ApiException fetching usage list in instance usage'
' on date range "%s to %s"' % (datetime_start,
datetime_end),
exc_info=True)
messages.error(request, 'Unable to get usage info: %s' % e.message)
dateform = forms.DateForm()
@ -88,9 +115,13 @@ def usage(request):
'total_active_ram_size': 0}
for usage in usage_list:
usage = usage.to_dict()
for k in usage:
v = usage[k]
# FIXME: api needs a simpler dict interface (with iteration) - anthony
# NOTE(mgius): Changed this on the api end. Not too much neater, but
# at least its not going into private member data of an external
# class anymore
#usage = usage._info
for k in usage._attrs:
v = usage.__getattr__(k)
if type(v) in [float, int]:
if not k in global_summary:
global_summary[k] = 0
@ -145,6 +176,10 @@ def tenant_usage(request, tenant_id):
try:
usage = api.usage_get(request, tenant_id, datetime_start, datetime_end)
except api_exceptions.ApiException, e:
LOG.error('ApiException getting usage info for tenant "%s"'
' on date range "%s to %s"' % (tenant_id,
datetime_start,
datetime_end))
messages.error(request, 'Unable to get usage info: %s' % e.message)
running_instances = []
@ -177,13 +212,13 @@ def index(request):
instances = []
try:
image_dict = api.image_all_metadata(request)
instances = api.server_list(request)
for instance in instances:
# FIXME - ported this over, but it is hacky
instance._info['attrs']['image_name'] =\
image_dict.get(int(instance.attrs['image_ref']),{}).get('name')
except Exception as e:
LOG.error('Unspecified error in instance index', exc_info=True)
messages.error(request, 'Unable to get instance list: %s' % e.message)
# We don't have any way of showing errors for these, so don't bother

View File

@ -1,5 +1,23 @@
# 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.
from django import template
from django import http
from django.conf import settings
@ -12,6 +30,7 @@ import datetime
import json
import logging
import subprocess
import sys
import urlparse
from django.contrib import messages
@ -21,6 +40,8 @@ from django_openstack import forms
from django_openstack.dash.views import instances as dash_instances
from openstackx.api import exceptions as api_exceptions
LOG = logging.getLogger('django_openstack.syspanel.views.services')
class ToggleService(forms.SelfHandlingForm):
service = forms.CharField(required=False)
@ -39,6 +60,8 @@ class ToggleService(forms.SelfHandlingForm):
messages.info(request, "Service '%s' has been disabled"
% data['name'])
except api_exceptions.ApiException, e:
LOG.error('ApiException while toggling service %s' %
data['service'], exc_info=True)
messages.error(request, "Unable to update service '%s': %s"
% data['name'], e.message)
@ -56,6 +79,7 @@ def index(request):
try:
services = api.service_list(request)
except api_exceptions.ApiException, e:
LOG.error('ApiException fetching service list', exc_info=True)
messages.error(request, 'Unable to get service info: %s' % e.message)
other_services = []
@ -63,7 +87,11 @@ def index(request):
for k, v in request.session['serviceCatalog'].iteritems():
v = v[0]
try:
subprocess.check_call(['curl', '-m', '1', v['internalURL']])
# TODO(mgius): This silences curl, but there's probably
# a better solution than using curl to begin with
subprocess.check_call(['curl', '-m', '1', v['internalURL']],
stdout=open(sys.devnull, 'w'),
stderr=open(sys.devnull, 'w'))
up = True
except:
up = False

View File

@ -1,5 +1,23 @@
# 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.
from django import template
from django import http
from django.conf import settings
@ -20,6 +38,9 @@ from django_openstack.dash.views import instances as dash_instances
from openstackx.api import exceptions as api_exceptions
LOG = logging.getLogger('django_openstack.syspanel.views.tenants')
class AddUser(forms.SelfHandlingForm):
user = forms.CharField()
tenant = forms.CharField()
@ -61,6 +82,7 @@ class CreateTenant(forms.SelfHandlingForm):
def handle(self, request, data):
try:
LOG.info('Creating tenant with id "%s"' % data['id'])
api.tenant_create(request,
data['id'],
data['description'],
@ -69,6 +91,10 @@ class CreateTenant(forms.SelfHandlingForm):
'%s was successfully created.'
% data['id'])
except api_exceptions.ApiException, e:
LOG.error('ApiException while creating tenant\n'
'Id: "%s", Description: "%s", Enabled "%s"' %
(data['id'], data['description'], data['enabled']),
exc_info=True)
messages.error(request, 'Unable to create tenant: %s' %
(e.message))
return redirect('syspanel_tenants')
@ -81,6 +107,7 @@ class UpdateTenant(forms.SelfHandlingForm):
def handle(self, request, data):
try:
LOG.info('Updating tenant with id "%s"' % data['id'])
api.tenant_update(request,
data['id'],
data['description'],
@ -89,9 +116,14 @@ class UpdateTenant(forms.SelfHandlingForm):
'%s was successfully updated.'
% data['id'])
except api_exceptions.ApiException, e:
LOG.error('ApiException while updating tenant\n'
'Id: "%s", Description: "%s", Enabled "%s"' %
(data['id'], data['description'], data['enabled']),
exc_info=True)
messages.error(request, 'Unable to update tenant: %s' % e.message)
return redirect('syspanel_tenants')
class UpdateQuotas(forms.SelfHandlingForm):
tenant_id = forms.CharField(label="ID (name)", widget=forms.TextInput(attrs={'readonly':'readonly'}))
metadata_items = forms.CharField(label="Metadata Items")
@ -125,12 +157,14 @@ class UpdateQuotas(forms.SelfHandlingForm):
messages.error(request, 'Unable to update quotas: %s' % e.message)
return redirect('syspanel_tenants')
@login_required
def index(request):
tenants = []
try:
tenants = api.tenant_list(request)
except api_exceptions.ApiException, e:
LOG.error('ApiException while getting tenant list', exc_info=True)
messages.error(request, 'Unable to get tenant info: %s' % e.message)
tenants.sort(key=lambda x: x.id, reverse=True)
return render_to_response('syspanel_tenants.html',{
@ -163,6 +197,8 @@ def update(request, tenant_id):
'description': tenant.description,
'enabled': tenant.enabled})
except api_exceptions.ApiException, e:
LOG.error('Error fetching tenant with id "%s"' % tenant_id,
exc_info=True)
messages.error(request, 'Unable to update tenant: %s' % e.message)
return redirect('syspanel_tenants')
@ -204,6 +240,7 @@ def users(request, tenant_id):
'new_users': new_user_ids,
}, context_instance = template.RequestContext(request))
@login_required
def quotas(request, tenant_id):
for f in (UpdateQuotas,):

View File

@ -1,5 +1,23 @@
# 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.
from django import template
from django import http
from django.conf import settings
@ -20,6 +38,8 @@ from django_openstack.dash.views import instances as dash_instances
from openstackx.api import exceptions as api_exceptions
LOG = logging.getLogger('django_openstack.syspanel.views.users')
class UserForm(forms.Form):
def __init__(self, *args, **kwargs):
@ -38,6 +58,7 @@ class UserDeleteForm(forms.SelfHandlingForm):
def handle(self, request, data):
user_id = data['user']
LOG.info('Deleting user with id "%s"' % user_id)
api.user_delete(request, user_id)
messages.info(request, '%s was successfully deleted.'
% user_id)
@ -137,6 +158,7 @@ def create(request):
user = form.clean()
# TODO Make this a real request
try:
LOG.info('Creating user with id "%s"' % user['id'])
api.user_create(request,
user['id'],
user['email'],
@ -153,6 +175,10 @@ def create(request):
return redirect('syspanel_users')
except api_exceptions.ApiException, e:
LOG.error('ApiException while creating user\n'
'id: "%s", email: "%s", tenant_id: "%s"' %
(user['id'], user['email'], user['tenant_id']),
exc_info=True)
messages.error(request,
'Error creating user: %s'
% e.message)

View File

@ -1,9 +1,11 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# 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

View File

@ -1,3 +1,23 @@
# 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.
"""
Template tags for parsing date strings.
"""

View File

@ -1,9 +1,11 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# 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
@ -15,6 +17,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Template tags for truncating strings.
"""

View File

@ -0,0 +1 @@
from testsettings import *

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,2 @@
Intentionally not a python module so that test runner won't find
these broken tests

View File

@ -0,0 +1,39 @@
# 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.
"""
Tests for dependency packages
Honestly, this can probably go away once tests that depend on these
packages become more ingrained in the code.
"""
from django import test
from django.core import mail
from mailer import engine
from mailer import send_mail
class DjangoMailerPresenceTest(test.TestCase):
def test_mailsent(self):
send_mail('subject', 'message_body', 'from@test.com', ['to@test.com'])
engine.send_all()
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].subject, 'subject')

View File

@ -1,3 +1,23 @@
# 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.
import os
ROOT_PATH = os.path.dirname(os.path.abspath(__file__))
@ -16,7 +36,17 @@ INSTALLED_APPS = ['django.contrib.auth',
'django_openstack',
'django_openstack.tests',
'django_openstack.templatetags',
'mailer',
]
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django_openstack.middleware.keystone.AuthenticationMiddleware',
)
ROOT_URLCONF = 'django_openstack.tests.testurls'
TEMPLATE_DIRS = (
os.path.join(ROOT_PATH, 'tests', 'templates')
@ -29,6 +59,23 @@ NOVA_DEFAULT_ENDPOINT = None
NOVA_DEFAULT_REGION = 'test'
NOVA_ACCESS_KEY = 'test'
NOVA_SECRET_KEY = 'test'
OPENSTACK_ADMIN_TOKEN = 'test'
CREDENTIAL_AUTHORIZATION_DAYS = 2
CREDENTIAL_DOWNLOAD_URL = TESTSERVER + '/credentials/'
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
NOSE_ARGS = ['--nocapture',
'--cover-package=django_openstack',
'--cover-inclusive',
]
# django-mailer uses a different config attribute
# even though it just wraps django.core.mail
MAILER_EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'
EMAIL_BACKEND = MAILER_EMAIL_BACKEND
SWIFT_ACCOUNT = 'test'
SWIFT_USER = 'tester'
SWIFT_PASS = 'testing'
SWIFT_AUTHURL = 'http://swift/swiftapi/v1.0'

View File

@ -1,9 +1,11 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# 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
@ -22,13 +24,14 @@ URL patterns for testing django-openstack views.
from django.conf.urls.defaults import *
from django_openstack import urls as django_openstack_urls
urlpatterns = patterns('',
url(r'^projects/', include('django_openstack.nova.urls.project')),
url(r'^region/', include('django_openstack.nova.urls.region')),
#url(r'^admin/projects/', include('django_openstack.nova.urls.admin_project')),
#url(r'^admin/roles/', include('django_openstack.nova.urls.admin_roles')),
url(r'^credentials/download/(?P<auth_token>\w+)/$',
'django_openstack.nova.views.credentials.authorize_credentials',
name='nova_credentials_authorize'),
url(r'^dash/$', 'django_openstack.dash.views.instances.usage', name='dash_overview'),
url(r'^syspanel/$', 'django_openstack.syspanel.views.instances.usage', name='syspanel_overview')
)
# NOTE(termie): just append them since we want the routes at the root
urlpatterns += django_openstack_urls.urlpatterns

View File

@ -1,7 +0,0 @@
from credential_tests import *
from image_tests import *
from instance_tests import *
from keypair_tests import *
from region_tests import *
from volume_tests import *

View File

@ -0,0 +1,106 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 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.
"""
Base classes for view based unit tests.
"""
import mox
from django import http
from django import shortcuts
from django import template as django_template
from django import test
from django.conf import settings
from django_openstack.middleware import keystone
class Object(object):
"""Inner Object for api resource wrappers"""
pass
def fake_render_to_response(template_name, context, context_instance=None,
mimetype='text/html'):
"""Replacement for render_to_response so that views can be tested
without having to stub out templates that belong in the frontend
implementation.
Should be able to be tested using the django unit test assertions like a
normal render_to_response return value can be.
"""
class Template(object):
def __init__(self, name):
self.name = name
if context_instance is None:
context_instance = django_template.Context(context)
else:
context_instance.update(context)
resp = http.HttpResponse()
template = Template(template_name)
resp.write('<html><body><p>'
'This is a fake httpresponse for testing purposes only'
'</p></body></html>')
# Allows django.test.client to populate fields on the response object
test.signals.template_rendered.send(template, template=template,
context=context_instance)
return resp
class BaseViewTests(test.TestCase):
TEST_PROJECT = 'test'
TEST_REGION = 'test'
TEST_STAFF_USER = 'staffUser'
TEST_TENANT = 'aTenant'
TEST_TOKEN = 'aToken'
TEST_USER = 'test'
@classmethod
def setUpClass(cls):
cls._real_render_to_response = shortcuts.render_to_response
shortcuts.render_to_response = fake_render_to_response
cls._real_get_user_from_request = keystone.get_user_from_request
@classmethod
def tearDownClass(cls):
shortcuts.render_to_response = cls._real_render_to_response
keystone.get_user_from_request = cls._real_get_user_from_request
def setUp(self):
self.mox = mox.Mox()
self.setActiveUser(self.TEST_TOKEN, self.TEST_USER, self.TEST_TENANT,
True)
def tearDown(self):
self.mox.UnsetStubs()
def assertRedirectsNoFollow(self, response, expected_url):
self.assertEqual(response._headers['location'],
('Location', settings.TESTSERVER + expected_url))
self.assertEqual(response.status_code, 302)
def setActiveUser(self, token, username, tenant, is_admin):
keystone.get_user_from_request = \
lambda x: keystone.User(token, username, tenant, is_admin)

View File

@ -0,0 +1,94 @@
from cloudfiles.errors import ContainerNotEmpty
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 ContainerViewTests(base.BaseViewTests):
def setUp(self):
super(ContainerViewTests, self).setUp()
container_inner = base.Object()
container_inner.name = 'containerName'
self.container = api.Container(container_inner)
def test_index(self):
self.mox.StubOutWithMock(api, 'swift_get_containers')
api.swift_get_containers().AndReturn([self.container])
self.mox.ReplayAll()
res = self.client.get(reverse('dash_containers', args=['tenant']))
self.assertTemplateUsed(res, 'dash_containers.html')
self.assertIn('containers', res.context)
containers = res.context['containers']
self.assertEqual(len(containers), 1)
self.assertEqual(containers[0].name, 'containerName')
self.mox.VerifyAll()
def test_delete_container(self):
formData = {'container_name': 'containerName',
'method': 'DeleteContainer'}
self.mox.StubOutWithMock(api, 'swift_delete_container')
api.swift_delete_container('containerName')
self.mox.ReplayAll()
res = self.client.post(reverse('dash_containers', args=['tenant']),
formData)
self.assertRedirectsNoFollow(res, reverse('dash_containers',
args=['tenant']))
self.mox.VerifyAll()
def test_delete_container_nonempty(self):
formData = {'container_name': 'containerName',
'method': 'DeleteContainer'}
exception = ContainerNotEmpty('containerNotEmpty')
self.mox.StubOutWithMock(api, 'swift_delete_container')
api.swift_delete_container('containerName').AndRaise(exception)
self.mox.StubOutWithMock(messages, 'error')
messages.error(IgnoreArg(), IsA(unicode))
self.mox.ReplayAll()
res = self.client.post(reverse('dash_containers', args=['tenant']),
formData)
self.assertRedirectsNoFollow(res, reverse('dash_containers',
args=['tenant']))
self.mox.VerifyAll()
def test_create_container_get(self):
res = self.client.get(reverse('dash_containers_create',
args=['tenant']))
self.assertTemplateUsed(res, 'dash_containers_create.html')
def test_create_container_post(self):
formData = {'name': 'containerName',
'method': 'CreateContainer'}
self.mox.StubOutWithMock(api, 'swift_create_container')
api.swift_create_container('CreateContainer')
self.mox.StubOutWithMock(messages, 'success')
messages.success(IgnoreArg(), IsA(str))
res = self.client.post(reverse('dash_containers_create',
args=['tenant']),
formData)
self.assertRedirectsNoFollow(res, reverse('dash_containers_create',
args=['tenant']))

View File

@ -0,0 +1,330 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
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 glance.common import exception as glance_exception
from openstackx.api import exceptions as api_exceptions
from mox import IgnoreArg, IsA
class ImageViewTests(base.BaseViewTests):
def setUp(self):
super(ImageViewTests, self).setUp()
image_dict = {'name': 'visibleImage',
'container_format': 'novaImage'}
self.visibleImage = api.Image(image_dict)
image_dict = {'name': 'invisibleImage',
'container_format': 'aki'}
self.invisibleImage = api.Image(image_dict)
self.images = (self.visibleImage, self.invisibleImage)
flavor_inner = base.Object()
flavor_inner.id = 1
flavor_inner.name = 'm1.massive'
flavor_inner.vcpus = 1000
flavor_inner.disk = 1024
flavor_inner.ram = 10000
self.flavors = (api.Flavor(flavor_inner),)
keypair_inner = base.Object()
keypair_inner.key_name = 'keyName'
self.keypairs = (api.KeyPair(keypair_inner),)
def test_index(self):
self.mox.StubOutWithMock(api, 'token_get_tenant')
api.token_get_tenant(IsA(http.HttpRequest), self.TEST_TENANT)
self.mox.StubOutWithMock(api, 'image_list_detailed')
api.image_list_detailed(IsA(http.HttpRequest)).AndReturn(self.images)
self.mox.ReplayAll()
res = self.client.get(reverse('dash_images', args=[self.TEST_TENANT]))
self.assertTemplateUsed(res, 'dash_images.html')
self.assertIn('images', res.context)
images = res.context['images']
self.assertEqual(len(images), 1)
self.assertEqual(images[0].name, 'visibleImage')
self.mox.VerifyAll()
def test_index_no_images(self):
self.mox.StubOutWithMock(api, 'token_get_tenant')
api.token_get_tenant(IsA(http.HttpRequest), self.TEST_TENANT)
self.mox.StubOutWithMock(api, 'image_list_detailed')
api.image_list_detailed(IsA(http.HttpRequest)).AndReturn([])
self.mox.StubOutWithMock(messages, 'info')
messages.info(IsA(http.HttpRequest), IsA(str))
self.mox.ReplayAll()
res = self.client.get(reverse('dash_images', args=[self.TEST_TENANT]))
self.assertTemplateUsed(res, 'dash_images.html')
self.mox.VerifyAll()
def test_index_client_conn_error(self):
self.mox.StubOutWithMock(api, 'token_get_tenant')
api.token_get_tenant(IsA(http.HttpRequest), self.TEST_TENANT)
self.mox.StubOutWithMock(api, 'image_list_detailed')
exception = glance_exception.ClientConnectionError('clientConnError')
api.image_list_detailed(IsA(http.HttpRequest)).AndRaise(exception)
self.mox.StubOutWithMock(messages, 'error')
messages.error(IsA(http.HttpRequest), IsA(str))
self.mox.ReplayAll()
res = self.client.get(reverse('dash_images', args=[self.TEST_TENANT]))
self.assertTemplateUsed(res, 'dash_images.html')
self.mox.VerifyAll()
def test_index_glance_error(self):
self.mox.StubOutWithMock(api, 'token_get_tenant')
api.token_get_tenant(IsA(http.HttpRequest), self.TEST_TENANT)
self.mox.StubOutWithMock(api, 'image_list_detailed')
exception = glance_exception.Error('glanceError')
api.image_list_detailed(IsA(http.HttpRequest)).AndRaise(exception)
self.mox.StubOutWithMock(messages, 'error')
messages.error(IsA(http.HttpRequest), IsA(str))
self.mox.ReplayAll()
res = self.client.get(reverse('dash_images', args=[self.TEST_TENANT]))
self.assertTemplateUsed(res, 'dash_images.html')
self.mox.VerifyAll()
def test_launch_get(self):
IMAGE_ID = '1'
self.mox.StubOutWithMock(api, 'image_get')
api.image_get(IsA(http.HttpRequest),
IMAGE_ID).AndReturn(self.visibleImage)
self.mox.StubOutWithMock(api, 'token_get_tenant')
api.token_get_tenant(IsA(http.HttpRequest),
self.TEST_TENANT).AndReturn(self.TEST_TENANT)
self.mox.StubOutWithMock(api, 'flavor_list')
api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors)
self.mox.StubOutWithMock(api, 'keypair_list')
api.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs)
self.mox.ReplayAll()
res = self.client.get(reverse('dash_images_launch',
args=[self.TEST_TENANT, IMAGE_ID]))
self.assertTemplateUsed(res, 'dash_launch.html')
image = res.context['image']
self.assertEqual(image.name, self.visibleImage.name)
self.assertEqual(res.context['tenant'], self.TEST_TENANT)
form = res.context['form']
form_flavorfield = form.fields['flavor']
self.assertIn('m1.massive', form_flavorfield.choices[0][1])
form_keyfield = form.fields['key_name']
self.assertEqual(form_keyfield.choices[0][0],
self.keypairs[0].key_name)
self.mox.VerifyAll()
def test_launch_post(self):
FLAVOR_ID = self.flavors[0].id
IMAGE_ID = '1'
KEY_NAME = self.keypairs[0].key_name
SERVER_NAME = 'serverName'
USER_DATA = 'userData'
form_data = {'method': 'LaunchForm',
'flavor': FLAVOR_ID,
'image_id': IMAGE_ID,
'key_name': KEY_NAME,
'name': SERVER_NAME,
'user_data': USER_DATA,
}
self.mox.StubOutWithMock(api, 'image_get')
api.image_get(IsA(http.HttpRequest),
IMAGE_ID).AndReturn(self.visibleImage)
self.mox.StubOutWithMock(api, 'token_get_tenant')
api.token_get_tenant(IsA(http.HttpRequest),
self.TEST_TENANT).AndReturn(self.TEST_TENANT)
self.mox.StubOutWithMock(api, 'flavor_list')
api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors)
self.mox.StubOutWithMock(api, 'keypair_list')
api.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs)
# called again by the form
api.image_get(IsA(http.HttpRequest),
IMAGE_ID).AndReturn(self.visibleImage)
self.mox.StubOutWithMock(api, 'flavor_get')
api.flavor_get(IsA(http.HttpRequest),
IsA(unicode)).AndReturn(self.flavors[0])
self.mox.StubOutWithMock(api, 'server_create')
api.server_create(IsA(http.HttpRequest), SERVER_NAME,
self.visibleImage, self.flavors[0],
user_data=USER_DATA, key_name=KEY_NAME)
self.mox.StubOutWithMock(messages, 'success')
messages.success(IsA(http.HttpRequest), IsA(str))
self.mox.ReplayAll()
res = self.client.post(reverse('dash_images_launch',
args=[self.TEST_TENANT, IMAGE_ID]),
form_data)
self.assertRedirectsNoFollow(res, reverse('dash_images_launch',
args=[self.TEST_TENANT, IMAGE_ID]))
self.mox.VerifyAll()
def test_launch_flavorlist_error(self):
IMAGE_ID = '1'
self.mox.StubOutWithMock(api, 'image_get')
api.image_get(IsA(http.HttpRequest),
IMAGE_ID).AndReturn(self.visibleImage)
self.mox.StubOutWithMock(api, 'token_get_tenant')
api.token_get_tenant(IsA(http.HttpRequest),
self.TEST_TENANT).AndReturn(self.TEST_TENANT)
exception = api_exceptions.ApiException('apiException')
self.mox.StubOutWithMock(api, 'flavor_list')
api.flavor_list(IsA(http.HttpRequest)).AndRaise(exception)
self.mox.StubOutWithMock(api, 'keypair_list')
api.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs)
self.mox.ReplayAll()
res = self.client.get(reverse('dash_images_launch',
args=[self.TEST_TENANT, IMAGE_ID]))
self.assertTemplateUsed(res, 'dash_launch.html')
form = res.context['form']
form_flavorfield = form.fields['flavor']
self.assertIn('m1.tiny', form_flavorfield.choices[0][1])
self.mox.VerifyAll()
def test_launch_keypairlist_error(self):
IMAGE_ID = '1'
self.mox.StubOutWithMock(api, 'image_get')
api.image_get(IsA(http.HttpRequest),
IMAGE_ID).AndReturn(self.visibleImage)
self.mox.StubOutWithMock(api, 'token_get_tenant')
api.token_get_tenant(IsA(http.HttpRequest),
self.TEST_TENANT).AndReturn(self.TEST_TENANT)
self.mox.StubOutWithMock(api, 'flavor_list')
api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors)
exception = api_exceptions.ApiException('apiException')
self.mox.StubOutWithMock(api, 'keypair_list')
api.keypair_list(IsA(http.HttpRequest)).AndRaise(exception)
self.mox.ReplayAll()
res = self.client.get(reverse('dash_images_launch',
args=[self.TEST_TENANT, IMAGE_ID]))
self.assertTemplateUsed(res, 'dash_launch.html')
form = res.context['form']
form_keyfield = form.fields['key_name']
self.assertEqual(len(form_keyfield.choices), 0)
self.mox.VerifyAll()
def test_launch_form_apiexception(self):
FLAVOR_ID = self.flavors[0].id
IMAGE_ID = '1'
KEY_NAME = self.keypairs[0].key_name
SERVER_NAME = 'serverName'
USER_DATA = 'userData'
form_data = {'method': 'LaunchForm',
'flavor': FLAVOR_ID,
'image_id': IMAGE_ID,
'key_name': KEY_NAME,
'name': SERVER_NAME,
'user_data': USER_DATA,
}
self.mox.StubOutWithMock(api, 'image_get')
api.image_get(IsA(http.HttpRequest),
IMAGE_ID).AndReturn(self.visibleImage)
self.mox.StubOutWithMock(api, 'token_get_tenant')
api.token_get_tenant(IsA(http.HttpRequest),
self.TEST_TENANT).AndReturn(self.TEST_TENANT)
self.mox.StubOutWithMock(api, 'flavor_list')
api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors)
self.mox.StubOutWithMock(api, 'keypair_list')
api.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs)
# called again by the form
api.image_get(IsA(http.HttpRequest),
IMAGE_ID).AndReturn(self.visibleImage)
self.mox.StubOutWithMock(api, 'flavor_get')
api.flavor_get(IsA(http.HttpRequest),
IsA(unicode)).AndReturn(self.flavors[0])
self.mox.StubOutWithMock(api, 'server_create')
exception = api_exceptions.ApiException('apiException')
api.server_create(IsA(http.HttpRequest), SERVER_NAME,
self.visibleImage, self.flavors[0],
user_data=USER_DATA,
key_name=KEY_NAME).AndRaise(exception)
self.mox.StubOutWithMock(messages, 'error')
messages.error(IsA(http.HttpRequest), IsA(str))
self.mox.ReplayAll()
res = self.client.post(reverse('dash_images_launch',
args=[self.TEST_TENANT, IMAGE_ID]),
form_data)
self.assertTemplateUsed(res, 'dash_launch.html')
self.mox.VerifyAll()

View File

@ -0,0 +1,317 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
import datetime
from django import http
from django.contrib import messages
from django.core.urlresolvers import reverse
from django_openstack import api
from django_openstack import utils
from django_openstack.tests.view_tests import base
from openstackx.api import exceptions as api_exceptions
from mox import IsA
class InstanceViewTests(base.BaseViewTests):
def setUp(self):
super(InstanceViewTests, self).setUp()
server_inner = base.Object()
server_inner.id = 1
server_inner.name = 'serverName'
self.servers = (api.Server(server_inner, None),)
def test_index(self):
self.mox.StubOutWithMock(api, 'server_list')
api.server_list(IsA(http.HttpRequest)).AndReturn(self.servers)
self.mox.ReplayAll()
res = self.client.get(reverse('dash_instances',
args=[self.TEST_TENANT]))
self.assertTemplateUsed(res, 'dash_instances.html')
self.assertItemsEqual(res.context['instances'], self.servers)
self.mox.VerifyAll()
def test_index_server_list_exception(self):
self.mox.StubOutWithMock(api, 'server_list')
exception = api_exceptions.ApiException('apiException')
api.server_list(IsA(http.HttpRequest)).AndRaise(exception)
self.mox.ReplayAll()
res = self.client.get(reverse('dash_instances',
args=[self.TEST_TENANT]))
self.assertTemplateUsed(res, 'dash_instances.html')
self.assertEqual(len(res.context['instances']), 0)
self.mox.VerifyAll()
def test_terminate_instance(self):
formData = {'method': 'TerminateInstance',
'instance': self.servers[0].id,
}
self.mox.StubOutWithMock(api, 'server_get')
api.server_get(IsA(http.HttpRequest),
str(self.servers[0].id)).AndReturn(self.servers[0])
self.mox.StubOutWithMock(api, 'server_delete')
api.server_delete(IsA(http.HttpRequest),
self.servers[0])
self.mox.ReplayAll()
res = self.client.post(reverse('dash_instances',
args=[self.TEST_TENANT]),
formData)
self.assertRedirectsNoFollow(res, reverse('dash_instances',
args=[self.TEST_TENANT]))
self.mox.VerifyAll()
def test_terminate_instance_exception(self):
formData = {'method': 'TerminateInstance',
'instance': self.servers[0].id,
}
self.mox.StubOutWithMock(api, 'server_get')
api.server_get(IsA(http.HttpRequest),
str(self.servers[0].id)).AndReturn(self.servers[0])
exception = api_exceptions.ApiException('ApiException',
message='apiException')
self.mox.StubOutWithMock(api, 'server_delete')
api.server_delete(IsA(http.HttpRequest),
self.servers[0]).AndRaise(exception)
self.mox.StubOutWithMock(messages, 'error')
messages.error(IsA(http.HttpRequest), IsA(unicode))
self.mox.ReplayAll()
res = self.client.post(reverse('dash_instances',
args=[self.TEST_TENANT]),
formData)
self.assertRedirectsNoFollow(res, reverse('dash_instances',
args=[self.TEST_TENANT]))
self.mox.VerifyAll()
def test_reboot_instance(self):
formData = {'method': 'RebootInstance',
'instance': self.servers[0].id,
}
self.mox.StubOutWithMock(api, 'server_reboot')
api.server_reboot(IsA(http.HttpRequest), unicode(self.servers[0].id))
self.mox.ReplayAll()
res = self.client.post(reverse('dash_instances',
args=[self.TEST_TENANT]),
formData)
self.assertRedirectsNoFollow(res, reverse('dash_instances',
args=[self.TEST_TENANT]))
self.mox.VerifyAll()
def test_reboot_instance_exception(self):
formData = {'method': 'RebootInstance',
'instance': self.servers[0].id,
}
self.mox.StubOutWithMock(api, 'server_reboot')
exception = api_exceptions.ApiException('ApiException',
message='apiException')
api.server_reboot(IsA(http.HttpRequest),
unicode(self.servers[0].id)).AndRaise(exception)
self.mox.StubOutWithMock(messages, 'error')
messages.error(IsA(http.HttpRequest), IsA(str))
self.mox.ReplayAll()
res = self.client.post(reverse('dash_instances',
args=[self.TEST_TENANT]),
formData)
self.assertRedirectsNoFollow(res, reverse('dash_instances',
args=[self.TEST_TENANT]))
self.mox.VerifyAll()
def override_times(self, time=datetime.datetime.now):
now = datetime.datetime.utcnow()
utils.time.override_time = \
datetime.time(now.hour, now.minute, now.second)
utils.today.override_time = datetime.date(now.year, now.month, now.day)
utils.utcnow.override_time = now
return now
def reset_times(self):
utils.time.override_time = None
utils.today.override_time = None
utils.utcnow.override_time = None
def test_instance_usage(self):
TEST_RETURN = 'testReturn'
now = self.override_times()
self.mox.StubOutWithMock(api, 'usage_get')
api.usage_get(IsA(http.HttpRequest), self.TEST_TENANT,
datetime.datetime(now.year, now.month, 1,
now.hour, now.minute, now.second),
now).AndReturn(TEST_RETURN)
self.mox.ReplayAll()
res = self.client.get(reverse('dash_usage', args=[self.TEST_TENANT]))
self.assertTemplateUsed(res, 'dash_usage.html')
self.assertEqual(res.context['usage'], TEST_RETURN)
self.mox.VerifyAll()
self.reset_times()
def test_instance_usage_exception(self):
now = self.override_times()
exception = api_exceptions.ApiException('apiException',
message='apiException')
self.mox.StubOutWithMock(api, 'usage_get')
api.usage_get(IsA(http.HttpRequest), self.TEST_TENANT,
datetime.datetime(now.year, now.month, 1,
now.hour, now.minute, now.second),
now).AndRaise(exception)
self.mox.StubOutWithMock(messages, 'error')
messages.error(IsA(http.HttpRequest), IsA(str))
self.mox.ReplayAll()
res = self.client.get(reverse('dash_usage', args=[self.TEST_TENANT]))
self.assertTemplateUsed(res, 'dash_usage.html')
self.assertEqual(res.context['usage'], {})
self.mox.VerifyAll()
self.reset_times()
def test_instance_usage_default_tenant(self):
TEST_RETURN = 'testReturn'
now = self.override_times()
self.mox.StubOutWithMock(api, 'usage_get')
api.usage_get(IsA(http.HttpRequest), self.TEST_TENANT,
datetime.datetime(now.year, now.month, 1,
now.hour, now.minute, now.second),
now).AndReturn(TEST_RETURN)
self.mox.ReplayAll()
res = self.client.get(reverse('dash_overview'))
self.assertTemplateUsed(res, 'dash_usage.html')
self.assertEqual(res.context['usage'], TEST_RETURN)
self.mox.VerifyAll()
self.reset_times()
def test_instance_console(self):
CONSOLE_OUTPUT = 'output'
INSTANCE_ID = self.servers[0].id
console_mock = self.mox.CreateMock(api.Console)
console_mock.output = CONSOLE_OUTPUT
self.mox.StubOutWithMock(api, 'console_create')
api.console_create(IsA(http.HttpRequest),
unicode(INSTANCE_ID)).AndReturn(console_mock)
self.mox.ReplayAll()
res = self.client.get(reverse('dash_instances_console',
args=[self.TEST_TENANT, INSTANCE_ID]))
self.assertIsInstance(res, http.HttpResponse)
self.assertContains(res, CONSOLE_OUTPUT)
self.mox.VerifyAll()
def test_instance_console_exception(self):
INSTANCE_ID = self.servers[0].id
exception = api_exceptions.ApiException('apiException',
message='apiException')
self.mox.StubOutWithMock(api, 'console_create')
api.console_create(IsA(http.HttpRequest),
unicode(INSTANCE_ID)).AndRaise(exception)
self.mox.StubOutWithMock(messages, 'error')
messages.error(IsA(http.HttpRequest), IsA(unicode))
self.mox.ReplayAll()
res = self.client.get(reverse('dash_instances_console',
args=[self.TEST_TENANT, INSTANCE_ID]))
self.assertRedirectsNoFollow(res, reverse('dash_instances',
args=[self.TEST_TENANT]))
self.mox.VerifyAll()
def test_instance_vnc(self):
INSTANCE_ID = self.servers[0].id
CONSOLE_OUTPUT = '/vncserver'
console_mock = self.mox.CreateMock(api.Console)
console_mock.output = CONSOLE_OUTPUT
self.mox.StubOutWithMock(api, 'console_create')
api.console_create(IsA(http.HttpRequest),
unicode(INSTANCE_ID),
'vnc').AndReturn(console_mock)
self.mox.ReplayAll()
res = self.client.get(reverse('dash_instances_vnc',
args=[self.TEST_TENANT, INSTANCE_ID]))
self.assertRedirectsNoFollow(res, CONSOLE_OUTPUT)
self.mox.VerifyAll()
def test_instance_vnc_exception(self):
INSTANCE_ID = self.servers[0].id
exception = api_exceptions.ApiException('apiException',
message='apiException')
self.mox.StubOutWithMock(api, 'console_create')
api.console_create(IsA(http.HttpRequest),
unicode(INSTANCE_ID),
'vnc').AndRaise(exception)
self.mox.ReplayAll()
res = self.client.get(reverse('dash_instances_vnc',
args=[self.TEST_TENANT, INSTANCE_ID]))
self.assertRedirectsNoFollow(res, reverse('dash_instances',
args=[self.TEST_TENANT]))
self.mox.VerifyAll()

View File

@ -0,0 +1,145 @@
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 IsA
import openstackx.api.exceptions as api_exceptions
class KeyPairViewTests(base.BaseViewTests):
def setUp(self):
super(KeyPairViewTests, self).setUp()
keypair_inner = base.Object()
keypair_inner.key_name = 'keyName'
self.keypairs = (api.KeyPair(keypair_inner),)
def test_index(self):
self.mox.StubOutWithMock(api, 'keypair_list')
api.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs)
self.mox.ReplayAll()
res = self.client.get(reverse('dash_keypairs', args=[self.TEST_TENANT]))
self.assertTemplateUsed(res, 'dash_keypairs.html')
self.assertItemsEqual(res.context['keypairs'], self.keypairs)
self.mox.VerifyAll()
def test_index_exception(self):
exception = api_exceptions.ApiException('apiException',
message='apiException')
self.mox.StubOutWithMock(api, 'keypair_list')
api.keypair_list(IsA(http.HttpRequest)).AndRaise(exception)
self.mox.StubOutWithMock(messages, 'error')
messages.error(IsA(http.HttpRequest), IsA(str))
self.mox.ReplayAll()
res = self.client.get(reverse('dash_keypairs', args=[self.TEST_TENANT]))
self.assertTemplateUsed(res, 'dash_keypairs.html')
self.assertEqual(len(res.context['keypairs']), 0)
self.mox.VerifyAll()
def test_delete_keypair(self):
KEYPAIR_ID = self.keypairs[0].key_name
formData = {'method': 'DeleteKeypair',
'keypair_id': KEYPAIR_ID,
}
self.mox.StubOutWithMock(api, 'keypair_delete')
api.keypair_delete(IsA(http.HttpRequest), unicode(KEYPAIR_ID))
self.mox.ReplayAll()
res = self.client.post(reverse('dash_keypairs',
args=[self.TEST_TENANT]),
formData)
self.assertRedirectsNoFollow(res, reverse('dash_keypairs',
args=[self.TEST_TENANT]))
self.mox.VerifyAll()
def test_delete_keypair_exception(self):
KEYPAIR_ID = self.keypairs[0].key_name
formData = {'method': 'DeleteKeypair',
'keypair_id': KEYPAIR_ID,
}
exception = api_exceptions.ApiException('apiException',
message='apiException')
self.mox.StubOutWithMock(api, 'keypair_delete')
api.keypair_delete(IsA(http.HttpRequest),
unicode(KEYPAIR_ID)).AndRaise(exception)
self.mox.ReplayAll()
res = self.client.post(reverse('dash_keypairs',
args=[self.TEST_TENANT]),
formData)
self.assertRedirectsNoFollow(res, reverse('dash_keypairs',
args=[self.TEST_TENANT]))
self.mox.VerifyAll()
def test_create_keypair_get(self):
res = self.client.get(reverse('dash_keypairs_create',
args=[self.TEST_TENANT]))
self.assertTemplateUsed(res, 'dash_keypairs_create.html')
def test_create_keypair_post(self):
KEYPAIR_NAME = 'newKeypair'
PRIVATE_KEY = 'privateKey'
newKeyPair = self.mox.CreateMock(api.KeyPair)
newKeyPair.key_name = KEYPAIR_NAME
newKeyPair.private_key = PRIVATE_KEY
formData = {'method': 'CreateKeypair',
'name': KEYPAIR_NAME,
}
self.mox.StubOutWithMock(api, 'keypair_create')
api.keypair_create(IsA(http.HttpRequest),
KEYPAIR_NAME).AndReturn(newKeyPair)
self.mox.ReplayAll()
res = self.client.post(reverse('dash_keypairs_create',
args=[self.TEST_TENANT]),
formData)
self.assertTrue(res.has_header('Content-Disposition'))
self.mox.VerifyAll()
def test_create_keypair_exception(self):
KEYPAIR_NAME = 'newKeypair'
formData = {'method': 'CreateKeypair',
'name': KEYPAIR_NAME,
}
exception = api_exceptions.ApiException('apiException',
message='apiException')
self.mox.StubOutWithMock(api, 'keypair_create')
api.keypair_create(IsA(http.HttpRequest),
KEYPAIR_NAME).AndRaise(exception)
self.mox.ReplayAll()
res = self.client.post(reverse('dash_keypairs_create',
args=[self.TEST_TENANT]),
formData)
self.assertRedirectsNoFollow(res, reverse('dash_keypairs_create',
args=[self.TEST_TENANT]))
self.mox.VerifyAll()

View File

@ -0,0 +1,190 @@
import tempfile
from django.core.urlresolvers import reverse
from django_openstack import api
from django_openstack.tests.view_tests import base
class ObjectViewTests(base.BaseViewTests):
CONTAINER_NAME = 'containerName'
def setUp(self):
super(ObjectViewTests, self).setUp()
swift_object = self.mox.CreateMock(api.SwiftObject)
self.swift_objects = [swift_object]
def test_index(self):
self.mox.StubOutWithMock(api, 'swift_get_objects')
api.swift_get_objects(
self.CONTAINER_NAME).AndReturn(self.swift_objects)
self.mox.ReplayAll()
res = self.client.get(reverse('dash_objects',
args=[self.TEST_TENANT,
self.CONTAINER_NAME]))
self.assertTemplateUsed(res, 'dash_objects.html')
self.assertItemsEqual(res.context['objects'], self.swift_objects)
self.mox.VerifyAll()
def test_upload_index(self):
res = self.client.get(reverse('dash_objects_upload',
args=[self.TEST_TENANT,
self.CONTAINER_NAME]))
self.assertTemplateUsed(res, 'dash_objects_upload.html')
def test_upload(self):
OBJECT_DATA = 'objectData'
OBJECT_FILE = tempfile.TemporaryFile()
OBJECT_FILE.write(OBJECT_DATA)
OBJECT_FILE.flush()
OBJECT_FILE.seek(0)
OBJECT_NAME = 'objectName'
formData = {'method': 'UploadObject',
'container_name': self.CONTAINER_NAME,
'name': OBJECT_NAME,
'object_file': OBJECT_FILE}
self.mox.StubOutWithMock(api, 'swift_upload_object')
api.swift_upload_object(unicode(self.CONTAINER_NAME),
unicode(OBJECT_NAME),
OBJECT_DATA)
self.mox.ReplayAll()
res = self.client.post(reverse('dash_objects_upload',
args=[self.TEST_TENANT,
self.CONTAINER_NAME]),
formData)
self.assertRedirectsNoFollow(res, reverse('dash_objects_upload',
args=[self.TEST_TENANT,
self.CONTAINER_NAME]))
self.mox.VerifyAll()
def test_delete(self):
OBJECT_NAME = 'objectName'
formData = {'method': 'DeleteObject',
'container_name': self.CONTAINER_NAME,
'object_name': OBJECT_NAME}
self.mox.StubOutWithMock(api, 'swift_delete_object')
api.swift_delete_object(self.CONTAINER_NAME, OBJECT_NAME)
self.mox.ReplayAll()
res = self.client.post(reverse('dash_objects',
args=[self.TEST_TENANT,
self.CONTAINER_NAME]),
formData)
self.assertRedirectsNoFollow(res, reverse('dash_objects',
args=[self.TEST_TENANT,
self.CONTAINER_NAME]))
self.mox.VerifyAll()
def test_download(self):
OBJECT_DATA = 'objectData'
OBJECT_NAME = 'objectName'
self.mox.StubOutWithMock(api, 'swift_get_object_data')
api.swift_get_object_data(unicode(self.CONTAINER_NAME),
unicode(OBJECT_NAME)).AndReturn(OBJECT_DATA)
self.mox.ReplayAll()
res = self.client.get(reverse('dash_objects_download',
args=[self.TEST_TENANT,
self.CONTAINER_NAME,
OBJECT_NAME]))
self.assertEqual(res.content, OBJECT_DATA)
self.assertTrue(res.has_header('Content-Disposition'))
self.mox.VerifyAll()
def test_copy_index(self):
OBJECT_NAME = 'objectName'
container = self.mox.CreateMock(api.Container)
container.name = self.CONTAINER_NAME
self.mox.StubOutWithMock(api, 'swift_get_containers')
api.swift_get_containers().AndReturn([container])
self.mox.ReplayAll()
res = self.client.get(reverse('dash_object_copy',
args=[self.TEST_TENANT,
self.CONTAINER_NAME,
OBJECT_NAME]))
self.assertTemplateUsed(res, 'dash_object_copy.html')
self.mox.VerifyAll()
def test_copy(self):
NEW_CONTAINER_NAME = self.CONTAINER_NAME
NEW_OBJECT_NAME = 'newObjectName'
ORIG_CONTAINER_NAME = 'origContainerName'
ORIG_OBJECT_NAME = 'origObjectName'
formData = {'method': 'CopyObject',
'new_container_name': NEW_CONTAINER_NAME,
'new_object_name': NEW_OBJECT_NAME,
'orig_container_name': ORIG_CONTAINER_NAME,
'orig_object_name': ORIG_OBJECT_NAME}
container = self.mox.CreateMock(api.Container)
container.name = self.CONTAINER_NAME
self.mox.StubOutWithMock(api, 'swift_get_containers')
api.swift_get_containers().AndReturn([container])
self.mox.StubOutWithMock(api, 'swift_copy_object')
api.swift_copy_object(ORIG_CONTAINER_NAME, ORIG_OBJECT_NAME,
NEW_CONTAINER_NAME, NEW_OBJECT_NAME)
self.mox.ReplayAll()
res = self.client.post(reverse('dash_object_copy',
args=[self.TEST_TENANT,
ORIG_CONTAINER_NAME,
ORIG_OBJECT_NAME]),
formData)
self.assertRedirectsNoFollow(res, reverse('dash_object_copy',
args=[self.TEST_TENANT,
ORIG_CONTAINER_NAME,
ORIG_OBJECT_NAME]))
self.mox.VerifyAll()
def test_filter(self):
PREFIX = 'prefix'
formData = {'method': 'FilterObjects',
'container_name': self.CONTAINER_NAME,
'object_prefix': PREFIX,
}
self.mox.StubOutWithMock(api, 'swift_get_objects')
api.swift_get_objects(unicode(self.CONTAINER_NAME),
prefix=unicode(PREFIX)
).AndReturn(self.swift_objects)
self.mox.ReplayAll()
res = self.client.post(reverse('dash_objects',
args=[self.TEST_TENANT,
self.CONTAINER_NAME]),
formData)
self.assertTemplateUsed(res, 'dash_objects.html')
self.mox.VerifyAll()

View File

@ -1,5 +1,23 @@
# 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.
from django.conf.urls.defaults import *
from django.conf import settings

View File

@ -1,5 +1,44 @@
# 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.
import datetime
def time():
'''Overrideable version of datetime.datetime.today'''
if time.override_time:
return time.override_time
return datetime.time()
time.override_time = None
def today():
'''Overridable version of datetime.datetime.today'''
if today.override_time:
return today.override_time
return datetime.datetime.today()
today.override_time = None
def utcnow():
'''Overridable version of datetime.datetime.utcnow'''
if utcnow.override_time:

View File

@ -1,3 +1,23 @@
# 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.
import os
from setuptools import setup, find_packages, findall
@ -17,7 +37,7 @@ setup(
package_data = {'django_openstack':
[s[len('django_openstack/'):] for s in
findall('django_openstack/templates')]},
install_requires = ['setuptools', 'mox>=0.5.0'],
install_requires = ['setuptools', 'mox>=0.5.3', 'django_nose'],
classifiers = [
'Development Status :: 4 - Beta',
'Framework :: Django',

View File

@ -33,5 +33,21 @@ If all is well you should now able to run the server locally:
$ tools/with_venv.sh dashboard/manage.py runserver
Adding openstackx Extensions to Nova
------------------------------------
If you are seeing large numbers of 404 exceptions on operations such as listing
servers, you are probably not running the openstackx extensions that the
dashboard depends on. You will need to download the openstackx code from
> https://github.com/cloudbuilders/openstackx
and add the following option to your nova instantiation:
> --osapi_extensions_path=/path/to/openstackx/extensions
The rackspace cloudbuilders nova.sh script automates this process and creates a
full nova installation compatible with the dashboard. You can acquire this
script from the repository at
https://github.com/cloudbuilders/deploy.sh

View File

@ -1,4 +1,24 @@
#!/usr/bin/env python
# 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.
from django.core.management import execute_manager
try:
import settings # Assumed to be in the same directory.

View File

@ -1,11 +1,29 @@
# 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.
import logging
import traceback
LOG = logging.getLogger('openstack_dashboard')
class DashboardLogUnhandledExceptionsMiddleware(object):
def process_exception(self, request, exception):
tb_text = traceback.format_exc()
LOG.critical('Unhandled Exception in dashboard. Exception "%s"'
'\n%s' % (str(exception), tb_text))
LOG.critical('Unhandled Exception in of type "%s" in dashboard.'
% type(exception), exc_info=True)

View File

@ -1,4 +1,23 @@
import boto
# 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.
import logging
import os
import sys
@ -45,6 +64,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
'django.core.context_processors.media',
'django.contrib.messages.context_processors.messages',
'django_openstack.context_processors.tenants',
'django_openstack.context_processors.swift',
)
TEMPLATE_LOADERS = (
@ -67,6 +87,7 @@ INSTALLED_APPS = (
'django.contrib.syndication',
'django_openstack',
'django_openstack.templatetags',
'mailer',
)
AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',)
@ -90,10 +111,7 @@ USE_I18N = True
ACCOUNT_ACTIVATION_DAYS = 7
# NOTE(devcamcar): Prevent boto from retrying and stalling the connection.
if not boto.config.has_section('Boto'):
boto.config.add_section('Boto')
boto.config.set('Boto', 'num_retries', '0')
TOTAL_CLOUD_RAM_GB = 10
try:
from local.local_settings import *

View File

@ -0,0 +1,10 @@
<form id="container_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 Container" class="large-rounded" />
</form>

View File

@ -0,0 +1,18 @@
<table id="containers" style="width: 100%">
<tr>
<th>Name</th>
<th style="width: 100px;">Actions</th>
</tr>
{% for container in containers %}
<tr class="{% cycle 'odd' 'even' %}">
<td>{{ container.name }}</td>
<td id="actions">
<ul>
<li><a href="{% url dash_objects request.user.tenant container.name %}">List Objects</a></li>
<li><a href="{% url dash_objects_upload request.user.tenant container.name %}">Upload Object</a></li>
<li>{% include "_delete_container.html" with form=delete_form %}</li>
</ul>
</td>
</tr>
{% endfor %}
</table>

View File

@ -0,0 +1,10 @@
<form id="object_copy" 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="Copy Object" class="large-rounded" />
</form>

View File

@ -6,4 +6,10 @@
<li><a {% if current_sidebar == "images" %} class="active" {% endif %} href="{% url dash_images request.user.tenant %}">Images</a></li>
<li><a {% if current_sidebar == "keypairs" %} class="active" {% endif %} href="{% url dash_keypairs request.user.tenant %}">Keypairs</a></li>
</ul>
{% if swift_configured %}
<h3>Manage Object Store</h3>
<ul class='sub_nav'>
<li><a {% if current_sidebar == "containers" %} class="active" {% endif %} href="{% url dash_containers request.user.tenant %}">Containers</a></li>
</ul>
{% endif %}
</div>

View File

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

View File

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

View File

@ -6,7 +6,7 @@
</tr>
{% for keypair in keypairs %}
<tr class="{% cycle 'odd' 'even' %}">
<td>{{ keypair.name }}</td>
<td>{{ keypair.key_name }}</td>
<td>{{ keypair.fingerprint }}</td>
<td id="actions">
<ul>

View File

@ -0,0 +1,10 @@
<form id="form_filter_{{ container_name }}" class="form-filter" method="post">
{% csrf_token %}
{% for hidden in form.hidden_fields %}
{{hidden}}
{% endfor %}
{% for field in form.visible_fields %}
{{field}}
{% endfor %}
<input id="filter_{{ container_name }}" class="filter" type="submit" value="Filter" />
</form>

View File

@ -0,0 +1,10 @@
<form id="object_form" enctype="multipart/form-data" 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="Upload Object" class="large-rounded" />
</form>

View File

@ -0,0 +1,18 @@
<table id="objects" style="width: 100%">
<tr>
<th>Name</th>
<th style="width: 100px;">Actions</th>
</tr>
{% for object in objects %}
<tr class="{% cycle 'odd' 'even' %}">
<td>{{ object.name }}</td>
<td id="actions">
<ul>
<li><a href="{% url dash_object_copy request.user.tenant container_name object.name %}">Copy</a></li>
<li>{% include "_delete_object.html" with form=delete_form %}</li>
<li><a href="{% url dash_objects_download request.user.tenant container_name object.name %}">Download</a>
</ul>
</td>
</tr>
{% endfor %}
</table>

View File

@ -17,7 +17,7 @@
<td>{{instance.attrs.user_id}}</td>
<td class="name">{{instance.attrs.host}}</td>
<td>{{instance.attrs.launched_at|parse_date}}</td>
<td>{{instance.attrs.image_name}}</td>
<td>{{instance.image_name}}</td>
<td>{{instance.addresses.public.0.addr|default:'N/A'}}</td>
<td>{{instance.addresses.private.0.addr|default:'-'}}</td>

View File

@ -0,0 +1,37 @@
{% extends 'dash_base.html' %}
{# list of user's containers #}
{% block sidebar %}
{% with current_sidebar="containers" %}
{{block.super}}
{% endwith %}
{% endblock %}
{% block main %}
<div id='page_header'>
<h2><span>Object Store:</span> Containers</h2>
<p class='desc'><span>&mdash;</span> Manage Containers.</p>
</div>
{% include "_messages.html" %}
<div class='main_content'>
{% if containers %}
<div class='table_title wide'>
<h3>Containers</h3>
<div class='search'>
<form action='' method='post'>
<fieldset>
<label for='table_search'>Search</label>
<input id='table_search' name='search' type='text' value='' />
</fieldset>
</form>
</div>
</div>
{% include '_container_list.html' %}
{% endif %}
<a href="{% url dash_containers_create request.user.tenant %}">Create New Container &gt;&gt;</a>
</div>
{% endblock %}

View File

@ -0,0 +1,33 @@
{% extends 'dash_base.html' %}
{% block sidebar %}
{% with current_sidebar="containers" %}
{{block.super}}
{% endwith %}
{% endblock %}
{% block main %}
<div id='page_header'>
<h2><span>Object Store:</span> Containers</h2>
<p class='desc'><span>&mdash;</span> Create a container.</p>
</div>
{% include "_messages.html" %}
<div class="main_content">
<div class="dash_block wide form">
<div class='title_block'>
<h3>Create Container</h3>
</div>
<div class="left">
{% include '_container_form.html' with form=create_form %}
<h3><a href="{% url dash_containers request.user.tenant %}">&lt;&lt; Return to containers list</a></h3>
</div>
<div class="right">
<h3>Description:</h3>
<p>A container is a storage compartment for your data and provides a way for you to organize your data. You can think of a container as a folder in Windows® or a directory in UNIX®. The primary difference between a container and these other file system concepts is that containers cannot be nested. You can, however, create an unlimited number of containers within your account. Data must be stored in a container so you must have at least one container defined in your account prior to uploading data.</p>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,36 @@
{% extends 'dash_base.html' %}
{% block sidebar %}
{% with current_sidebar="containers" %}
{{block.super}}
{% endwith %}
{% endblock %}
{% block main %}
<div id='page_header'>
<h2><span>Object Store:</span> Objects</h2>
<p class='desc'><span>&mdash;</span> Copy an object.</p>
</div>
{% include "_messages.html" %}
<div class="container-label">Container:</div>
<div class="container-name">{{ container_name }}</div>
<div style="clear: both;"></div>
<div class="main_content">
<div class="dash_block wide form">
<div class='title_block'>
<h3>Copy {{ object_name }}</h3>
</div>
<div class="left">
{% include '_copy_object.html' with form=copy_form greeting="HI" %}
<h3><a href="{% url dash_objects request.user.tenant container_name %}">&lt;&lt; Return to objects list</a></h3>
</div>
<div class="right">
<h3>Description:</h3>
<p>You may make a new copy of an existing object to store in this or another container.</p>
</div>
</div>
</div>
{% endblock %}

View File

@ -0,0 +1,33 @@
{% extends 'dash_base.html' %}
{% block sidebar %}
{% with current_sidebar="containers" %}
{{block.super}}
{% endwith %}
{% endblock %}
{% block main %}
<div id='page_header'>
<h2><span>Object Store:</span> Objects</h2>
<p class='desc'><span>&mdash;</span> Manage Objects.</p>
</div>
{% include "_messages.html" %}
<div class="container-label">Container:</div>
<div class="container-name">{{ container_name }}</div>
<div style="clear: both;"></div>
<div class='main_content'>
{% if objects %}
<div class='table_title wide'>
<h3>Objects</h3>
<div class='filter'>
{% include '_object_filter.html' with form=filter_form %}
</div>
</div>
{% include '_object_list.html' %}
{% endif %}
<a href="{% url dash_objects_upload request.user.tenant container_name %}">Upload New Object &gt;&gt;</a>
</div>
{% endblock %}

View File

@ -0,0 +1,36 @@
{% extends 'dash_base.html' %}
{% block sidebar %}
{% with current_sidebar="containers" %}
{{block.super}}
{% endwith %}
{% endblock %}
{% block main %}
<div id='page_header'>
<h2><span>Object Store:</span> Objects</h2>
<p class='desc'><span>&mdash;</span> Upload an object.</p>
</div>
{% include "_messages.html" %}
<div class="container-label">Container:</div>
<div class="container-name">{{ container_name }}</div>
<div style="clear: both;"></div>
<div class="main_content">
<div class="dash_block wide form">
<div class='title_block'>
<h3>Upload Object</h3>
</div>
<div class="left">
{% include '_object_form.html' with form=upload_form %}
<h3><a href="{% url dash_objects request.user.tenant container_name %}">&lt;&lt; Return to objects list</a></h3>
</div>
<div class="right">
<h3>Description:</h3>
<p>An object is the basic storage entity and any optional metadata that represents the files you store in the OpenStack Object Storage system. When you upload data to OpenStack Object Storage, the data is stored as-is (no compression or encryption) and consists of a location (container), the object's name, and any metadata consisting of key/value pairs.</p>
</div>
</div>
</div>
{% endblock %}

View File

@ -5,9 +5,7 @@
{% block sidebar %}
<div id="sidebar">
EMPTY_SIDEBAR
</div>
<div id="sidebar" class="empty-sidebar"></div>
{% endblock %}
{% block main %}

View File

@ -0,0 +1,19 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
''' Test for django mailer.
This test is pretty much worthless, and should be removed once real testing of
views that send emails is implemented
'''
from django import test
from django.core import mail
from mailer import engine
from mailer import send_mail
class DjangoMailerPresenceTest(test.TestCase):
def test_mailsent(self):
send_mail('subject', 'message_body', 'from@test.com', ['to@test.com'])
engine.send_all()
self.assertEqual(len(mail.outbox), 1)
self.assertEqual(mail.outbox[0].subject, 'subject')

View File

@ -1,9 +1,11 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# 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

View File

@ -1,9 +1,11 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# 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
@ -19,8 +21,6 @@
"""
Views for home page.
"""
import logging
from django import template
from django import shortcuts
from django.views.decorators import vary
@ -28,6 +28,7 @@ from django.views.decorators import vary
from django_openstack import api
from django_openstack.auth import views as auth_views
@vary.vary_on_cookie
def splash(request):
if request.user:

View File

@ -15,16 +15,33 @@ DATABASES = {
CACHE_BACKEND = 'dummy://'
# Send email to the console by default
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# Or send them to /dev/null
#EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend'
# django-mailer uses a different settings attribute
MAILER_EMAIL_BACKEND = EMAIL_BACKEND
# Configure these for your outgoing email host
# EMAIL_HOST = 'smtp.my-company.com'
# EMAIL_PORT = 25
# EMAIL_HOST_USER = 'djangomail'
# EMAIL_HOST_PASSWORD = 'top-secret!'
OPENSTACK_ADMIN_TOKEN = "999888777666"
OPENSTACK_KEYSTONE_URL = "http://localhost:8080/v2.0/"
OPENSTACK_KEYSTONE_URL = "http://localhost:5000/v2.0/"
OPENSTACK_KEYSTONE_DEFAULT_ROLE = "Member"
# NOTE(tres): Replace all of this nonsense once Swift+Keystone
# is stable.
# SWIFT_AUTHURL = 'http://localhost:8080/auth/v1.0'
# SWIFT_ACCOUNT = 'test'
# SWIFT_USER = 'tester'
# SWIFT_PASS = 'testing'
# If you have external monitoring links
EXTERNAL_MONITORING = [
['Nagios','http://foo.com'],
@ -58,10 +75,6 @@ EXTERNAL_MONITORING = [
# 'handlers': ['null'],
# 'propagate': False,
# },
# 'boto': {
# 'handlers': ['null'],
# 'propagate': False,
# },
# 'django_openstack': {
# 'handlers': ['null'],
# 'propagate': False,

View File

@ -737,7 +737,6 @@ div.image_detail, div.instance_detail {
border-color: #b7b7b7;
}
/* Footer */
#footer {

View File

@ -333,6 +333,10 @@ input[readonly="readonly"] {
float: left;
}
.empty-sidebar {
height: 400px;
}
#main {
float: left;
width: 735px;
@ -357,8 +361,9 @@ input[readonly="readonly"] {
}
.form-filter {
float: right;
}
.search {
@ -1195,7 +1200,7 @@ ol li.first a {
margin-bottom: 25px;
}
.status_box.info, .stat_box.info {
.status_box.info {
background-color: #e8f8ff;
border-color: #9ac7dc;
color: #7ab6c5;
@ -1715,3 +1720,17 @@ li.title h4{
bottom: 0;
margin-bottom: -24px;
}
.container-label {
font-weight: bold;
font-size: 1.22em;
margin-left: 10px;
margin-bottom: 10px;
float: left;
}
.container-name {
float: left;
margin-left: 5px;
}

View File

@ -1,10 +1,12 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# Copyright 2011 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Copyright 2010 OpenStack, LLC
# Copyright 2011 OpenStack, LLC
#
# 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

View File

@ -1,9 +1,20 @@
boto==1.9b
nose==1.0.0
Django==1.3
django-nose==0.1.2
django-mailer
django-registration==0.7
nova-adminclient
python-cloudfiles
python-dateutil
routes
webob
sqlalchemy
paste
PasteDeploy
sqlalchemy-migrate
eventlet
-e bzr+https://launchpad.net/glance#egg=glance
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