245 lines
9.4 KiB
Python
245 lines
9.4 KiB
Python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
|
|
|
#
|
|
# Copyright (c) 2012, Intel Performance Learning Solutions Ltd.
|
|
#
|
|
# 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 random
|
|
|
|
from occi import backend
|
|
from occi.extensions import infrastructure
|
|
from webob import exc
|
|
|
|
from nova import exception
|
|
from nova import log as logging
|
|
from nova import volume
|
|
|
|
|
|
#Hi I'm a logger, use me! :-)
|
|
LOG = logging.getLogger('nova.api.occi.backends.storage')
|
|
|
|
|
|
class StorageBackend(backend.KindBackend, backend.ActionBackend):
|
|
"""
|
|
Backend to handle storage resources.
|
|
"""
|
|
def __init__(self):
|
|
super(StorageBackend, self).__init__()
|
|
self.volume_api = volume.API()
|
|
|
|
def create(self, resource, extras):
|
|
"""
|
|
Creates a new volume.
|
|
"""
|
|
|
|
if 'occi.storage.size' not in resource.attributes:
|
|
exc.HTTPBadRequest()
|
|
|
|
size = float(resource.attributes['occi.storage.size'])
|
|
|
|
# TODO(dizz): A blueprint?
|
|
# OpenStack deals with size in terms of integer.
|
|
# Need to convert float to integer for now and only if the float
|
|
# can be losslessly converted to integer
|
|
# e.g. See nova/quota.py:allowed_volumes(...)
|
|
if not size.is_integer:
|
|
msg = _('Volume sizes cannot be specified as fractional floats.')
|
|
LOG.error(msg)
|
|
raise exc.HTTPBadRequest()
|
|
|
|
size = str(int(size))
|
|
|
|
msg = _("Creating volume of %s GB") % size
|
|
LOG.info(msg)
|
|
|
|
disp_name = ''
|
|
try:
|
|
disp_name = resource.attributes['occi.core.title']
|
|
except KeyError:
|
|
#Generate more suitable name as it's used for hostname
|
|
#where no hostname is supplied.
|
|
disp_name = resource.attributes['occi.core.title'] = \
|
|
str(random.randrange(0, 99999999)) + \
|
|
'-storage.occi-wg.org'
|
|
if 'occi.core.summary' in resource.attributes:
|
|
disp_descr = resource.attributes['occi.core.summary']
|
|
else:
|
|
disp_descr = disp_name
|
|
|
|
snapshot = None
|
|
# volume_type can be specified by mixin
|
|
volume_type = None
|
|
metadata = None
|
|
avail_zone = None
|
|
new_volume = self.volume_api.create(extras['nova_ctx'],
|
|
size,
|
|
disp_name,
|
|
disp_descr,
|
|
snapshot=snapshot,
|
|
volume_type=volume_type,
|
|
metadata=metadata,
|
|
availability_zone=avail_zone)
|
|
|
|
# Work around problem that instance is lazy-loaded...
|
|
new_volume = self.volume_api.get(extras['nova_ctx'], new_volume['id'])
|
|
|
|
if new_volume['status'] == 'error':
|
|
msg = _('There was an error creating the volume')
|
|
LOG.error(msg)
|
|
raise exc.HTTPServerError(msg)
|
|
|
|
resource.attributes['occi.core.id'] = str(new_volume['id'])
|
|
|
|
if new_volume['status'] == 'available':
|
|
resource.attributes['occi.storage.state'] = 'online'
|
|
|
|
resource.actions = [infrastructure.OFFLINE, infrastructure.BACKUP,
|
|
infrastructure.SNAPSHOT, infrastructure.RESIZE]
|
|
|
|
def retrieve(self, entity, extras):
|
|
"""
|
|
Gets a representation of the storage volume and presents it ready for
|
|
rendering by pyssf.
|
|
"""
|
|
v_id = int(entity.attributes['occi.core.id'])
|
|
|
|
try:
|
|
vol = self.volume_api.get(extras['nova_ctx'], v_id)
|
|
except exception.NotFound:
|
|
raise exc.HTTPNotFound()
|
|
|
|
entity.attributes['occi.storage.size'] = str(float(vol['size']))
|
|
|
|
# OS volume states:
|
|
# available, creating, deleting, in-use, error, error_deleting
|
|
if vol['status'] == 'available' or vol['status'] == 'in-use':
|
|
entity.attributes['occi.storage.state'] = 'online'
|
|
entity.actions = [infrastructure.OFFLINE, infrastructure.BACKUP,
|
|
infrastructure.SNAPSHOT, infrastructure.RESIZE]
|
|
|
|
def delete(self, entity, extras):
|
|
"""
|
|
Deletes the storage resource
|
|
"""
|
|
msg = _('Removing storage device with id: %s') % entity.identifier
|
|
LOG.info(msg)
|
|
|
|
volume_id = int(entity.attributes['occi.core.id'])
|
|
|
|
try:
|
|
vol = self.volume_api.get(extras['nova_ctx'], volume_id)
|
|
self.volume_api.delete(extras['nova_ctx'], vol)
|
|
except exception.NotFound:
|
|
raise exc.HTTPNotFound()
|
|
|
|
def action(self, entity, action, attributes, extras):
|
|
"""
|
|
Executes actions against the target storage resource.
|
|
"""
|
|
if action not in entity.actions:
|
|
raise AttributeError("This action is currently no applicable.")
|
|
|
|
elif action == infrastructure.ONLINE:
|
|
# ONLINE, ready for service, default state of a created volume.
|
|
# could this cover the attach functionality in storage link?
|
|
# The following is not an approach to use:
|
|
# self.volume_api.initialize_connection(context, volume, connector)
|
|
|
|
# By default storage is ONLINE and can not be brought OFFLINE
|
|
|
|
msg = _('Online storage action requested resource with id: %s') % \
|
|
entity.identifier
|
|
LOG.warn(msg)
|
|
raise exc.HTTPBadRequest()
|
|
|
|
elif action == infrastructure.OFFLINE:
|
|
# OFFLINE, disconnected? disconnection supported in API otherwise
|
|
# not. The following is not an approach to use:
|
|
# self.volume_api.terminate_connection(context, volume, connector)
|
|
|
|
# By default storage cannot be brought OFFLINE
|
|
msg = _('Offline storage action requested for resource: %s') % \
|
|
entity.identifier
|
|
LOG.warn(msg)
|
|
raise exc.HTTPBadRequest()
|
|
|
|
elif action == infrastructure.BACKUP:
|
|
# BACKUP: create a complete copy of the volume.
|
|
msg = _('Backup action for storage resource with id: %s') % \
|
|
entity.identifier
|
|
LOG.warn(msg)
|
|
raise exc.HTTPBadRequest()
|
|
|
|
elif action == infrastructure.SNAPSHOT:
|
|
# CDMI?!
|
|
# SNAPSHOT: create a time-stamped copy of the volume? Supported in
|
|
# OS volume API
|
|
self._snapshot_storage(entity, extras)
|
|
|
|
elif action == infrastructure.RESIZE:
|
|
# TODO(dizz): not supported by API. A blueprint candidate?
|
|
# RESIZE: increase, decrease size of volume. Not supported directly
|
|
# by the API
|
|
|
|
msg = _('Resize storage actio requested resource with id: %s') % \
|
|
entity.identifier
|
|
LOG.warn(msg)
|
|
raise exc.HTTPNotImplemented()
|
|
|
|
def _snapshot_storage(self, entity, extras, backup=False):
|
|
"""
|
|
Takes a snapshot of the specified storage resource
|
|
"""
|
|
msg = _('Snapshoting storage resource with id: %s') % entity.identifier
|
|
LOG.info(msg)
|
|
|
|
volume_id = int(entity.attributes['occi.core.id'])
|
|
vol = self.volume_api.get(extras['nova_ctx'], volume_id)
|
|
#TODO(dizz): these names/descriptions should be made better.
|
|
if backup:
|
|
name = 'backup name'
|
|
description = 'backup description'
|
|
else:
|
|
# occi.core.title, occi.core.summary
|
|
name = 'snapshot name'
|
|
description = 'snapshot description'
|
|
self.volume_api.create_snapshot(extras['nova_ctx'],
|
|
vol, name, description)
|
|
|
|
def update(self, old, new, extras):
|
|
"""
|
|
Updates simple attributes of a storage resource:
|
|
occi.core.title, occi.core.summary
|
|
"""
|
|
# update attributes.
|
|
if len(new.attributes) > 0:
|
|
msg = _('Updating mutable attributes of volume instance')
|
|
LOG.info(msg)
|
|
# support only title and summary changes now.
|
|
if (('occi.core.title' in new.attributes)
|
|
or ('occi.core.title' in new.attributes)):
|
|
if len(new.attributes['occi.core.title']) > 0:
|
|
old.attributes['occi.core.title'] = \
|
|
new.attributes['occi.core.title']
|
|
|
|
if len(new.attributes['occi.core.summary']) > 0:
|
|
old.attributes['occi.core.summary'] = \
|
|
new.attributes['occi.core.summary']
|
|
else:
|
|
msg = _('Cannot update the supplied attributes.')
|
|
LOG.error()
|
|
raise exc.HTTPBadRequest()
|
|
else:
|
|
raise exc.HTTPBadRequest()
|