summaryrefslogtreecommitdiff
path: root/openstack_dashboard/api
diff options
context:
space:
mode:
Diffstat (limited to 'openstack_dashboard/api')
-rw-r--r--openstack_dashboard/api/__init__.py39
-rw-r--r--openstack_dashboard/api/base.py118
-rw-r--r--openstack_dashboard/api/glance.py98
-rw-r--r--openstack_dashboard/api/keystone.py291
-rw-r--r--openstack_dashboard/api/nova.py587
-rw-r--r--openstack_dashboard/api/quantum.py261
-rw-r--r--openstack_dashboard/api/swift.py265
7 files changed, 1659 insertions, 0 deletions
diff --git a/openstack_dashboard/api/__init__.py b/openstack_dashboard/api/__init__.py
new file mode 100644
index 000000000..65383e366
--- /dev/null
+++ b/openstack_dashboard/api/__init__.py
@@ -0,0 +1,39 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+#
+# Copyright 2012 Nebula, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+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, Horizon developers not working on openstack_dashboard.api
+shouldn't need to understand the finer details of APIs for
+Keystone/Nova/Glance/Swift et. al.
+"""
+from openstack_dashboard.api.glance import *
+from openstack_dashboard.api.keystone import *
+from openstack_dashboard.api.nova import *
+from openstack_dashboard.api.swift import *
+from openstack_dashboard.api.quantum import *
diff --git a/openstack_dashboard/api/base.py b/openstack_dashboard/api/base.py
new file mode 100644
index 000000000..15f9f9eeb
--- /dev/null
+++ b/openstack_dashboard/api/base.py
@@ -0,0 +1,118 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+#
+# Copyright 2012 Nebula, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import logging
+
+from django.conf import settings
+
+from horizon import exceptions
+
+
+__all__ = ('APIResourceWrapper', 'APIDictWrapper',
+ 'get_service_from_catalog', 'url_for',)
+
+
+LOG = logging.getLogger(__name__)
+
+
+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:
+ msg = ('Attempted to access unknown attribute "%s" on '
+ 'APIResource object of type "%s" wrapping resource of '
+ 'type "%s".') % (attr, self.__class__,
+ self._apiresource.__class__)
+ LOG.debug(exceptions.error_color(msg))
+ 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 novclient.
+ """
+ def __init__(self, apidict):
+ self._apidict = apidict
+
+ def __getattr__(self, attr):
+ try:
+ return self._apidict[attr]
+ except KeyError:
+ msg = 'Unknown attribute "%(attr)s" on APIResource object ' \
+ 'of type "%(cls)s"' % {'attr': attr, 'cls': self.__class__}
+ LOG.debug(exceptions.error_color(msg))
+ raise AttributeError(msg)
+
+ 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
+
+
+def get_service_from_catalog(catalog, service_type):
+ if catalog:
+ for service in catalog:
+ if service['type'] == service_type:
+ return service
+ return None
+
+
+def url_for(request, service_type, admin=False, endpoint_type=None):
+ endpoint_type = endpoint_type or getattr(settings,
+ 'OPENSTACK_ENDPOINT_TYPE',
+ 'publicURL')
+ catalog = request.user.service_catalog
+ service = get_service_from_catalog(catalog, service_type)
+ if service:
+ try:
+ if admin:
+ return service['endpoints'][0]['adminURL']
+ else:
+ return service['endpoints'][0][endpoint_type]
+ except (IndexError, KeyError):
+ raise exceptions.ServiceCatalogException(service_type)
+ else:
+ raise exceptions.ServiceCatalogException(service_type)
diff --git a/openstack_dashboard/api/glance.py b/openstack_dashboard/api/glance.py
new file mode 100644
index 000000000..9e0666fad
--- /dev/null
+++ b/openstack_dashboard/api/glance.py
@@ -0,0 +1,98 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+#
+# Copyright 2012 Nebula, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from __future__ import absolute_import
+
+import logging
+import thread
+import urlparse
+
+from django.conf import settings
+
+import glanceclient as glance_client
+
+from openstack_dashboard.api.base import url_for
+
+
+LOG = logging.getLogger(__name__)
+
+
+def glanceclient(request):
+ o = urlparse.urlparse(url_for(request, 'image'))
+ url = "://".join((o.scheme, o.netloc))
+ insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
+ LOG.debug('glanceclient connection created using token "%s" and url "%s"'
+ % (request.user.token.id, url))
+ return glance_client.Client('1', url, token=request.user.token.id,
+ insecure=insecure)
+
+
+def image_delete(request, image_id):
+ return glanceclient(request).images.delete(image_id)
+
+
+def image_get(request, image_id):
+ """
+ Returns an Image object populated with metadata for image
+ with supplied identifier.
+ """
+ return glanceclient(request).images.get(image_id)
+
+
+def image_list_detailed(request, marker=None, filters=None):
+ limit = getattr(settings, 'API_RESULT_LIMIT', 1000)
+ page_size = getattr(settings, 'API_RESULT_PAGE_SIZE', 20)
+ kwargs = {'filters': filters or {}}
+ if marker:
+ kwargs['marker'] = marker
+ images = list(glanceclient(request).images.list(page_size=page_size,
+ limit=limit,
+ **kwargs))
+ # Glance returns (page_size + 1) items if more items are available
+ if(len(images) > page_size):
+ return (images[0:-1], True)
+ else:
+ return (images, False)
+
+
+def image_update(request, image_id, **kwargs):
+ return glanceclient(request).images.update(image_id, **kwargs)
+
+
+def image_create(request, **kwargs):
+ copy_from = None
+
+ if kwargs.get('copy_from'):
+ copy_from = kwargs.pop('copy_from')
+
+ image = glanceclient(request).images.create(**kwargs)
+
+ if copy_from:
+ thread.start_new_thread(image_update,
+ (request, image.id),
+ {'copy_from': copy_from})
+
+ return image
+
+
+def snapshot_list_detailed(request, marker=None, extra_filters=None):
+ filters = {'property-image_type': 'snapshot'}
+ filters.update(extra_filters or {})
+ return image_list_detailed(request, marker, filters)
diff --git a/openstack_dashboard/api/keystone.py b/openstack_dashboard/api/keystone.py
new file mode 100644
index 000000000..aabf5006e
--- /dev/null
+++ b/openstack_dashboard/api/keystone.py
@@ -0,0 +1,291 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+#
+# Copyright 2012 Openstack, LLC
+# Copyright 2012 Nebula, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import logging
+import urlparse
+
+from django.conf import settings
+from django.utils.translation import ugettext_lazy as _
+
+from keystoneclient import service_catalog
+from keystoneclient.v2_0 import client as keystone_client
+from keystoneclient.v2_0 import tokens
+
+from openstack_auth.backend import KEYSTONE_CLIENT_ATTR
+
+from horizon import exceptions
+
+from openstack_dashboard.api import base
+
+
+LOG = logging.getLogger(__name__)
+DEFAULT_ROLE = None
+
+
+class Service(base.APIDictWrapper):
+ """ Wrapper for a dict based on the service data from keystone. """
+ _attrs = ['id', 'type', 'name']
+
+ def __init__(self, service, *args, **kwargs):
+ super(Service, self).__init__(service, *args, **kwargs)
+ self.url = service['endpoints'][0]['internalURL']
+ self.host = urlparse.urlparse(self.url).hostname
+ self.region = service['endpoints'][0]['region']
+ self.disabled = None
+
+ def __unicode__(self):
+ if(self.type == "identity"):
+ return _("%(type)s (%(backend)s backend)") \
+ % {"type": self.type,
+ "backend": keystone_backend_name()}
+ else:
+ return self.type
+
+ def __repr__(self):
+ return "<Service: %s>" % unicode(self)
+
+
+def _get_endpoint_url(request, endpoint_type, catalog=None):
+ if getattr(request.user, "service_catalog", None):
+ return base.url_for(request,
+ service_type='identity',
+ endpoint_type=endpoint_type)
+ return request.session.get('region_endpoint',
+ getattr(settings, 'OPENSTACK_KEYSTONE_URL'))
+
+
+def keystoneclient(request, admin=False):
+ """Returns a client connected to the Keystone backend.
+
+ Several forms of authentication are supported:
+
+ * Username + password -> Unscoped authentication
+ * Username + password + tenant id -> Scoped authentication
+ * Unscoped token -> Unscoped authentication
+ * Unscoped token + tenant id -> Scoped authentication
+ * Scoped token -> Scoped authentication
+
+ Available services and data from the backend will vary depending on
+ whether the authentication was scoped or unscoped.
+
+ Lazy authentication if an ``endpoint`` parameter is provided.
+
+ Calls requiring the admin endpoint should have ``admin=True`` passed in
+ as a keyword argument.
+
+ The client is cached so that subsequent API calls during the same
+ request/response cycle don't have to be re-authenticated.
+ """
+ user = request.user
+ if admin:
+ if not user.is_superuser:
+ raise exceptions.NotAuthorized
+ endpoint_type = 'adminURL'
+ else:
+ endpoint_type = getattr(settings,
+ 'OPENSTACK_ENDPOINT_TYPE',
+ 'internalURL')
+
+ # Take care of client connection caching/fetching a new client.
+ # Admin vs. non-admin clients are cached separately for token matching.
+ cache_attr = "_keystoneclient_admin" if admin else KEYSTONE_CLIENT_ATTR
+ if hasattr(request, cache_attr) and (not user.token.id
+ or getattr(request, cache_attr).auth_token == user.token.id):
+ LOG.debug("Using cached client for token: %s" % user.token.id)
+ conn = getattr(request, cache_attr)
+ else:
+ endpoint = _get_endpoint_url(request, endpoint_type)
+ # FIXME(ttrifonov): temporarily commented,
+ # as the fix in Keystone is not merged yet
+ #insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
+ LOG.debug("Creating a new keystoneclient connection to %s." % endpoint)
+ conn = keystone_client.Client(token=user.token.id,
+ endpoint=endpoint)
+ # insecure=insecure)
+ setattr(request, cache_attr, conn)
+ return conn
+
+
+def tenant_create(request, tenant_name, description, enabled):
+ return keystoneclient(request, admin=True).tenants.create(tenant_name,
+ description,
+ enabled)
+
+
+def tenant_get(request, tenant_id, admin=False):
+ return keystoneclient(request, admin=admin).tenants.get(tenant_id)
+
+
+def tenant_delete(request, tenant_id):
+ keystoneclient(request, admin=True).tenants.delete(tenant_id)
+
+
+def tenant_list(request, admin=False):
+ return keystoneclient(request, admin=admin).tenants.list()
+
+
+def tenant_update(request, tenant_id, tenant_name, description, enabled):
+ return keystoneclient(request, admin=True).tenants.update(tenant_id,
+ tenant_name,
+ description,
+ enabled)
+
+
+def token_create_scoped(request, tenant, token):
+ '''
+ Creates a scoped token using the tenant id and unscoped token; retrieves
+ the service catalog for the given tenant.
+ '''
+ if hasattr(request, '_keystone'):
+ del request._keystone
+ c = keystoneclient(request)
+ raw_token = c.tokens.authenticate(tenant_id=tenant,
+ token=token,
+ return_raw=True)
+ c.service_catalog = service_catalog.ServiceCatalog(raw_token)
+ if request.user.is_superuser:
+ c.management_url = c.service_catalog.url_for(service_type='identity',
+ endpoint_type='adminURL')
+ else:
+ endpoint_type = getattr(settings,
+ 'OPENSTACK_ENDPOINT_TYPE',
+ 'internalURL')
+ c.management_url = c.service_catalog.url_for(
+ service_type='identity', endpoint_type=endpoint_type)
+ scoped_token = tokens.Token(tokens.TokenManager, raw_token)
+ return scoped_token
+
+
+def user_list(request, tenant_id=None):
+ return keystoneclient(request, admin=True).users.list(tenant_id=tenant_id)
+
+
+def user_create(request, user_id, email, password, tenant_id, enabled):
+ return keystoneclient(request, admin=True).users.create(user_id,
+ password,
+ email,
+ tenant_id,
+ enabled)
+
+
+def user_delete(request, user_id):
+ keystoneclient(request, admin=True).users.delete(user_id)
+
+
+def user_get(request, user_id, admin=True):
+ return keystoneclient(request, admin=admin).users.get(user_id)
+
+
+def user_update(request, user, **data):
+ return keystoneclient(request, admin=True).users.update(user, **data)
+
+
+def user_update_enabled(request, user_id, enabled):
+ return keystoneclient(request, admin=True).users.update_enabled(user_id,
+ enabled)
+
+
+def user_update_password(request, user_id, password, admin=True):
+ return keystoneclient(request, admin=admin).users.update_password(user_id,
+ password)
+
+
+def user_update_tenant(request, user_id, tenant_id, admin=True):
+ return keystoneclient(request, admin=admin).users.update_tenant(user_id,
+ tenant_id)
+
+
+def role_list(request):
+ """ Returns a global list of available roles. """
+ return keystoneclient(request, admin=True).roles.list()
+
+
+def roles_for_user(request, user, project):
+ return keystoneclient(request, admin=True).roles.roles_for_user(user,
+ project)
+
+
+def add_tenant_user_role(request, tenant_id, user_id, role_id):
+ """ Adds a role for a user on a tenant. """
+ return keystoneclient(request, admin=True).roles.add_user_role(user_id,
+ role_id,
+ tenant_id)
+
+
+def remove_tenant_user_role(request, tenant_id, user_id, role_id):
+ """ Removes a given single role for a user from a tenant. """
+ client = keystoneclient(request, admin=True)
+ client.roles.remove_user_role(user_id, role_id, tenant_id)
+
+
+def remove_tenant_user(request, tenant_id, user_id):
+ """ Removes all roles from a user on a tenant, removing them from it. """
+ client = keystoneclient(request, admin=True)
+ roles = client.roles.roles_for_user(user_id, tenant_id)
+ for role in roles:
+ client.roles.remove_user_role(user_id, role.id, tenant_id)
+
+
+def get_default_role(request):
+ """
+ Gets the default role object from Keystone and saves it as a global
+ since this is configured in settings and should not change from request
+ to request. Supports lookup by name or id.
+ """
+ global DEFAULT_ROLE
+ default = getattr(settings, "OPENSTACK_KEYSTONE_DEFAULT_ROLE", None)
+ if default and DEFAULT_ROLE is None:
+ try:
+ roles = keystoneclient(request, admin=True).roles.list()
+ except:
+ roles = []
+ exceptions.handle(request)
+ for role in roles:
+ if role.id == default or role.name == default:
+ DEFAULT_ROLE = role
+ break
+ return DEFAULT_ROLE
+
+
+def list_ec2_credentials(request, user_id):
+ return keystoneclient(request).ec2.list(user_id)
+
+
+def create_ec2_credentials(request, user_id, tenant_id):
+ return keystoneclient(request).ec2.create(user_id, tenant_id)
+
+
+def get_user_ec2_credentials(request, user_id, access_token):
+ return keystoneclient(request).ec2.get(user_id, access_token)
+
+
+def keystone_can_edit_user():
+ if hasattr(settings, "OPENSTACK_KEYSTONE_BACKEND"):
+ return settings.OPENSTACK_KEYSTONE_BACKEND['can_edit_user']
+ else:
+ return False
+
+
+def keystone_backend_name():
+ if hasattr(settings, "OPENSTACK_KEYSTONE_BACKEND"):
+ return settings.OPENSTACK_KEYSTONE_BACKEND['name']
+ else:
+ return 'unknown'
diff --git a/openstack_dashboard/api/nova.py b/openstack_dashboard/api/nova.py
new file mode 100644
index 000000000..088231470
--- /dev/null
+++ b/openstack_dashboard/api/nova.py
@@ -0,0 +1,587 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+#
+# Copyright 2012 Openstack, LLC
+# Copyright 2012 Nebula, Inc.
+# Copyright (c) 2012 X.commerce, a business unit of eBay Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from __future__ import absolute_import
+
+import logging
+
+from django.conf import settings
+from django.utils.translation import ugettext as _
+
+from cinderclient.v1 import client as cinder_client
+
+from novaclient.v1_1 import client as nova_client
+from novaclient.v1_1 import security_group_rules as nova_rules
+from novaclient.v1_1.security_groups import SecurityGroup as NovaSecurityGroup
+from novaclient.v1_1.servers import REBOOT_HARD
+
+from horizon import exceptions
+from horizon.utils.memoized import memoized
+
+from openstack_dashboard.api.base import (APIResourceWrapper,
+ APIDictWrapper, url_for)
+
+
+LOG = logging.getLogger(__name__)
+
+
+# API static values
+INSTANCE_ACTIVE_STATE = 'ACTIVE'
+VOLUME_STATE_AVAILABLE = "available"
+
+
+class VNCConsole(APIDictWrapper):
+ """Wrapper for the "console" dictionary returned by the
+ novaclient.servers.get_vnc_console method.
+ """
+ _attrs = ['url', 'type']
+
+
+class Quota(object):
+ """Wrapper for individual limits in a quota."""
+ def __init__(self, name, limit):
+ self.name = name
+ self.limit = limit
+
+ def __repr__(self):
+ return "<Quota: (%s, %s)>" % (self.name, self.limit)
+
+
+class QuotaSet(object):
+ """Wrapper for novaclient.quotas.QuotaSet objects which wraps the
+ individual quotas inside Quota objects.
+ """
+ def __init__(self, apiresource):
+ self.items = []
+ for k in apiresource._info.keys():
+ if k in ['id']:
+ continue
+ limit = apiresource._info[k]
+ v = int(limit) if limit is not None else limit
+ q = Quota(k, v)
+ self.items.append(q)
+ setattr(self, k, v)
+
+
+class Server(APIResourceWrapper):
+ """Simple wrapper around novaclient.server.Server
+
+ Preserves the request info so image name can later be retrieved
+
+ """
+ _attrs = ['addresses', 'attrs', 'id', 'image', 'links',
+ 'metadata', 'name', 'private_ip', 'public_ip', 'status', 'uuid',
+ 'image_name', 'VirtualInterfaces', 'flavor', 'key_name',
+ 'tenant_id', 'user_id', 'OS-EXT-STS:power_state',
+ 'OS-EXT-STS:task_state', 'OS-EXT-SRV-ATTR:instance_name',
+ 'OS-EXT-SRV-ATTR:host']
+
+ def __init__(self, apiresource, request):
+ super(Server, self).__init__(apiresource)
+ self.request = request
+
+ @property
+ def image_name(self):
+ import glanceclient.exc as glance_exceptions
+ from openstack_dashboard.api import glance
+ try:
+ image = glance.image_get(self.request, self.image['id'])
+ return image.name
+ except glance_exceptions.ClientException:
+ return "(not found)"
+
+ @property
+ def internal_name(self):
+ return getattr(self, 'OS-EXT-SRV-ATTR:instance_name', "")
+
+ def reboot(self, hardness=REBOOT_HARD):
+ novaclient(self.request).servers.reboot(self.id, hardness)
+
+
+class Usage(APIResourceWrapper):
+ """Simple wrapper around contrib/simple_usage.py."""
+ _attrs = ['start', 'server_usages', 'stop', 'tenant_id',
+ 'total_local_gb_usage', 'total_memory_mb_usage',
+ 'total_vcpus_usage', 'total_hours']
+
+ def get_summary(self):
+ return {'instances': self.total_active_instances,
+ 'memory_mb': self.memory_mb,
+ 'vcpus': getattr(self, "total_vcpus_usage", 0),
+ 'vcpu_hours': self.vcpu_hours,
+ 'local_gb': self.local_gb,
+ 'disk_gb_hours': self.disk_gb_hours}
+
+ @property
+ def total_active_instances(self):
+ return sum(1 for s in self.server_usages if s['ended_at'] is None)
+
+ @property
+ def vcpus(self):
+ return sum(s['vcpus'] for s in self.server_usages
+ if s['ended_at'] is None)
+
+ @property
+ def vcpu_hours(self):
+ return getattr(self, "total_hours", 0)
+
+ @property
+ def local_gb(self):
+ return sum(s['local_gb'] for s in self.server_usages
+ if s['ended_at'] is None)
+
+ @property
+ def memory_mb(self):
+ return sum(s['memory_mb'] for s in self.server_usages
+ if s['ended_at'] is None)
+
+ @property
+ def disk_gb_hours(self):
+ return getattr(self, "total_local_gb_usage", 0)
+
+
+class SecurityGroup(APIResourceWrapper):
+ """Wrapper around novaclient.security_groups.SecurityGroup which wraps its
+ rules in SecurityGroupRule objects and allows access to them.
+ """
+ _attrs = ['id', 'name', 'description', 'tenant_id']
+
+ @property
+ def rules(self):
+ """Wraps transmitted rule info in the novaclient rule class."""
+ if "_rules" not in self.__dict__:
+ manager = nova_rules.SecurityGroupRuleManager
+ self._rules = [nova_rules.SecurityGroupRule(manager, rule)
+ for rule in self._apiresource.rules]
+ return self.__dict__['_rules']
+
+ @rules.setter
+ def rules(self, value):
+ self._rules = value
+
+
+class SecurityGroupRule(APIResourceWrapper):
+ """ Wrapper for individual rules in a SecurityGroup. """
+ _attrs = ['id', 'ip_protocol', 'from_port', 'to_port', 'ip_range', 'group']
+
+ def __unicode__(self):
+ if 'name' in self.group:
+ vals = {'from': self.from_port,
+ 'to': self.to_port,
+ 'group': self.group['name']}
+ return _('ALLOW %(from)s:%(to)s from %(group)s') % vals
+ else:
+ vals = {'from': self.from_port,
+ 'to': self.to_port,
+ 'cidr': self.ip_range['cidr']}
+ return _('ALLOW %(from)s:%(to)s from %(cidr)s') % vals
+
+
+def novaclient(request):
+ insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
+ LOG.debug('novaclient connection created using token "%s" and url "%s"' %
+ (request.user.token.id, url_for(request, 'compute')))
+ c = nova_client.Client(request.user.username,
+ request.user.token.id,
+ project_id=request.user.tenant_id,
+ auth_url=url_for(request, 'compute'),
+ insecure=insecure)
+ c.client.auth_token = request.user.token.id
+ c.client.management_url = url_for(request, 'compute')
+ return c
+
+
+def cinderclient(request):
+ insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
+ LOG.debug('cinderclient connection created using token "%s" and url "%s"' %
+ (request.user.token.id, url_for(request, 'volume')))
+ c = cinder_client.Client(request.user.username,
+ request.user.token.id,
+ project_id=request.user.tenant_id,
+ auth_url=url_for(request, 'volume'),
+ insecure=insecure)
+ c.client.auth_token = request.user.token.id
+ c.client.management_url = url_for(request, 'volume')
+ return c
+
+
+def server_vnc_console(request, instance_id, console_type='novnc'):
+ return VNCConsole(novaclient(request).servers.get_vnc_console(instance_id,
+ console_type)['console'])
+
+
+def flavor_create(request, name, memory, vcpu, disk, flavor_id, ephemeral=0):
+ return novaclient(request).flavors.create(name, int(memory), int(vcpu),
+ int(disk), flavor_id,
+ ephemeral=int(ephemeral))
+
+
+def flavor_delete(request, flavor_id):
+ novaclient(request).flavors.delete(flavor_id)
+
+
+def flavor_get(request, flavor_id):
+ return novaclient(request).flavors.get(flavor_id)
+
+
+@memoized
+def flavor_list(request):
+ """Get the list of available instance sizes (flavors)."""
+ return novaclient(request).flavors.list()
+
+
+def tenant_floating_ip_list(request):
+ """Fetches a list of all floating ips."""
+ return novaclient(request).floating_ips.list()
+
+
+def floating_ip_pools_list(request):
+ """Fetches a list of all floating ip pools."""
+ return novaclient(request).floating_ip_pools.list()
+
+
+def tenant_floating_ip_get(request, floating_ip_id):
+ """Fetches a floating ip."""
+ return novaclient(request).floating_ips.get(floating_ip_id)
+
+
+def tenant_floating_ip_allocate(request, pool=None):
+ """Allocates a floating ip to tenant. Optionally you may provide a pool
+ for which you would like the IP.
+ """
+ return novaclient(request).floating_ips.create(pool=pool)
+
+
+def tenant_floating_ip_release(request, floating_ip_id):
+ """Releases floating ip from the pool of a tenant."""
+ return novaclient(request).floating_ips.delete(floating_ip_id)
+
+
+def snapshot_create(request, instance_id, name):
+ return novaclient(request).servers.create_image(instance_id, name)
+
+
+def keypair_create(request, name):
+ return novaclient(request).keypairs.create(name)
+
+
+def keypair_import(request, name, public_key):
+ return novaclient(request).keypairs.create(name, public_key)
+
+
+def keypair_delete(request, keypair_id):
+ novaclient(request).keypairs.delete(keypair_id)
+
+
+def keypair_list(request):
+ return novaclient(request).keypairs.list()
+
+
+def server_create(request, name, image, flavor, key_name, user_data,
+ security_groups, block_device_mapping, nics=None,
+ instance_count=1):
+ return Server(novaclient(request).servers.create(
+ name, image, flavor, userdata=user_data,
+ security_groups=security_groups,
+ key_name=key_name, block_device_mapping=block_device_mapping,
+ nics=nics,
+ min_count=instance_count), request)
+
+
+def server_delete(request, instance):
+ novaclient(request).servers.delete(instance)
+
+
+def server_get(request, instance_id):
+ return Server(novaclient(request).servers.get(instance_id), request)
+
+
+def server_list(request, search_opts=None, all_tenants=False):
+ if search_opts is None:
+ search_opts = {}
+ if all_tenants:
+ search_opts['all_tenants'] = True
+ else:
+ search_opts['project_id'] = request.user.tenant_id
+ return [Server(s, request)
+ for s in novaclient(request).servers.list(True, search_opts)]
+
+
+def server_console_output(request, instance_id, tail_length=None):
+ """Gets console output of an instance."""
+ return novaclient(request).servers.get_console_output(instance_id,
+ length=tail_length)
+
+
+def server_security_groups(request, instance_id):
+ """Gets security groups of an instance."""
+ # TODO(gabriel): This needs to be moved up to novaclient, and should
+ # be removed once novaclient supports this call.
+ security_groups = []
+ nclient = novaclient(request)
+ resp, body = nclient.client.get('/servers/%s/os-security-groups'
+ % instance_id)
+ if body:
+ # Wrap data in SG objects as novaclient would.
+ sg_objs = [NovaSecurityGroup(nclient.security_groups, sg, loaded=True)
+ for sg in body.get('security_groups', [])]
+ # Then wrap novaclient's object with our own. Yes, sadly wrapping
+ # with two layers of objects is necessary.
+ security_groups = [SecurityGroup(sg) for sg in sg_objs]
+ # Package up the rules, as well.
+ for sg in security_groups:
+ rule_objects = [SecurityGroupRule(rule) for rule in sg.rules]
+ sg.rules = rule_objects
+ return security_groups
+
+
+def server_pause(request, instance_id):
+ novaclient(request).servers.pause(instance_id)
+
+
+def server_unpause(request, instance_id):
+ novaclient(request).servers.unpause(instance_id)
+
+
+def server_suspend(request, instance_id):
+ novaclient(request).servers.suspend(instance_id)
+
+
+def server_resume(request, instance_id):
+ novaclient(request).servers.resume(instance_id)
+
+
+def server_reboot(request, instance_id, hardness=REBOOT_HARD):
+ server = server_get(request, instance_id)
+ server.reboot(hardness)
+
+
+def server_update(request, instance_id, name):
+ response = novaclient(request).servers.update(instance_id, name=name)
+ # TODO(gabriel): servers.update method doesn't return anything. :-(
+ if response is None:
+ return True
+ else:
+ return response
+
+
+def server_add_floating_ip(request, server, floating_ip):
+ """Associates floating IP to server's fixed IP.
+ """
+ server = novaclient(request).servers.get(server)
+ fip = novaclient(request).floating_ips.get(floating_ip)
+ return novaclient(request).servers.add_floating_ip(server.id, fip.ip)
+
+
+def server_remove_floating_ip(request, server, floating_ip):
+ """Removes relationship between floating and server's fixed ip.
+ """
+ fip = novaclient(request).floating_ips.get(floating_ip)
+ server = novaclient(request).servers.get(fip.instance_id)
+ return novaclient(request).servers.remove_floating_ip(server.id, fip.ip)
+
+
+def tenant_quota_get(request, tenant_id):
+ return QuotaSet(novaclient(request).quotas.get(tenant_id))
+
+
+def tenant_quota_update(request, tenant_id, **kwargs):
+ novaclient(request).quotas.update(tenant_id, **kwargs)
+
+
+def tenant_quota_defaults(request, tenant_id):
+ return QuotaSet(novaclient(request).quotas.defaults(tenant_id))
+
+
+def usage_get(request, tenant_id, start, end):
+ return Usage(novaclient(request).usage.get(tenant_id, start, end))
+
+
+def usage_list(request, start, end):
+ return [Usage(u) for u in novaclient(request).usage.list(start, end, True)]
+
+
+@memoized
+def tenant_quota_usages(request):
+ """
+ Builds a dictionary of current usage against quota for the current
+ project.
+ """
+ instances = server_list(request)
+ floating_ips = tenant_floating_ip_list(request)
+ quotas = tenant_quota_get(request, request.user.tenant_id)
+ flavors = dict([(f.id, f) for f in flavor_list(request)])
+ volumes = volume_list(request)
+
+ usages = {'instances': {'flavor_fields': [], 'used': len(instances)},
+ 'cores': {'flavor_fields': ['vcpus'], 'used': 0},
+ 'gigabytes': {'used': sum([int(v.size) for v in volumes]),
+ 'flavor_fields': []},
+ 'volumes': {'used': len(volumes), 'flavor_fields': []},
+ 'ram': {'flavor_fields': ['ram'], 'used': 0},
+ 'floating_ips': {'flavor_fields': [], 'used': len(floating_ips)}}
+
+ for usage in usages:
+ for instance in instances:
+ used_flavor = instance.flavor['id']
+ if used_flavor not in flavors:
+ try:
+ flavors[used_flavor] = flavor_get(request, used_flavor)
+ except:
+ flavors[used_flavor] = {}
+ exceptions.handle(request, ignore=True)
+ for flavor_field in usages[usage]['flavor_fields']:
+ instance_flavor = flavors[used_flavor]
+ usages[usage]['used'] += getattr(instance_flavor,
+ flavor_field,
+ 0)
+
+ usages[usage]['quota'] = getattr(quotas, usage)
+
+ if usages[usage]['quota'] is None:
+ usages[usage]['quota'] = float("inf")
+ usages[usage]['available'] = float("inf")
+ elif type(usages[usage]['quota']) is str:
+ usages[usage]['quota'] = int(usages[usage]['quota'])
+ else:
+ if type(usages[usage]['used']) is str:
+ usages[usage]['used'] = int(usages[usage]['used'])
+
+ usages[usage]['available'] = usages[usage]['quota'] - \
+ usages[usage]['used']
+
+ return usages
+
+
+def security_group_list(request):
+ return [SecurityGroup(g) for g
+ in novaclient(request).security_groups.list()]
+
+
+def security_group_get(request, sg_id):
+ return SecurityGroup(novaclient(request).security_groups.get(sg_id))
+
+
+def security_group_create(request, name, desc):
+ return SecurityGroup(novaclient(request).security_groups.create(name,
+ desc))
+
+
+def security_group_delete(request, security_group_id):
+ novaclient(request).security_groups.delete(security_group_id)
+
+
+def security_group_rule_create(request, parent_group_id, ip_protocol=None,
+ from_port=None, to_port=None, cidr=None,
+ group_id=None):
+ sg = novaclient(request).security_group_rules.create(parent_group_id,
+ ip_protocol,
+ from_port,
+ to_port,
+ cidr,
+ group_id)
+ return SecurityGroupRule(sg)
+
+
+def security_group_rule_delete(request, security_group_rule_id):
+ novaclient(request).security_group_rules.delete(security_group_rule_id)
+
+
+def virtual_interfaces_list(request, instance_id):
+ return novaclient(request).virtual_interfaces.list(instance_id)
+
+
+def volume_list(request, search_opts=None):
+ """
+ To see all volumes in the cloud as an admin you can pass in a special
+ search option: {'all_tenants': 1}
+ """
+ return cinderclient(request).volumes.list(search_opts=search_opts)
+
+
+def volume_get(request, volume_id):
+ volume_data = cinderclient(request).volumes.get(volume_id)
+
+ for attachment in volume_data.attachments:
+ if "server_id" in attachment:
+ instance = server_get(request, attachment['server_id'])
+ attachment['instance_name'] = instance.name
+ else:
+ # Nova volume can occasionally send back error'd attachments
+ # the lack a server_id property; to work around that we'll
+ # give the attached instance a generic name.
+ attachment['instance_name'] = _("Unknown instance")
+ return volume_data
+
+
+def volume_instance_list(request, instance_id):
+ volumes = novaclient(request).volumes.get_server_volumes(instance_id)
+
+ for volume in volumes:
+ volume_data = cinderclient(request).volumes.get(volume.id)
+ volume.name = volume_data.display_name
+
+ return volumes
+
+
+def volume_create(request, size, name, description, snapshot_id=None):
+ return cinderclient(request).volumes.create(size, display_name=name,
+ display_description=description, snapshot_id=snapshot_id)
+
+
+def volume_delete(request, volume_id):
+ cinderclient(request).volumes.delete(volume_id)
+
+
+def volume_attach(request, volume_id, instance_id, device):
+ return novaclient(request).volumes.create_server_volume(instance_id,
+ volume_id,
+ device)
+
+
+def volume_detach(request, instance_id, att_id):
+ novaclient(request).volumes.delete_server_volume(instance_id, att_id)
+
+
+def volume_snapshot_get(request, snapshot_id):
+ return cinderclient(request).volume_snapshots.get(snapshot_id)
+
+
+def volume_snapshot_list(request):
+ return cinderclient(request).volume_snapshots.list()
+
+
+def volume_snapshot_create(request, volume_id, name, description):
+ return cinderclient(request).volume_snapshots.create(
+ volume_id, display_name=name, display_description=description)
+
+
+def volume_snapshot_delete(request, snapshot_id):
+ cinderclient(request).volume_snapshots.delete(snapshot_id)
+
+
+def get_x509_credentials(request):
+ return novaclient(request).certs.create()
+
+
+def get_x509_root_certificate(request):
+ return novaclient(request).certs.get()
diff --git a/openstack_dashboard/api/quantum.py b/openstack_dashboard/api/quantum.py
new file mode 100644
index 000000000..22c86271b
--- /dev/null
+++ b/openstack_dashboard/api/quantum.py
@@ -0,0 +1,261 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+#
+# Copyright 2012 Cisco Systems, Inc.
+# Copyright 2012 NEC Corporation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from __future__ import absolute_import
+
+import logging
+
+from quantumclient.v2_0 import client as quantum_client
+from django.utils.datastructures import SortedDict
+
+from openstack_dashboard.api.base import APIDictWrapper, url_for
+
+
+LOG = logging.getLogger(__name__)
+
+
+class QuantumAPIDictWrapper(APIDictWrapper):
+
+ def set_id_as_name_if_empty(self, length=8):
+ try:
+ if not self._apidict['name']:
+ id = self._apidict['id']
+ if length:
+ id = id[:length]
+ self._apidict['name'] = '(%s)' % id
+ except KeyError:
+ pass
+
+ def items(self):
+ return self._apidict.items()
+
+
+class Network(QuantumAPIDictWrapper):
+ """Wrapper for quantum Networks"""
+ _attrs = ['name', 'id', 'subnets', 'tenant_id', 'status',
+ 'admin_state_up', 'shared']
+
+ def __init__(self, apiresource):
+ apiresource['admin_state'] = \
+ 'UP' if apiresource['admin_state_up'] else 'DOWN'
+ super(Network, self).__init__(apiresource)
+
+
+class Subnet(QuantumAPIDictWrapper):
+ """Wrapper for quantum subnets"""
+ _attrs = ['name', 'id', 'cidr', 'network_id', 'tenant_id',
+ 'ip_version', 'ipver_str']
+
+ def __init__(self, apiresource):
+ apiresource['ipver_str'] = get_ipver_str(apiresource['ip_version'])
+ super(Subnet, self).__init__(apiresource)
+
+
+class Port(QuantumAPIDictWrapper):
+ """Wrapper for quantum ports"""
+ _attrs = ['name', 'id', 'network_id', 'tenant_id',
+ 'admin_state_up', 'status', 'mac_address',
+ 'fixed_ips', 'host_routes', 'device_id']
+
+ def __init__(self, apiresource):
+ apiresource['admin_state'] = \
+ 'UP' if apiresource['admin_state_up'] else 'DOWN'
+ super(Port, self).__init__(apiresource)
+
+
+IP_VERSION_DICT = {4: 'IPv4', 6: 'IPv6'}
+
+
+def get_ipver_str(ip_version):
+ """Convert an ip version number to a human-friendly string"""
+ return IP_VERSION_DICT.get(ip_version, '')
+
+
+def quantumclient(request):
+ LOG.debug('quantumclient connection created using token "%s" and url "%s"'
+ % (request.user.token.id, url_for(request, 'network')))
+ LOG.debug('user_id=%(user)s, tenant_id=%(tenant)s' %
+ {'user': request.user.id, 'tenant': request.user.tenant_id})
+ c = quantum_client.Client(token=request.user.token.id,
+ endpoint_url=url_for(request, 'network'))
+ return c
+
+
+def network_list(request, **params):
+ LOG.debug("network_list(): params=%s" % (params))
+ networks = quantumclient(request).list_networks(**params).get('networks')
+ # Get subnet list to expand subnet info in network list.
+ subnets = subnet_list(request)
+ subnet_dict = SortedDict([(s['id'], s) for s in subnets])
+ # Expand subnet list from subnet_id to values.
+ for n in networks:
+ n['subnets'] = [subnet_dict[s] for s in n['subnets']]
+ return [Network(n) for n in networks]
+
+
+def network_list_for_tenant(request, tenant_id, **params):
+ """Return a network list available for the tenant.
+ The list contains networks owned by the tenant and public networks.
+ If requested_networks specified, it searches requested_networks only.
+ """
+ LOG.debug("network_list_for_tenant(): tenant_id=%s, params=%s"
+ % (tenant_id, params))
+
+ # If a user has admin role, network list returned by Quantum API
+ # contains networks that do not belong to that tenant.
+ # So we need to specify tenant_id when calling network_list().
+ networks = network_list(request, tenant_id=tenant_id,
+ shared=False, **params)
+
+ # In the current Quantum API, there is no way to retrieve
+ # both owner networks and public networks in a single API call.
+ networks += network_list(request, shared=True, **params)
+
+ return networks
+
+
+def network_get(request, network_id, **params):
+ LOG.debug("network_get(): netid=%s, params=%s" % (network_id, params))
+ network = quantumclient(request).show_network(network_id,
+ **params).get('network')
+ # Since the number of subnets per network must be small,
+ # call subnet_get() for each subnet instead of calling
+ # subnet_list() once.
+ network['subnets'] = [subnet_get(request, sid)
+ for sid in network['subnets']]
+ return Network(network)
+
+
+def network_create(request, **kwargs):
+ """
+ Create a subnet on a specified network.
+ :param request: request context
+ :param tenant_id: (optional) tenant id of the network created
+ :param name: (optional) name of the network created
+ :returns: Subnet object
+ """
+ LOG.debug("network_create(): kwargs = %s" % kwargs)
+ body = {'network': kwargs}
+ network = quantumclient(request).create_network(body=body).get('network')
+ return Network(network)
+
+
+def network_modify(request, network_id, **kwargs):
+ LOG.debug("network_modify(): netid=%s, params=%s" % (network_id, kwargs))
+ body = {'network': kwargs}
+ network = quantumclient(request).update_network(network_id,
+ body=body).get('network')
+ return Network(network)
+
+
+def network_delete(request, network_id):
+ LOG.debug("network_delete(): netid=%s" % network_id)
+ quantumclient(request).delete_network(network_id)
+
+
+def subnet_list(request, **params):
+ LOG.debug("subnet_list(): params=%s" % (params))
+ subnets = quantumclient(request).list_subnets(**params).get('subnets')
+ return [Subnet(s) for s in subnets]
+
+
+def subnet_get(request, subnet_id, **params):
+ LOG.debug("subnet_get(): subnetid=%s, params=%s" % (subnet_id, params))
+ subnet = quantumclient(request).show_subnet(subnet_id,
+ **params).get('subnet')
+ return Subnet(subnet)
+
+
+def subnet_create(request, network_id, cidr, ip_version, **kwargs):
+ """
+ Create a subnet on a specified network.
+ :param request: request context
+ :param network_id: network id a subnet is created on
+ :param cidr: subnet IP address range
+ :param ip_version: IP version (4 or 6)
+ :param gateway_ip: (optional) IP address of gateway
+ :param tenant_id: (optional) tenant id of the subnet created
+ :param name: (optional) name of the subnet created
+ :returns: Subnet object
+ """
+ LOG.debug("subnet_create(): netid=%s, cidr=%s, ipver=%d, kwargs=%s"
+ % (network_id, cidr, ip_version, kwargs))
+ body = {'subnet':
+ {'network_id': network_id,
+ 'ip_version': ip_version,
+ 'cidr': cidr}}
+ body['subnet'].update(kwargs)
+ subnet = quantumclient(request).create_subnet(body=body).get('subnet')
+ return Subnet(subnet)
+
+
+def subnet_modify(request, subnet_id, **kwargs):
+ LOG.debug("subnet_modify(): subnetid=%s, kwargs=%s" % (subnet_id, kwargs))
+ body = {'subnet': kwargs}
+ subnet = quantumclient(request).update_subnet(subnet_id,
+ body=body).get('subnet')
+ return Subnet(subnet)
+
+
+def subnet_delete(request, subnet_id):
+ LOG.debug("subnet_delete(): subnetid=%s" % subnet_id)
+ quantumclient(request).delete_subnet(subnet_id)
+
+
+def port_list(request, **params):
+ LOG.debug("port_list(): params=%s" % (params))
+ ports = quantumclient(request).list_ports(**params).get('ports')
+ return [Port(p) for p in ports]
+
+
+def port_get(request, port_id, **params):
+ LOG.debug("port_get(): portid=%s, params=%s" % (port_id, params))
+ port = quantumclient(request).show_port(port_id, **params).get('port')
+ return Port(port)
+
+
+def port_create(request, network_id, **kwargs):
+ """
+ Create a port on a specified network.
+ :param request: request context
+ :param network_id: network id a subnet is created on
+ :param device_id: (optional) device id attached to the port
+ :param tenant_id: (optional) tenant id of the port created
+ :param name: (optional) name of the port created
+ :returns: Port object
+ """
+ LOG.debug("port_create(): netid=%s, kwargs=%s" % (network_id, kwargs))
+ body = {'port': {'network_id': network_id}}
+ body['port'].update(kwargs)
+ port = quantumclient(request).create_port(body=body).get('port')
+ return Port(port)
+
+
+def port_delete(request, port_id):
+ LOG.debug("port_delete(): portid=%s" % port_id)
+ quantumclient(request).delete_port(port_id)
+
+
+def port_modify(request, port_id, **kwargs):
+ LOG.debug("port_modify(): portid=%s, kwargs=%s" % (port_id, kwargs))
+ body = {'port': kwargs}
+ port = quantumclient(request).update_port(port_id, body=body).get('port')
+ return Port(port)
diff --git a/openstack_dashboard/api/swift.py b/openstack_dashboard/api/swift.py
new file mode 100644
index 000000000..b609b2e2f
--- /dev/null
+++ b/openstack_dashboard/api/swift.py
@@ -0,0 +1,265 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+#
+# Copyright 2012 Nebula, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import logging
+
+import swiftclient
+
+from django.conf import settings
+from django.utils.translation import ugettext as _
+
+from horizon import exceptions
+
+from openstack_dashboard.api.base import url_for, APIDictWrapper
+
+
+LOG = logging.getLogger(__name__)
+FOLDER_DELIMITER = "/"
+
+
+class Container(APIDictWrapper):
+ pass
+
+
+class StorageObject(APIDictWrapper):
+ def __init__(self, apidict, container_name, orig_name=None, data=None):
+ super(StorageObject, self).__init__(apidict)
+ self.container_name = container_name
+ self.orig_name = orig_name
+ self.data = data
+
+
+class PseudoFolder(APIDictWrapper):
+ """
+ Wrapper to smooth out discrepencies between swift "subdir" items
+ and swift pseudo-folder objects.
+ """
+
+ def __init__(self, apidict, container_name):
+ super(PseudoFolder, self).__init__(apidict)
+ self.container_name = container_name
+
+ def _has_content_type(self):
+ content_type = self._apidict.get("content_type", None)
+ return content_type == "application/directory"
+
+ @property
+ def name(self):
+ if self._has_content_type():
+ return self._apidict['name']
+ return self.subdir.rstrip(FOLDER_DELIMITER)
+
+ @property
+ def bytes(self):
+ if self._has_content_type():
+ return self._apidict['bytes']
+ return None
+
+ @property
+ def content_type(self):
+ return "application/directory"
+
+
+def _objectify(items, container_name):
+ """ Splits a listing of objects into their appropriate wrapper classes. """
+ objects = {}
+ subdir_markers = []
+
+ # Deal with objects and object pseudo-folders first, save subdirs for later
+ for item in items:
+ if item.get("content_type", None) == "application/directory":
+ objects[item['name']] = PseudoFolder(item, container_name)
+ elif item.get("subdir", None) is not None:
+ subdir_markers.append(PseudoFolder(item, container_name))
+ else:
+ objects[item['name']] = StorageObject(item, container_name)
+ # Revisit subdirs to see if we have any non-duplicates
+ for item in subdir_markers:
+ if item.name not in objects.keys():
+ objects[item.name] = item
+ return objects.values()
+
+
+def swift_api(request):
+ endpoint = url_for(request, 'object-store')
+ LOG.debug('Swift connection created using token "%s" and url "%s"'
+ % (request.user.token.id, endpoint))
+ return swiftclient.client.Connection(None,
+ request.user.username,
+ None,
+ preauthtoken=request.user.token.id,
+ preauthurl=endpoint,
+ auth_version="2.0")
+
+
+def swift_container_exists(request, container_name):
+ try:
+ swift_api(request).head_container(container_name)
+ return True
+ except swiftclient.client.ClientException:
+ return False
+
+
+def swift_object_exists(request, container_name, object_name):
+ try:
+ swift_api(request).head_object(container_name, object_name)
+ return True
+ except swiftclient.client.ClientException:
+ return False
+
+
+def swift_get_containers(request, marker=None):
+ limit = getattr(settings, 'API_RESULT_LIMIT', 1000)
+ headers, containers = swift_api(request).get_account(limit=limit + 1,
+ marker=marker,
+ full_listing=True)
+ container_objs = [Container(c) for c in containers]
+ if(len(container_objs) > limit):
+ return (container_objs[0:-1], True)
+ else:
+ return (container_objs, False)
+
+
+def swift_create_container(request, name):
+ if swift_container_exists(request, name):
+ raise exceptions.AlreadyExists(name, 'container')
+ swift_api(request).put_container(name)
+ return Container({'name': name})
+
+
+def swift_delete_container(request, name):
+ swift_api(request).delete_container(name)
+ return True
+
+
+def swift_get_objects(request, container_name, prefix=None, marker=None,
+ limit=None):
+ limit = limit or getattr(settings, 'API_RESULT_LIMIT', 1000)
+ kwargs = dict(prefix=prefix,
+ marker=marker,
+ limit=limit + 1,
+ delimiter=FOLDER_DELIMITER,
+ full_listing=True)
+ headers, objects = swift_api(request).get_container(container_name,
+ **kwargs)
+ object_objs = _objectify(objects, container_name)
+
+ if(len(object_objs) > limit):
+ return (object_objs[0:-1], True)
+ else:
+ return (object_objs, False)
+
+
+def swift_filter_objects(request, filter_string, container_name, prefix=None,
+ marker=None):
+ # FIXME(kewu): Swift currently has no real filtering API, thus the marker
+ # parameter here won't actually help the pagination. For now I am just
+ # getting the largest number of objects from a container and filtering
+ # based on those objects.
+ limit = 9999
+ objects = swift_get_objects(request,
+ container_name,
+ prefix=prefix,
+ marker=marker,
+ limit=limit)
+ filter_string_list = filter_string.lower().strip().split(' ')
+
+ def matches_filter(obj):
+ for q in filter_string_list:
+ return wildcard_search(obj.name.lower(), q)
+
+ return filter(matches_filter, objects[0])
+
+
+def wildcard_search(string, q):
+ q_list = q.split('*')
+ if all(map(lambda x: x == '', q_list)):
+ return True
+ elif q_list[0] not in string:
+ return False
+ else:
+ if q_list[0] == '':
+ tail = string
+ else:
+ head, delimiter, tail = string.partition(q_list[0])
+ return wildcard_search(tail, '*'.join(q_list[1:]))
+
+
+def swift_copy_object(request, orig_container_name, orig_object_name,
+ new_container_name, new_object_name):
+ try:
+ # FIXME(gabriel): The swift currently fails at unicode in the
+ # copy_to method, so to provide a better experience we check for
+ # unicode here and pre-empt with an error message rather than
+ # letting the call fail.
+ str(orig_container_name)
+ str(orig_object_name)
+ str(new_container_name)
+ str(new_object_name)
+ except UnicodeEncodeError:
+ raise exceptions.HorizonException(_("Unicode is not currently "
+ "supported for object copy."))
+
+ if swift_object_exists(request, new_container_name, new_object_name):
+ raise exceptions.AlreadyExists(new_object_name, 'object')
+
+ headers = {"X-Copy-From": FOLDER_DELIMITER.join([orig_container_name,
+ orig_object_name])}
+ return swift_api(request).put_object(new_container_name,
+ new_object_name,
+ None,
+ headers=headers)
+
+
+def swift_create_subfolder(request, container_name, folder_name):
+ headers = {'content-type': 'application/directory',
+ 'content-length': 0}
+ etag = swift_api(request).put_object(container_name,
+ folder_name,
+ None,
+ headers=headers)
+ obj_info = {'subdir': folder_name, 'etag': etag}
+ return PseudoFolder(obj_info, container_name)
+
+
+def swift_upload_object(request, container_name, object_name, object_file):
+ headers = {}
+ headers['X-Object-Meta-Orig-Filename'] = object_file.name
+ etag = swift_api(request).put_object(container_name,
+ object_name,
+ object_file,
+ headers=headers)
+ obj_info = {'name': object_name, 'bytes': object_file.size, 'etag': etag}
+ return StorageObject(obj_info, container_name)
+
+
+def swift_delete_object(request, container_name, object_name):
+ swift_api(request).delete_object(container_name, object_name)
+ return True
+
+
+def swift_get_object(request, container_name, object_name):
+ headers, data = swift_api(request).get_object(container_name, object_name)
+ orig_name = headers.get("x-object-meta-orig-filename")
+ obj_info = {'name': object_name, 'bytes': len(data)}
+ return StorageObject(obj_info,
+ container_name,
+ orig_name=orig_name,
+ data=data)