diff options
Diffstat (limited to 'openstack_dashboard/api')
-rw-r--r-- | openstack_dashboard/api/__init__.py | 39 | ||||
-rw-r--r-- | openstack_dashboard/api/base.py | 118 | ||||
-rw-r--r-- | openstack_dashboard/api/glance.py | 98 | ||||
-rw-r--r-- | openstack_dashboard/api/keystone.py | 291 | ||||
-rw-r--r-- | openstack_dashboard/api/nova.py | 587 | ||||
-rw-r--r-- | openstack_dashboard/api/quantum.py | 261 | ||||
-rw-r--r-- | openstack_dashboard/api/swift.py | 265 |
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) |