Improve cinderv3 module and states
Add states and modules for volumes
and unify volume_type states and modules
Change-Id: I23cc715df696c828e7f7b53d5c4c85b146d93417
Closes-Issue: PROD-25036 (PROD:25036)
diff --git a/_modules/cinderv3/__init__.py b/_modules/cinderv3/__init__.py
index 650a6a2..9bb71f2 100644
--- a/_modules/cinderv3/__init__.py
+++ b/_modules/cinderv3/__init__.py
@@ -3,26 +3,41 @@
REQUIREMENTS_MET = True
except ImportError:
REQUIREMENTS_MET = False
-import os
-import sys
-# i failed to load module witjout this
-# seems bugs in salt or it is only me
-sys.path.insert(0, os.path.abspath(os.path.dirname(__file__)))
+from cinderv3 import lists
+from cinderv3 import volumes
+from cinderv3 import volume_types
+from cinderv3 import volume_actions
-import volume
+volume_list = lists.volume_list
+volume_list_get_details = lists.volume_list_get_details
+volume_create = volumes.volume_create
+volume_get_details = volumes.volume_get_details
+volume_update = volumes.volume_update
+volume_delete = volumes.volume_delete
+volume_metadata_create = volumes.volume_metadata_create
+volume_metadata_show = volumes.volume_metadata_show
+volume_metadata_update = volumes.volume_metadata_update
+volume_metadata_show_key = volumes.volume_metadata_show_key
+volume_metadata_delete = volumes.volume_metadata_delete
+volume_metadata_update_key = volumes.volume_metadata_update_key
+volume_type_list = lists.volume_type_list
+volume_type_get_details = volume_types.volume_type_get_details
+volume_type_create = volume_types.volume_type_create
+volume_type_delete = volume_types.volume_type_delete
+keys_volume_type_get = volume_types.keys_volume_type_get
+keys_volume_type_set = volume_types.keys_volume_type_set
+image_upload_volume = volume_actions.image_upload_volume
-volume_list = volume.volume_list
-volume_type_list = volume.volume_type_list
-volume_type_get = volume.volume_type_get
-volume_type_create = volume.volume_type_create
-volume_type_delete = volume.volume_type_delete
-keys_volume_type_get = volume.keys_volume_type_get
-keys_volume_type_set = volume.keys_volume_type_set
-
-__all__ = ('volume_list', 'volume_type_list', 'volume_type_get',
+__all__ = ('volume_list', 'volume_create', 'volume_delete',
+ 'volume_get_details', 'volume_list_get_details', 'volume_update',
+ 'volume_metadata_create', 'volume_metadata_delete',
+ 'volume_metadata_show', 'volume_metadata_show_key',
+ 'volume_metadata_update', 'volume_metadata_update_key',
+ 'volume_type_list', 'volume_type_get_details',
'volume_type_create', 'keys_volume_type_get',
- 'keys_volume_type_set', 'volume_type_delete')
+ 'keys_volume_type_set', 'volume_type_delete',
+ 'image_upload_volume',)
def __virtual__():
diff --git a/_modules/cinderv3/arg_converter.py b/_modules/cinderv3/arg_converter.py
new file mode 100644
index 0000000..3506c6d
--- /dev/null
+++ b/_modules/cinderv3/arg_converter.py
@@ -0,0 +1,89 @@
+import uuid
+from cinderv3 import common
+from cinderv3 import lists
+
+
+class CheckId(object):
+ def check_id(self, val):
+ try:
+ return str(uuid.UUID(val)) == val
+ except (TypeError, ValueError, AttributeError):
+ return False
+
+
+def named_checker(resource, ref, cloud_name):
+ resp_key = response_keys[resource]
+ resp = resource_lists[resource](
+ name=ref, cloud_name=cloud_name)
+ try:
+ resp = resp[resp_key]
+ except KeyError:
+ raise common.ResourceNotFound(resp_key, ref)
+ if len(resp) == 0:
+ raise common.ResourceNotFound(resp_key, ref)
+ elif len(resp) > 1:
+ raise common.MultipleResourcesFound(resp_key, ref)
+ return resp[0]['id']
+
+
+def nameless_checker(resource, ref, cloud_name):
+ item_id = None
+ resp_key = response_keys[resource]
+ resp = resource_lists[resource](cloud_name=cloud_name)
+ try:
+ resp = resp[resp_key]
+ except KeyError:
+ raise common.ResourceNotFound(resp_key, ref)
+ for item in resp:
+ if item["name"] == ref:
+ if item_id is not None:
+ raise common.MultipleResourcesFound(resp_key, ref)
+ item_id = item["id"]
+ if not item_id:
+ raise common.ResourceNotFound(resp_key, ref)
+ return item_id
+
+
+
+resource_lists = {
+ 'volume': lists.volume_list,
+ 'volume_type': lists.volume_type_list
+}
+
+
+response_keys = {
+ 'volume': 'volumes',
+ 'volume_type': 'volume_types',
+}
+
+
+name_checkers = {
+ 'volume': named_checker,
+ 'volume_type': nameless_checker,
+}
+
+
+def get_by_name_or_uuid_multiple(resource_arg_name_pairs):
+ def wrap(func):
+ def wrapped_f(*args, **kwargs):
+ results = []
+ args_start = 0
+ for index, (resource, arg_name) in enumerate(
+ resource_arg_name_pairs):
+ if arg_name in kwargs:
+ ref = kwargs.pop(arg_name, None)
+ else:
+ ref = args[index]
+ args_start += 1
+ cloud_name = kwargs['cloud_name']
+ checker = CheckId()
+ if checker.check_id(ref):
+ results.append(ref)
+ else:
+ # Then we have name not uuid
+ res = name_checkers[resource](resource, ref, cloud_name)
+ results.append(res)
+ results.extend(args[args_start:])
+ return func(*results, **kwargs)
+ return wrapped_f
+ return wrap
\ No newline at end of file
diff --git a/_modules/cinderv3/common.py b/_modules/cinderv3/common.py
index 08447fb..4edd604 100644
--- a/_modules/cinderv3/common.py
+++ b/_modules/cinderv3/common.py
@@ -1,64 +1,100 @@
-import six
-import logging
-import uuid
import time
-
+import logging
import os_client_config
-from salt import exceptions
-
log = logging.getLogger(__name__)
-SERVICE_KEY = 'volumev3'
+
+class CinderException(Exception):
+
+ _msg = "Cinder module exception occured."
+
+ def __init__(self, message=None, **kwargs):
+ super(CinderException, self).__init__(message or self._msg)
-def get_raw_client(cloud_name):
+class NoCinderEndpoint(CinderException):
+ _msg = "Cinder endpoint not found in keystone catalog."
+
+
+class NoAuthPluginConfigured(CinderException):
+ _msg = ("You are using keystoneauth auth plugin that does not support "
+ "fetching endpoint list from token (noauth or admin_token).")
+
+
+class NoCredentials(CinderException):
+ _msg = "Please provide cloud name present in clouds.yaml."
+
+
+class ResourceNotFound(CinderException):
+ _msg = "Uniq resource: {resource} with name: {name} not found."
+
+ def __init__(self, resource, name, **kwargs):
+ super(CinderException, self).__init__(
+ self._msg.format(resource=resource, name=name))
+
+
+class MultipleResourcesFound(CinderException):
+ _msg = "Multiple resource: {resource} with name: {name} found."
+
+ def __init__(self, resource, name, **kwargs):
+ super(CinderException, self).__init__(
+ self._msg.format(resource=resource, name=name))
+
+
+def _get_raw_client(cloud_name):
+ service_type = 'volumev3'
config = os_client_config.OpenStackConfig()
cloud = config.get_one_cloud(cloud_name)
- adapter = cloud.get_session_client(SERVICE_KEY)
+ adapter = cloud.get_session_client(service_type)
try:
access_info = adapter.session.auth.get_access(adapter.session)
endpoints = access_info.service_catalog.get_endpoints()
- except (AttributeError, ValueError) as exc:
- six.raise_from(exc, exceptions.SaltInvocationError(
- "Cannot load keystoneauth plugin. Please check your environment "
- "configuration."))
- if SERVICE_KEY not in endpoints:
- raise exceptions.SaltInvocationError("Cannot find cinder endpoint in "
- "environment endpoint list.")
+ except (AttributeError, ValueError):
+ e = NoAuthPluginConfigured()
+ log.exception('%s' % e)
+ raise e
+ if service_type not in endpoints:
+ if not service_type:
+ e = NoCinderEndpoint()
+ log.error('%s' % e)
+ raise e
return adapter
def send(method):
def wrap(func):
- @six.wraps(func)
def wrapped_f(*args, **kwargs):
- cloud_name = kwargs.pop('cloud_name', None)
connect_retries = 30
connect_retry_delay = 1
+ cloud_name = kwargs.pop('cloud_name')
if not cloud_name:
- raise exceptions.SaltInvocationError(
- "No cloud_name specified. Please provide cloud_name "
- "parameter")
- adapter = get_raw_client(cloud_name)
+ e = NoCredentials()
+ log.error('%s' % e)
+ raise e
+ adapter = _get_raw_client(cloud_name)
+ # Remove salt internal kwargs
kwarg_keys = list(kwargs.keys())
for k in kwarg_keys:
if k.startswith('__'):
kwargs.pop(k)
- url, request_kwargs = func(*args, **kwargs)
+ url, json = func(*args, **kwargs)
response = None
for i in range(connect_retries):
try:
- response = getattr(adapter, method.lower())(
- url, connect_retries=connect_retries,
- **request_kwargs)
+ if json:
+ response = getattr(adapter, method)(
+ url, json=json, connect_retries=connect_retries)
+ else:
+ response = getattr(adapter, method)(url)
except Exception as e:
if hasattr(e, 'http_status') and (e.http_status >= 500
- or e.http_status == 0):
+ or e.http_status == 0):
msg = ("Got retriable exception when contacting "
"Cinder API. Sleeping for %ss. Attepmpts "
"%s of %s")
- log.error(msg % (connect_retry_delay, i, connect_retries))
+ log.error(
+ msg % (connect_retry_delay, i, connect_retries))
time.sleep(connect_retry_delay)
continue
break
@@ -66,55 +102,8 @@
return {}
try:
resp = response.json()
- except ValueError:
+ except:
resp = response.content
return resp
return wrapped_f
return wrap
-
-
-def _check_uuid(val):
- try:
- return str(uuid.UUID(val)) == val
- except (TypeError, ValueError, AttributeError):
- return False
-
-
-def get_by_name_or_uuid(resource_list, resp_key):
- def wrap(func):
- @six.wraps(func)
- def wrapped_f(*args, **kwargs):
- if 'name' in kwargs:
- ref = kwargs.pop('name', None)
- start_arg = 0
- else:
- start_arg = 1
- ref = args[0]
- item_id = None
- if _check_uuid(ref):
- item_id = ref
- else:
- cloud_name = kwargs['cloud_name']
- # seems no filtering on volume type name in cinder
- resp = resource_list(cloud_name=cloud_name)[resp_key]
- # so need to search in list directly
- for item in resp:
- if item["name"] == ref:
- if item_id is not None:
- msg = ("Multiple resource: {resource} "
- "with name: {name} found ").format(
- resource=resp_key, name=ref)
- return {"result": False,
- "body": msg,
- "status_code": 400}
- item_id = item["id"]
- if not item_id:
- msg = ("Uniq {resource} resource "
- "with name={name} not found.").format(
- resource=resp_key, name=ref)
- return {"result": False,
- "body": msg,
- "status_code": 404}
- return func(item_id, *args[start_arg:], **kwargs)
- return wrapped_f
- return wrap
diff --git a/_modules/cinderv3/lists.py b/_modules/cinderv3/lists.py
new file mode 100644
index 0000000..d0bf4e9
--- /dev/null
+++ b/_modules/cinderv3/lists.py
@@ -0,0 +1,29 @@
+from cinderv3.common import send
+
+try:
+ from urllib.parse import urlencode
+except ImportError:
+ from urllib import urlencode
+
+@send("get")
+def volume_list(**kwargs):
+ """
+ Return list of cinder volumes.
+ """
+ url = '/volumes?{}'.format(urlencode(kwargs))
+ return url, None
+
+
+@send("get")
+def volume_list_get_details(**kwargs):
+ url = '/volumes/detail?{}'.format(urlencode(kwargs))
+ return url, None
+
+
+@send("get")
+def volume_type_list(**kwargs):
+ """
+ Return list of volume types
+ """
+ url = '/types?{}'.format(urlencode(kwargs))
+ return url, None
diff --git a/_modules/cinderv3/volume_actions.py b/_modules/cinderv3/volume_actions.py
new file mode 100644
index 0000000..35697c6
--- /dev/null
+++ b/_modules/cinderv3/volume_actions.py
@@ -0,0 +1,20 @@
+from cinderv3.common import send
+from cinderv3.arg_converter import get_by_name_or_uuid_multiple
+
+try:
+ from urllib.parse import urlencode
+except ImportError:
+ from urllib import urlencode
+
+
+@get_by_name_or_uuid_multiple([('volume', 'volume_id')])
+@send('post')
+def image_upload_volume(volume_id, image_name, **kwargs):
+ url = '/volumes/{}/action'.format(volume_id)
+ json = {
+ 'os-volume_upload_image': {
+ 'image_name': image_name,
+ }
+ }
+ json['os-volume_upload_image'].update(kwargs)
+ return url, json
diff --git a/_modules/cinderv3/volume.py b/_modules/cinderv3/volume_types.py
similarity index 62%
rename from _modules/cinderv3/volume.py
rename to _modules/cinderv3/volume_types.py
index 876416e..8e6ebd1 100644
--- a/_modules/cinderv3/volume.py
+++ b/_modules/cinderv3/volume_types.py
@@ -1,80 +1,60 @@
-from __future__ import absolute_import
-
-import common
-
+from cinderv3.common import send
+from cinderv3.arg_converter import get_by_name_or_uuid_multiple
try:
from urllib.parse import urlencode
except ImportError:
from urllib import urlencode
-@common.send("get")
-def volume_list(**kwargs):
- """
- Return list of cinder volumes.
- """
- url = '/volumes?{}'.format(urlencode(kwargs))
- return url, {}
-
-
-@common.send("get")
-def volume_type_list(**kwargs):
- """
- Return list of volume types
- """
- url = '/types?{}'.format(urlencode(kwargs))
- return url, {}
-
-
-@common.get_by_name_or_uuid(volume_type_list, 'volume_types')
-@common.send("get")
-def volume_type_get(volume_type_id, **kwargs):
+@get_by_name_or_uuid_multiple([('volume_type', 'volume_type_id')])
+@send("get")
+def volume_type_get_details(volume_type_id, **kwargs):
"""
Returns id of the specified volume type name
"""
url = "/types/{volume_type_id}".format(volume_type_id=volume_type_id)
- return url, {}
+ return url, None
-@common.get_by_name_or_uuid(volume_type_list, 'volume_types')
-@common.send("delete")
+@get_by_name_or_uuid_multiple([('volume_type', 'volume_type_id')])
+@send("delete")
def volume_type_delete(volume_type_id, **kwargs):
"""
delete the specified volume type
"""
url = "/types/{volume_type_id}".format(volume_type_id=volume_type_id)
- return url, {}
+ return url, None
-@common.send("post")
+@send("post")
def volume_type_create(name, **kwargs):
"""
Create cinder volume type
"""
url = "/types"
req = {"volume_type": {"name": name}}
- return url, {'json': req}
+ return url, req
-@common.get_by_name_or_uuid(volume_type_list, 'volume_types')
-@common.send("get")
+@get_by_name_or_uuid_multiple([('volume_type', 'volume_type_id')])
+@send("get")
def keys_volume_type_get(volume_type_id, **kwargs):
"""
Return extra specs of the specified volume type.
"""
url = "/types/{volume_type_id}/extra_specs".format(
volume_type_id=volume_type_id)
- return url, {}
+ return url, None
-@common.send("put")
+@send("put")
def _key_volume_type_set(type_id, key, value, **kwargs):
url = "/types/{volume_type_id}/extra_specs/{key}".format(
volume_type_id=type_id, key=key)
- return url, {'json': {str(key): str(value)}}
+ return url, {str(key): str(value)}
-@common.get_by_name_or_uuid(volume_type_list, 'volume_types')
+@get_by_name_or_uuid_multiple([('volume_type', 'volume_type_id')])
def keys_volume_type_set(volume_type_id, keys=None, **kwargs):
"""
Set extra specs of the specified volume type.
diff --git a/_modules/cinderv3/volumes.py b/_modules/cinderv3/volumes.py
new file mode 100644
index 0000000..6b8353a
--- /dev/null
+++ b/_modules/cinderv3/volumes.py
@@ -0,0 +1,90 @@
+from cinderv3.common import send
+from cinderv3.arg_converter import get_by_name_or_uuid_multiple
+
+try:
+ from urllib.parse import urlencode
+except ImportError:
+ from urllib import urlencode
+
+
+@send("post")
+def volume_create(size, **kwargs):
+ url = '/volumes'
+ json = {'volume': {
+ 'size': size,
+ }}
+ json['volume'].update(kwargs)
+ return url, json
+
+
+@get_by_name_or_uuid_multiple([('volume', 'volume_id')])
+@send("get")
+def volume_get_details(volume_id, **kwargs):
+ url = '/volumes/{}'.format(volume_id)
+ return url, None
+
+
+@get_by_name_or_uuid_multiple([('volume', 'volume_id')])
+@send("put")
+def volume_update(volume_id, **kwargs):
+ url = '/volumes/{}'.format(volume_id)
+ json = {'volume': kwargs}
+ return url, json
+
+
+@get_by_name_or_uuid_multiple([('volume', 'volume_id')])
+@send("delete")
+def volume_delete(volume_id, **kwargs):
+ url = '/volumes/{}'.format(volume_id)
+ return url, None
+
+
+@get_by_name_or_uuid_multiple([('volume', 'volume_id')])
+@send("post")
+def volume_metadata_create(volume_id, metadata, **kwargs):
+ url = '/volumes/{}/metadata'.format(volume_id)
+ json = {
+ 'metadata': metadata,
+ }
+ return url, json
+
+
+@get_by_name_or_uuid_multiple([('volume', 'volume_id')])
+@send("get")
+def volume_metadata_show(volume_id, **kwargs):
+ url = '/volumes/{}/metadata'.format(volume_id)
+ return url, None
+
+
+@get_by_name_or_uuid_multiple([('volume', 'volume_id')])
+@send("put")
+def volume_metadata_update(volume_id, metadata, **kwargs):
+ url = '/volumes/{}/metadata'.format(volume_id)
+ json = {
+ 'metadata': metadata,
+ }
+ return url, json
+
+
+@get_by_name_or_uuid_multiple([('volume', 'volume_id')])
+@send("get")
+def volume_metadata_show_key(volume_id, key, **kwargs):
+ url = '/volumes/{}/metadata/{}'.format(volume_id, key)
+ return url, None
+
+
+@get_by_name_or_uuid_multiple([('volume', 'volume_id')])
+@send("delete")
+def volume_metadata_delete(volume_id, key):
+ url = '/volumes/{}/metadata/{}'.format(volume_id, key)
+ return url, None
+
+
+@get_by_name_or_uuid_multiple([('volume', 'volume_id')])
+@send("put")
+def volume_metadata_update_key(volume_id, key, meta, **kwargs):
+ url = '/volumes/{}/metadata/{}'.format(volume_id, key)
+ json = {
+ 'meta': meta,
+ }
+ return url, json