diff --git a/config.py b/config.py index d117a39..8b75bce 100644 --- a/config.py +++ b/config.py @@ -15,6 +15,8 @@ from oslo.config import cfg +from graffiti.common import driver_factory + # Server Specific Configurations server = { 'port': '21075', @@ -142,3 +144,7 @@ pydevd = { 'port': 22075, 'bindhost': 'localhost' } + + +# Discover and load drivers +df = driver_factory.DriverFactory() diff --git a/graffiti/api/controllers/v1/resource.py b/graffiti/api/controllers/v1/resource.py index 08d2b67..9d5e9fc 100644 --- a/graffiti/api/controllers/v1/resource.py +++ b/graffiti/api/controllers/v1/resource.py @@ -21,103 +21,97 @@ from wsme.api import Response from wsmeext.pecan import wsexpose from graffiti.api.model.v1.resource import Resource -from graffiti.api.plugins.glance_image import GlanceImage - - -from graffiti.api.model.v1.resource_dao_factory import \ - ResourceDAOFactory - -from graffiti.common.utils import _ - -from oslo.config import cfg +from graffiti.common import driver_factory import six -import keystoneclient.v2_0.client as ksclient - - -resource_controller_group = cfg.OptGroup('resource_controller') -resource_controller_opts = [ - cfg.StrOpt('type', - help=_("The resource controller plugin")) -] - -cfg.CONF.register_group(resource_controller_group) -cfg.CONF.register_opts(resource_controller_opts, - group=resource_controller_group) - class ResourceController(RestController): - # TODO(Lakshmi): Lookup supported types from plugin registry - local_resource_type = 'GFT:Local' - glance_resource_type = 'OS::Glance:Image' - def __init__(self): super(ResourceController, self).__init__() - self.status = 200 + self.default_resource_type = "GFT::Local" - self._controller = self._load_controller('Local') + @wsexpose(None, six.text_type, six.text_type, six.text_type) + def options(self, param1, param2=None, param3=None): + return Response(None, status_code=204) - def _load_controller(self, which_one): - controller_type = cfg.CONF.resource_controller.type - controller_type = controller_type if controller_type else 'Local' + @wsexpose(Resource, six.text_type, six.text_type, six.text_type) + def get_one(self, param1, param2=None, param3=None): + """Retrieve the resource based on the passed parameters + Depending on the number of parameters passed, the meaning + of the parameter is determined. - _controller = ResourceDAOFactory.create(controller_type) + Use case #1: only param1 is set + eg. /v1/resource/12345 + param1 is treated as resource id and resource type is defaulted + to graffiti local resource - return _controller + Use case #2: param1 and param2 are set + eg /v1/resource/OS::Glance::Image/d24a33343 + param1 = resource type + param2 = resource id - @wsexpose() - def options(self): - pass + Use case #3: param1, param2 and param3 are set + eg /v1/resource/OS::Glance::Image/d24a33343/e8dd383a838c83 + param1 = resource type + param2 = resource id + param3 = endpoint id - @wsexpose(Resource, six.text_type, six.text_type, six.text_type, - six.text_type) - def get_one(self, resource_id, resource_type=None, param1=None, - param2=None): - print "args:", resource_id, resource_type, param1, param2 - error_str = None - if not resource_type: - res = self._controller.get_resource(resource_id) + """ + print "args:", param1, param2, param3 + auth_token = pecan.request.headers.get('X-Auth-Token') + endpoint_id = None + + if not param2: + #Use case #1 + resource_id = param1 + resource_type = self.default_resource_type + else: + #Use case #2 + resource_type = param1 + resource_id = param2 + + if param3: + endpoint_id = param3 + + driver = driver_factory.get_driver(resource_type) + if driver.resource: + res = driver.resource.get_resource( + resource_id, + auth_token, + endpoint_id + ) return res - elif resource_type.lower() == \ - ResourceController.glance_resource_type.lower(): - auth_token = pecan.request.headers.get('X-Auth-Token') - endpoint_id = param1 - image_id = resource_id - glance_public_url = None - 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) - for endpoint in keystone.endpoints.list(): - if endpoint.id == endpoint_id: - glance_public_url = endpoint.publicurl - - # TODO(Lakshmi): Load plugins with plugin framework - if auth_token and glance_public_url: - glance_plugin = GlanceImage( - glance_public_url, - keystone.auth_token - ) - res = glance_plugin.get_resource(image_id) - if res: - return res - else: - error_str = "Resource not found" - else: - error_str = "auth_token and/or endpointid not found" + else: + error_str = "Driver not found for the resource type %s", \ + resource_type res = Response(Resource(), status_code=404, error=error_str) return res - @wsexpose([Resource], six.text_type) - def get_all(self, query_string=None): - res_list = self._controller.find_resources(query_string) - if res_list: - return res_list.itervalues() + @wsexpose([Resource], six.text_type, six.text_type) + def get_all(self, resource_type=None, query_string=None): + auth_token = pecan.request.headers.get('X-Auth-Token') + + if not resource_type: + resource_type = self.default_resource_type + + driver = driver_factory.get_driver(resource_type) + if driver.resource: + res_list = driver.resource.find_resources( + query_string, + auth_token + ) + if res_list: + return res_list.itervalues() + else: + resource = Response( + Resource(), + status_code=404, + error="Driver not found for the resource type") + return resource return [] @@ -126,42 +120,43 @@ class ResourceController(RestController): """Modify resource :resource param: graffiti.api.model.v1.resource.Resource """ - resource_type = resource.type - if not resource_type: - resource_type = ResourceController.local_resource_type - if resource_type.lower() == \ - ResourceController.local_resource_type.lower(): - self._controller.set_resource( - resource_id, - resource_definition=resource - ) - elif resource_type.lower() == \ - ResourceController.glance_resource_type.lower(): - auth_token = pecan.request.headers.get('X-Auth-Token') - endpoint_id = resource.provider.id - glance_public_url = None - 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) - for endpoint in keystone.endpoints.list(): - if endpoint.id == endpoint_id: - glance_public_url = endpoint.publicurl - # TODO(Lakshmi): Load plugins with plugin framework - if auth_token and glance_public_url: - glance_plugin = GlanceImage( - glance_public_url, - keystone.auth_token - ) - glance_plugin.update_resource(resource) + auth_token = pecan.request.headers.get('X-Auth-Token') + endpoint_id = resource.provider.id + + if not resource.type: + resource_type = self.default_resource_type + else: + resource_type = resource.type + + driver = driver_factory.get_driver(resource_type) + if driver.resource: + driver.resource.update_resource( + resource_id, + resource, + auth_token, + endpoint_id=endpoint_id + ) + else: + resource = Response( + Resource(), + status_code=404, + error="Driver not found for the resource type" + ) + return resource @wsexpose(Resource, body=Resource) def post(self, resource): + auth_token = pecan.request.headers.get('X-Auth-Token') - id = resource.id if hasattr(resource, 'id') else None - self._controller.set_resource(id, resource_definition=resource) + if not resource.type: + resource_type = self.default_resource_type + else: + resource_type = resource.type + + driver = driver_factory.get_driver(resource_type) + if driver.resource: + resource = driver.resource.create_resource(resource, auth_token) return resource diff --git a/graffiti/api/hooks.py b/graffiti/api/hooks.py index 9e69866..2ded002 100644 --- a/graffiti/api/hooks.py +++ b/graffiti/api/hooks.py @@ -24,7 +24,7 @@ class CorsHook(PecanHook): state.response.headers['Access-Control-Allow-Methods'] = \ 'GET, PUT, POST, DELETE, OPTIONS' state.response.headers['Access-Control-Allow-Headers'] = \ - 'origin, authorization, accept, content-type' + 'origin, authorization, accept, content-type, X-Auth-Token' if not state.response.headers['Content-Length']: state.response.headers['Content-Length'] = \ diff --git a/graffiti/api/model/v1/resource_dao.py b/graffiti/api/model/v1/resource_dao.py index 744a987..64625dc 100644 --- a/graffiti/api/model/v1/resource_dao.py +++ b/graffiti/api/model/v1/resource_dao.py @@ -55,6 +55,7 @@ class LocalResourceDAO(ResourceDAOBase): id = self._generate_id() self._resources[id] = resource_definition + return id def _generate_id(self): return_value = self._last_id diff --git a/graffiti/api/model/v1/resource_dao_factory.py b/graffiti/api/model/v1/resource_dao_factory.py index 71d3a90..f2aac0f 100644 --- a/graffiti/api/model/v1/resource_dao_factory.py +++ b/graffiti/api/model/v1/resource_dao_factory.py @@ -23,7 +23,8 @@ class ResourceDAOFactory(object): @staticmethod def create(dao_type, **kwargs): - if dao_type.lower() == 'local': + if dao_type.lower() == 'memory': + print "Directory persistence = memory" return LocalResourceDAO(**kwargs) return None diff --git a/graffiti/api/plugins/glance_image.py b/graffiti/api/plugins/glance_image.py deleted file mode 100644 index fa2eb4f..0000000 --- a/graffiti/api/plugins/glance_image.py +++ /dev/null @@ -1,95 +0,0 @@ -# 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 glanceclient 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 - - -class GlanceImage(object): - - def __init__(self, glance_endpoint, auth_token): - self.glance_endpoint = glance_endpoint - self.auth_token = auth_token - self.glance = Client('1', endpoint=glance_endpoint, token=auth_token) - self.separator = "." - - def get_resource(self, image_id): - # glance_image_properties = { - # "GLANCE.MySQL.Port": "3605", - # "GLANCE.MySQL.Home": "/opt/mysql", - # "GLANCE.Apache.Port": "8080", - # "GLANCE.Apache.docroot": "/var/apache/static" - # } - image = self.glance.images.get(image_id) - glance_image_properties = image.properties - image_resource = Resource() - image_capability = Capability() - image_capabilities = [] - image_resource.capabilities = image_capabilities - - image_resource.id = image_id - image_resource.type = 'image' - # image_resource.name = "ubuntu 12.04" - image_resource.name = image.name - - for key in glance_image_properties: - # replace if check with pattern matching - if key.count(self.separator) == 2: - (namespace, capability_type, prop_name) = key.split(".") - image_properties = [] - image_property = Property() - image_property.name = prop_name - image_property.value = glance_image_properties[key] - - image_capability = None - for capability in image_resource.capabilities: - if capability.capability_type_namespace == namespace and \ - capability.capability_type == capability_type: - image_capability = capability - - if not image_capability: - image_capability = Capability() - image_resource.capabilities.append(image_capability) - - image_capability.capability_type_namespace = namespace - image_capability.capability_type = capability_type - image_properties.append(image_property) - - image_capability.properties = image_properties - - return image_resource - - def update_resource(self, resource): - """Update Glance Image - :type param: graffiti.api.model.v1.resource.Resource - """ - - image_properties = {} - for capability in resource.capabilities: - properties = capability.properties - capability_type = capability.capability_type - capability_type_namespace = capability.capability_type_namespace - for property in properties: - prop_name = capability_type_namespace + \ - self.separator + \ - capability_type + \ - self.separator + \ - property.name - image_properties[prop_name] = property.value - - image = self.glance.images.get(resource.id) - image.update(properties=image_properties, purge_props=False) diff --git a/graffiti/api/tests/pecan_base.py b/graffiti/api/tests/pecan_base.py index d67762b..4068f3d 100644 --- a/graffiti/api/tests/pecan_base.py +++ b/graffiti/api/tests/pecan_base.py @@ -24,9 +24,10 @@ import os import pecan import pecan.testing -from oslo.config import cfg +#from oslo.config import cfg from graffiti.api.tests import base +from graffiti.common import driver_factory class TestCase(base.TestCase): @@ -35,8 +36,9 @@ class TestCase(base.TestCase): def setUp(self): super(TestCase, self).setUp() self.app = self._make_app() - cfg.CONF.set_override(name='type', override='Local', - group='resource_controller') + #cfg.CONF.set_override(name='type', override='Local', + # group='resource_controller') + driver_factory.DriverFactory() def _make_app(self): root_dir = self.path_get() diff --git a/graffiti/api/tests/samples/resource_2014-1.json b/graffiti/api/tests/samples/resource_2014-1.json index d6a54ac..c76d1e4 100644 --- a/graffiti/api/tests/samples/resource_2014-1.json +++ b/graffiti/api/tests/samples/resource_2014-1.json @@ -53,7 +53,7 @@ "capability_type_namespace": "TEST:RESOURCE:2014-1", "criterion": "StandardCriterion2" }], - "type": "TEST:RESOURCE:2014-1:StandardResource" + "type": "GFT::Local" }] } diff --git a/graffiti/api/tests/test_controller_v1.py b/graffiti/api/tests/test_controller_v1.py index af2f97b..404d306 100644 --- a/graffiti/api/tests/test_controller_v1.py +++ b/graffiti/api/tests/test_controller_v1.py @@ -78,7 +78,7 @@ class TestControllerV1(base.TestCase): self.assertIn(hasattr(v1, 'resource'), [True]) def test_v1_resource_controller_factory__local(self): - rc = ResourceDAOFactory.create('local') + rc = ResourceDAOFactory.create('memory') self.assertEquals(rc.get_type(), 'LocalResourceDAO') def test_v1_resource_controller_factory__unknown(self): diff --git a/graffiti/common/driver_factory.py b/graffiti/common/driver_factory.py new file mode 100644 index 0000000..2e8efa8 --- /dev/null +++ b/graffiti/common/driver_factory.py @@ -0,0 +1,119 @@ +# 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.common import exception + +from oslo.config import cfg + +from stevedore import dispatch + +driver_opts = [ + cfg.ListOpt('enabled_drivers', + default=['local', 'glance'], + help='List of drivers to enable. Missing drivers, or ' + 'drivers which can not be loaded will be ' + 'treated as a fatal exception.'), +] + +CONF = cfg.CONF +CONF.register_opts(driver_opts) + + +def get_driver(resource_type): + """Simple method to get a ref to an instance of a driver by the + supported resource type. + + Driver loading is handled by the DriverFactory class. This method + conveniently wraps that class and returns the actual driver object. + + :param resource_type: the resource type supported by a driver + :returns: An instance of a class which implements + graffiti.drivers.base.BaseResourceDriver + :raises: DriverNotFound if the requested driver_name could not be + found in the "graffiti.drivers" namespace. + + """ + + try: + factory = DriverFactory() + print "resource types", factory.resource_types + print "resource type", resource_type + if resource_type in factory.resource_types.keys(): + driver_name = factory.resource_types[resource_type] + return factory[driver_name].obj + else: + raise exception.DriverNotFoundForResourceType( + resource_type=resource_type + ) + + except KeyError: + raise exception.DriverNotFound(driver_name=driver_name) + + +class DriverFactory(object): + """Discover, load and manage the drivers available.""" + + _driver_manager = None + _resource_types = {} + + def __init__(self): + if not DriverFactory._driver_manager: + DriverFactory._init_driver_manager() + print "Loaded drivers:", self.names + + def __getitem__(self, name): + return self._driver_manager[name] + + @classmethod + def _init_driver_manager(self): + if self._driver_manager: + return + + def _catch_driver_not_found(mgr, ep, exc): + if (isinstance(exc, exception.DriverLoadError) and + ep.name not in CONF.enabled_drivers): + return + raise exc + + def _check_func(ext): + return ext.name in CONF.enabled_drivers + + self._driver_manager = dispatch.NameDispatchExtensionManager( + 'graffiti.drivers', + _check_func, + invoke_on_load=True, + on_load_failure_callback=_catch_driver_not_found + ) + + #Get supported resource types + for driver_name in self._driver_manager.names(): + driver = self._driver_manager[driver_name].obj + driver_resource_types = driver.get_resource_types() + for type in driver_resource_types: + self._resource_types[type] = driver_name + + @property + def names(self): + """The list of driver names available.""" + return self._driver_manager.names() + + @property + def resource_types(self): + """Returns all resource types supported by all the drivers + :returns dictionary with resource type as key and driver name + as its value + """ + return self._resource_types diff --git a/graffiti/common/exception.py b/graffiti/common/exception.py index 68bd957..46e55c0 100644 --- a/graffiti/common/exception.py +++ b/graffiti/common/exception.py @@ -13,34 +13,55 @@ # See the License for the specific language governing permissions and # limitations under the License. +from graffiti.openstack.common.gettextutils import _ + class GraffitiException(Exception): """Base Exception for the project To correctly use this class, inherit from it and define the 'message' property. + That message will get printf'd + with the keyword arguments provided to the constructor. """ - message = "An unknown exception occurred" + message = _("An unknown exception occurred") def __str__(self): return self.message - def __init__(self): - super(GraffitiException, self).__init__(self.message) + def __init__(self, **kwargs): + self.kwargs = kwargs + + try: + message = self.message % kwargs + except KeyError: + #TODO(Any): print to log + pass + + super(GraffitiException, self).__init__(message) class NotFound(GraffitiException): - message = "Object not found" - - def __init__(self, message=None): - if message: - self.message = message + message = _("Object not found") class DuplicateEntry(GraffitiException): - message = "Database object already exists" + message = _("Database object already exists") - def __init__(self, message=None): - if message: - self.message = message + +class DriverNotFound(NotFound): + message = _("Failed to load driver %(driver_name)s.") + + +class DriverLoadError(GraffitiException): + message = _("Driver %(driver)s could not be loaded. Reason: %(reason)s.") + + +class MethodNotSupported(GraffitiException): + message = _("Method %(method)s is not supported by this driver") + + +class DriverNotFoundForResourceType(NotFound): + message = _("Cannot find a registered driver for the resource " + "type %(resource_type)s") diff --git a/graffiti/api/plugins/__init__.py b/graffiti/drivers/__init__.py similarity index 100% rename from graffiti/api/plugins/__init__.py rename to graffiti/drivers/__init__.py diff --git a/graffiti/drivers/base.py b/graffiti/drivers/base.py new file mode 100644 index 0000000..59943d5 --- /dev/null +++ b/graffiti/drivers/base.py @@ -0,0 +1,103 @@ +# 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. + +""" +Abstract base class for graffiti resource drivers. +""" + +import abc +import six + + +@six.add_metaclass(abc.ABCMeta) +class BaseDriver(object): + """Base class for all drivers. + + Defines resource and definitions interface. + Any loadable driver must implement the interfaces it supports + + """ + + resource = None + + #TBD in future + #definitions = None + + @abc.abstractmethod + def __init__(self): + pass + + @abc.abstractmethod + def get_resource_types(self): + """Returns the resource types supported by the implementing driver + :returns [str] List of resource type strings + """ + + +@six.add_metaclass(abc.ABCMeta) +class ResourceInterface(object): + + @abc.abstractmethod + def get_resource(self, resource_id, auth_token, endpoint_id=None, + **kwargs): + """Retrieve the resource detail + :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 + """ + + @abc.abstractmethod + def find_resources(self, query_string, auth_token, endpoint_id=None, + **kwargs): + """Find resources matching the query + :param query_string: query expression + :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 + """ + + @abc.abstractmethod + def create_resource(self, resource, auth_token, endpoint_id=None, + **kwargs): + """Create resource + :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, + """ + + @abc.abstractmethod + def update_resource(self, resource_id, resource, auth_token, + endpoint_id=None, **kwargs): + """Update resource + :param resource_id: unique resource identifier + :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, + """ + + @abc.abstractmethod + def delete_resource(self, resource_id, auth_token, endpoint_id=None, + **kwargs): + """Delete resource + :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, + """ diff --git a/graffiti/drivers/glance.py b/graffiti/drivers/glance.py new file mode 100644 index 0000000..23bd690 --- /dev/null +++ b/graffiti/drivers/glance.py @@ -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 glance + + +class GlanceResourceDriver(base.BaseDriver): + """This driver implements glance resource driver interface + """ + + def __init__(self): + self.resource = glance.GlanceResourceDriver() + self.resource_types = ["OS::Glance::Image"] + + 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 diff --git a/graffiti/drivers/local.py b/graffiti/drivers/local.py new file mode 100644 index 0000000..798cf03 --- /dev/null +++ b/graffiti/drivers/local.py @@ -0,0 +1,33 @@ +# 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 local + + +class LocalResourceDriver(base.BaseDriver): + """This driver implements resource interface locally by graffiti + """ + + def __init__(self): + self.resource = local.LocalResourceDriver() + self.resource_types = ["GFT::Local"] + + 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 diff --git a/graffiti/drivers/modules/__init__.py b/graffiti/drivers/modules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/graffiti/drivers/modules/glance.py b/graffiti/drivers/modules/glance.py new file mode 100644 index 0000000..bdf4756 --- /dev/null +++ b/graffiti/drivers/modules/glance.py @@ -0,0 +1,167 @@ +# 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 glanceclient 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 + +import keystoneclient.v2_0.client as ksclient + +from oslo.config import cfg + + +class GlanceResourceDriver(base.ResourceInterface): + + def __init__(self): + super(GlanceResourceDriver, self).__init__() + self.separator = "." + + def get_resource(self, resource_id, auth_token, endpoint_id=None, + **kwargs): + """Retrieve the resource detail + :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 + """ + + # glance_image_properties = { + # "GLANCE.MySQL.Port": "3605", + # "GLANCE.MySQL.Home": "/opt/mysql", + # "GLANCE.Apache.Port": "8080", + # "GLANCE.Apache.docroot": "/var/apache/static" + # } + + glance_client = self.__get_glance_client(endpoint_id, auth_token) + + image = glance_client.images.get(resource_id) + glance_image_properties = image.properties + image_resource = Resource() + image_capabilities = [] + image_resource.capabilities = image_capabilities + + image_resource.id = resource_id + image_resource.type = 'image' + image_resource.name = image.name + + for key in glance_image_properties: + # replace if check with pattern matching + if key.count(self.separator) == 2: + (namespace, capability_type, prop_name) = key.split(".") + image_properties = [] + image_property = Property() + image_property.name = prop_name + image_property.value = glance_image_properties[key] + + image_capability = None + for capability in image_resource.capabilities: + if capability.capability_type_namespace == namespace and \ + capability.capability_type == capability_type: + image_capability = capability + + if not image_capability: + image_capability = Capability() + image_resource.capabilities.append(image_capability) + + image_capability.capability_type_namespace = namespace + image_capability.capability_type = capability_type + image_properties.append(image_property) + + image_capability.properties = image_properties + + return image_resource + + def update_resource(self, resource_id, resource, auth_token, + endpoint_id=None, **kwargs): + """Update resource + :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, + """ + + glance_client = self.__get_glance_client(endpoint_id, auth_token) + + image_properties = {} + for capability in resource.capabilities: + properties = capability.properties + capability_type = capability.capability_type + capability_type_namespace = capability.capability_type_namespace + for property in properties: + prop_name = capability_type_namespace + \ + self.separator + \ + capability_type + \ + self.separator + \ + property.name + image_properties[prop_name] = property.value + + image = glance_client.images.get(resource.id) + image.update(properties=image_properties, purge_props=False) + + def find_resources(self, query_string, auth_token, endpoint_id=None, + **kwargs): + """Find resources matching the query + :param query_string: query expression + :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 + """ + #TODO(Lakshmi): Implement this method + pass + + def create_resource(self, resource, auth_token, endpoint_id=None, + **kwargs): + """Create resource + :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_id, auth_token, endpoint_id=None, + **kwargs): + """Delete resource + :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_glance_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 + ) + self.__endpoint_list = keystone.endpoints.list() + for endpoint in self.__endpoint_list: + if endpoint.id == endpoint_id: + glance_public_url = endpoint.publicurl + glance_client = Client( + '1', + endpoint=glance_public_url, + token=auth_token + ) + return glance_client diff --git a/graffiti/drivers/modules/local.py b/graffiti/drivers/modules/local.py new file mode 100644 index 0000000..3f09474 --- /dev/null +++ b/graffiti/drivers/modules/local.py @@ -0,0 +1,97 @@ +# 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.api.model.v1.resource_dao_factory import \ + ResourceDAOFactory + +from graffiti.drivers import base + +from oslo.config import cfg + + +class LocalResourceDriver(base.ResourceInterface): + + def __init__(self): + super(LocalResourceDriver, self).__init__() + persistence_type = cfg.CONF.DEFAULT.persistence_type + self._resource_dao = ResourceDAOFactory.create(persistence_type) + + def get_resource(self, resource_id, auth_token, endpoint_id=None, + **kwargs): + """Retrieve the resource detail + :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 + """ + + res = self._resource_dao.get_resource(resource_id) + return res + + def find_resources(self, query_string, auth_token, endpoint_id=None, + **kwargs): + """Find resources matching the query + :param query_string: query expression + :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 + """ + res_list = self._resource_dao.find_resources(query_string) + if res_list: + return res_list + + return [] + + def create_resource(self, resource, auth_token, endpoint_id=None, + **kwargs): + """Create resource + :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, + """ + + id = resource.id if hasattr(resource, 'id') else None + self._resource_dao.set_resource(id, resource_definition=resource) + + return resource + + def update_resource(self, resource_id, resource, auth_token, + endpoint_id=None, **kwargs): + """Update resource + :param resource_id: unique resource identifier + :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, + """ + self._resource_dao.set_resource( + resource_id, + resource_definition=resource + ) + return resource + + def delete_resource(self, resource_id, auth_token, endpoint_id=None, + **kwargs): + """Delete resource + :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, + """ + #TODO(Lakshmi): Implement delete + pass diff --git a/graffiti/tests/drivers/__init__.py b/graffiti/tests/drivers/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/graffiti/tests/drivers/test_db_resource_driver.py b/graffiti/tests/drivers/test_db_resource_driver.py new file mode 100644 index 0000000..0105955 --- /dev/null +++ b/graffiti/tests/drivers/test_db_resource_driver.py @@ -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.api.tests import pecan_base +from graffiti.common import driver_factory +from graffiti.drivers import base as driver_base + + +class TestLocalResourceDriver(pecan_base.TestCase): + + def setUp(self): + super(TestLocalResourceDriver, self).setUp() + self.driver = driver_factory.get_driver("GFT::Local") + + def test_driver_interfaces(self): + self.assertIsInstance( + self.driver.resource, + driver_base.ResourceInterface + ) diff --git a/graffiti/tests/drivers/test_glance_resource_driver.py b/graffiti/tests/drivers/test_glance_resource_driver.py new file mode 100644 index 0000000..4f1b65b --- /dev/null +++ b/graffiti/tests/drivers/test_glance_resource_driver.py @@ -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.api.tests import pecan_base +from graffiti.common import driver_factory +from graffiti.drivers import base as driver_base + + +class TestGlanceResourceDriver(pecan_base.TestCase): + + def setUp(self): + super(TestGlanceResourceDriver, self).setUp() + self.driver = driver_factory.get_driver("OS::Glance::Image") + + def test_driver_interfaces(self): + self.assertIsInstance( + self.driver.resource, + driver_base.ResourceInterface + ) diff --git a/requirements.txt b/requirements.txt index 1a5841b..850cd93 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,3 +9,4 @@ iso8601>=0.1.8 requests>=1.1 six>=1.5.2 SQLAlchemy>=0.8,<=0.8.99 +stevedore>=0.14 diff --git a/setup.cfg b/setup.cfg index 08310b5..3375146 100644 --- a/setup.cfg +++ b/setup.cfg @@ -23,6 +23,12 @@ classifier = packages = graffiti +[entry_points] +graffiti.drivers = + local = graffiti.drivers.local:LocalResourceDriver + glance = graffiti.drivers.glance:GlanceResourceDriver + + [build_sphinx] source-dir = doc/source build-dir = doc/build