Cinder driver initial add

Basic support for geting and updating cinder metadata
Outstanding issues to be resolved on metadata handling
See mailing list archive

http://lists.openstack.org/pipermail/openstack-dev/2014-May/034657.html

Change-Id: I866c1e2d90f9b7a6b81f9945b6663939fe1fd10d
This commit is contained in:
Travis Tripp 2014-05-07 22:36:17 -06:00
parent e62f700b00
commit 64fe52c179
5 changed files with 310 additions and 2 deletions

View File

@ -22,7 +22,7 @@ from stevedore import dispatch
driver_opts = [
cfg.ListOpt('enabled_drivers',
default=['local', 'glance', 'nova'],
default=['local', 'glance', 'nova', 'cinder'],
help='List of drivers to enable. Missing drivers, or '
'drivers which can not be loaded will be '
'treated as a fatal exception.'),

View File

@ -0,0 +1,32 @@
# Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
#
# 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 graffiti.drivers import base
from graffiti.drivers.modules import cinder
class CinderResourceDriver(base.BaseDriver):
"""This driver implements cinder resource driver interface
"""
def __init__(self):
self.resource = cinder.CinderResourceDriver()
self.resource_types = ["OS::Cinder::Volume"]
def get_resource_types(self):
"""Returns the resource types supported by the implementing driver
:returns [str] List of resource type strings
"""
return self.resource_types

View File

@ -0,0 +1,274 @@
# Copyright (c) 2014 Hewlett-Packard Development Company, L.P.
#
# 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 cinderclient import client
from graffiti.api.model.v1.capability import Capability
from graffiti.api.model.v1.property import Property
from graffiti.api.model.v1.resource import Resource
from graffiti.common import exception
from graffiti.drivers import base
from graffiti.openstack.common import log as logging
import keystoneclient.v2_0.client as ksclient
from oslo.config import cfg
LOG = logging.getLogger(__name__)
class CinderResourceDriver(base.ResourceInterface):
def __init__(self):
super(CinderResourceDriver, self).__init__()
self.separator = "."
self.service_type = 'volume'
self.endpoint_type = 'publicURL'
self.default_namespace_suffix = "::Default"
self.unknown_properties_type = "AdditionalProperties"
self.unmodifiable_properties = [u'instance_uuid', u'kernel_id',
u'ramdisk_id', u'disk_format',
u'image_name', u'image_id',
u'readonly', u'container_format',
u'checksum', u'size']
def get_resource(self, resource_type, resource_id, auth_token,
endpoint_id=None, **kwargs):
"""Retrieve the resource detail
:param resource_type: resource_type set for this call
:param resource_id: unique resource identifier
:param auth_token: keystone auth_token of request user
:param endpoint_id: id for locating the cloud resource provider
:param **kwargs: Include additional info required by the driver,
:returns resource detail
"""
cinder_client = self.__get_cinder_client(endpoint_id, auth_token)
volume = cinder_client.volumes.get(resource_id)
cinder_resource = self.transform_to_resource(resource_type, volume)
return cinder_resource
def update_resource(self, resource_type, resource_id, resource, auth_token,
endpoint_id=None, **kwargs):
"""Update resource
:param resource_type: resource_type set for this call
:param resource_id: unique resource identifier
:param resource: resource detail
:type param: graffiti.api.model.v1.resource.Resource
:param auth_token: keystone auth_token of request user
:param endpoint_id: id for locating the cloud resource provider
:param **kwargs: Include additional info required by the driver,
"""
cinder_client = self.__get_cinder_client(endpoint_id, auth_token)
volume_properties = {}
for capability in resource.capabilities:
if capability.capability_type_namespace \
== resource_type + self.default_namespace_suffix \
and capability.capability_type \
== self.unknown_properties_type:
# For unknown properties, just directly set property name.
for property_name, property_value in \
capability.properties.iteritems():
volume_properties[property_name] = property_value
else:
properties = capability.properties
capability_type = self.replace_colon_from_name(
capability.capability_type
)
capability_type_namespace = self.replace_colon_from_name(
capability.capability_type_namespace
)
for property_name, property_value in properties.iteritems():
prop_name = capability_type_namespace + \
self.separator + \
capability_type + \
self.separator + \
self.replace_colon_from_name(property_name)
volume_properties[prop_name] = property_value
volume = cinder_client.volumes.get(resource_id)
properties_to_remove = self.get_volume_merged_properties(volume).keys()
#TODO(Travis) metadata_to_delete = []
for property_to_keep in volume_properties:
try:
properties_to_remove.remove(property_to_keep)
except ValueError:
#Sometimes properties to delete don't exist on server
pass
try:
volume.set_metadata(volume, volume_properties)
#TODO(Travis) cinder_client.volumes.
# delete_metadata(volume, properties_to_remove)
except AttributeError:
#Temporary until bug fixed
LOG.debug('Hit error: https://bugs.launchpad.net/bugs/1315175')
pass
def find_resources(self, resource_query, auth_token,
endpoint_id=None, **kwargs):
"""Find resources matching the query
:param resource_query: query object. Includes resource type(s)
:param auth_token: keystone auth_token of request user
:param endpoint_id: id for locating the cloud resource provider
:param **kwargs: Include additional info required by the driver,
:returns list of resources
"""
resource_list = dict()
#TODO(Travis): Filter based on resource type for snapshot etc
if resource_query:
cinder_client = self.__get_cinder_client(endpoint_id, auth_token)
for resource_type in resource_query.resource_types:
volumes = cinder_client.volumes.list()
for volume in list(volumes):
resource = self.transform_to_resource(
resource_type,
volume
)
resource_list[resource.id] = resource
return resource_list
def create_resource(self, resource_type, resource, auth_token,
endpoint_id=None, **kwargs):
"""Create resource
:param resource_type: resource_type set for this call
:param resource: resource detail
:param auth_token: keystone auth_token of request user
:param endpoint_id: id for locating the cloud resource provider
:param **kwargs: Include additional info required by the driver,
"""
raise exception.MethodNotSupported(method="create_resource")
def delete_resource(self, resource_type, resource_id, auth_token,
endpoint_id=None, **kwargs):
"""Delete resource
:param resource_type: resource_type set for this call
:param resource_id: unique resource identifier
:param auth_token: keystone auth_token of request user
:param endpoint_id: id for locating the cloud resource provider
:param **kwargs: Include additional info required by the driver,
"""
raise exception.MethodNotSupported(method="delete_resource")
def __get_cinder_client(self, endpoint_id, auth_token):
keystone = ksclient.Client(
auth_url=cfg.CONF.keystone.auth_url,
username=cfg.CONF.keystone.username,
password=cfg.CONF.keystone.password,
tenant_name=cfg.CONF.keystone.tenant_name
)
cinder_public_url = None
if endpoint_id:
for entry in keystone.service_catalog.catalog.get(
'serviceCatalog'):
for endpoint in entry['endpoints']:
if endpoint['id'] == endpoint_id:
cinder_public_url = endpoint['publicURL']
break
if cinder_public_url:
break
else:
cinder_public_url = keystone.service_catalog.url_for(
service_type=self.service_type,
endpoint_type=self.endpoint_type
)
cinder_client = client.Client(
'2',
cfg.CONF.keystone.username,
cfg.CONF.keystone.password,
cfg.CONF.keystone.tenant_name,
cfg.CONF.keystone.auth_url
)
return cinder_client
def get_volume_merged_properties(self, volume):
cinder_volume_properties = {}
volume_metadata = {}
volume_image_metadata = {}
try:
volume_image_metadata = volume.volume_image_metadata
except AttributeError:
pass
try:
volume_metadata = volume.metadata
except AttributeError:
pass
cinder_volume_properties = dict(volume_metadata,
**volume_image_metadata)
return cinder_volume_properties
def transform_to_resource(self, resource_type, volume):
cinder_volume_properties = self.get_volume_merged_properties(volume)
result = Resource()
result_capabilities = []
result.capabilities = result_capabilities
result.id = volume.id
result.type = resource_type
result.name = volume.name
for key in cinder_volume_properties:
if key in self.unmodifiable_properties:
continue
if key.count(self.separator) == 2:
(namespace, capability_type, prop_name) = key.split(".")
namespace = self.replace_hash_from_name(namespace)
capability_type = self.replace_hash_from_name(capability_type)
prop_name = self.replace_hash_from_name(prop_name)
else:
namespace = resource_type + self.default_namespace_suffix
capability_type = self.unknown_properties_type
prop_name = key
result_property = Property()
result_property.name = prop_name
result_property.value = cinder_volume_properties[key]
result_capability = None
for capability in result.capabilities:
if capability.capability_type_namespace == namespace and \
capability.capability_type == capability_type:
result_capability = capability
if not result_capability:
result_capability = Capability()
result_capability.properties = {}
result.capabilities.append(result_capability)
result_capability.capability_type_namespace = namespace
result_capability.capability_type = capability_type
result_capability.properties[result_property.name] = \
result_property.value
return result
def replace_colon_from_name(self, name):
if name:
return name.replace(':', '#')
return
def replace_hash_from_name(self, name):
if name:
return name.replace('#', ':')
return

View File

@ -5,6 +5,7 @@ WSME>=0.6
oslo.config>=1.2.0
python-glanceclient
python-novaclient
python-cinderclient
alembic>=0.4.1
iso8601>=0.1.8
requests>=1.1

View File

@ -28,6 +28,7 @@ graffiti.drivers =
local = graffiti.drivers.local:LocalResourceDriver
glance = graffiti.drivers.glance:GlanceResourceDriver
nova = graffiti.drivers.nova:NovaResourceDriver
cinder = graffiti.drivers.cinder:CinderResourceDriver
[build_sphinx]
source-dir = doc/source
@ -49,4 +50,4 @@ input_file = graffiti/locale/graffiti.pot
[extract_messages]
keywords = _ gettext ngettext l_ lazy_gettext
mapping_file = babel.cfg
output_file = graffiti/locale/graffiti.pot
output_file = graffiti/locale/graffiti.pot