big ugly merge
This commit is contained in:
commit
dda978611d
8
.gitignore
vendored
8
.gitignore
vendored
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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")}
|
||||
|
@ -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'),
|
||||
)
|
||||
|
90
django-openstack/django_openstack/dash/views/containers.py
Normal file
90
django-openstack/django_openstack/dash/views/containers.py
Normal 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))
|
@ -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))
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
176
django-openstack/django_openstack/dash/views/objects.py
Normal file
176
django-openstack/django_openstack/dash/views/objects.py
Normal 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))
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
'''
|
||||
"""
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,):
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -0,0 +1 @@
|
||||
from testsettings import *
|
1541
django-openstack/django_openstack/tests/api_tests.py
Normal file
1541
django-openstack/django_openstack/tests/api_tests.py
Normal file
File diff suppressed because it is too large
Load Diff
2
django-openstack/django_openstack/tests/broken/README
Normal file
2
django-openstack/django_openstack/tests/broken/README
Normal file
@ -0,0 +1,2 @@
|
||||
Intentionally not a python module so that test runner won't find
|
||||
these broken tests
|
39
django-openstack/django_openstack/tests/dependency_tests.py
Normal file
39
django-openstack/django_openstack/tests/dependency_tests.py
Normal 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')
|
@ -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'
|
||||
|
@ -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
|
||||
|
@ -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 *
|
||||
|
106
django-openstack/django_openstack/tests/view_tests/base.py
Normal file
106
django-openstack/django_openstack/tests/view_tests/base.py
Normal 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)
|
@ -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']))
|
@ -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()
|
@ -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()
|
@ -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()
|
@ -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()
|
@ -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
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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',
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
|
@ -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 *
|
||||
|
10
openstack-dashboard/dashboard/templates/_container_form.html
Normal file
10
openstack-dashboard/dashboard/templates/_container_form.html
Normal 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>
|
18
openstack-dashboard/dashboard/templates/_container_list.html
Normal file
18
openstack-dashboard/dashboard/templates/_container_list.html
Normal 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>
|
10
openstack-dashboard/dashboard/templates/_copy_object.html
Normal file
10
openstack-dashboard/dashboard/templates/_copy_object.html
Normal 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>
|
@ -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>
|
||||
|
@ -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>
|
@ -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>
|
@ -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>
|
||||
|
10
openstack-dashboard/dashboard/templates/_object_filter.html
Normal file
10
openstack-dashboard/dashboard/templates/_object_filter.html
Normal 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>
|
10
openstack-dashboard/dashboard/templates/_object_form.html
Normal file
10
openstack-dashboard/dashboard/templates/_object_form.html
Normal 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>
|
18
openstack-dashboard/dashboard/templates/_object_list.html
Normal file
18
openstack-dashboard/dashboard/templates/_object_list.html
Normal 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>
|
@ -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>
|
||||
|
37
openstack-dashboard/dashboard/templates/dash_containers.html
Normal file
37
openstack-dashboard/dashboard/templates/dash_containers.html
Normal 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>—</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 >></a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -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>—</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 %}"><< 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 %}
|
@ -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>—</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 %}"><< 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 %}
|
33
openstack-dashboard/dashboard/templates/dash_objects.html
Normal file
33
openstack-dashboard/dashboard/templates/dash_objects.html
Normal 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>—</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 >></a>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -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>—</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 %}"><< 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 %}
|
@ -5,9 +5,7 @@
|
||||
|
||||
|
||||
{% block sidebar %}
|
||||
<div id="sidebar">
|
||||
EMPTY_SIDEBAR
|
||||
</div>
|
||||
<div id="sidebar" class="empty-sidebar"></div>
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
19
openstack-dashboard/dashboard/tests.py
Normal file
19
openstack-dashboard/dashboard/tests.py
Normal 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')
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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,
|
||||
|
@ -737,7 +737,6 @@ div.image_detail, div.instance_detail {
|
||||
border-color: #b7b7b7;
|
||||
}
|
||||
|
||||
|
||||
/* Footer */
|
||||
|
||||
#footer {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user