Nova Style API Version Support for Client
The Manila client needs the following changes to support microversions:
* Maintain backwards compatibility with Kilo. When the client detects
that the server doesn't support microversions it will fall back to
using the v1 API.
* The --os-share-api-version option supports overriding the version.
* If 1.0 is specified as the version the client will load the v1
client and use the server's v1 API.
* The client will send a request for the server's API version and
determine if the client's supported versions and the server's
supported versions overlap. If not the client will display an error
and quit. See diagram 1 below.
* The client supports the @wraps annotation. The annotation is used
with the v2/shell.py commands and any class that inherits from
the Manager class in manilaclient/base.py.
* If an appropriate command version isn't found for commands using
@wraps then the client will display an error and quit.
following commit: ab49d645be
.
Diagram 1:
Client:
2.5 2.8
|-------------|
Server1:
2.0 2.5
|-------------|
Client uses version 2.5
Server2:
2.7 2.10
|-------------|
Client uses version 2.8
Server3:
2.9 2.12
|-------------|
Client displays error and quits
Server4:
1.0 (Kilo Server)
|-|
Client detects pre-microversion server and loads v1 client
Example usage of wraps annotation:
* Support 2.0 - 2.4: @api_versions.wraps("2.0", "2.4")
* Support 2.5 - latest: @api_versions.wraps("2.5")
Implements: blueprint manila-client-advanced-microversion-support
Change-Id: I3733fe85424e39566addc070d42609e508259f19
This commit is contained in:
parent
47b519106a
commit
c34e3b7d2c
|
@ -1,6 +1,6 @@
|
|||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2012 OpenStack LLC
|
||||
# Copyright 2015 Chuck Fouts
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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
|
||||
|
@ -18,6 +18,8 @@ __all__ = ['__version__']
|
|||
|
||||
import pbr.version
|
||||
|
||||
from manilaclient import api_versions
|
||||
|
||||
version_info = pbr.version.VersionInfo('python-manilaclient')
|
||||
# We have a circular import problem when we first run python setup.py sdist
|
||||
# It's harmless, so deflect it.
|
||||
|
@ -25,3 +27,9 @@ try:
|
|||
__version__ = version_info.version_string()
|
||||
except AttributeError:
|
||||
__version__ = None
|
||||
|
||||
|
||||
API_MAX_VERSION = api_versions.APIVersion(api_versions.MAX_VERSION)
|
||||
API_MIN_VERSION = api_versions.APIVersion(api_versions.MIN_VERSION)
|
||||
API_DEPRECATED_VERSION = api_versions.APIVersion(
|
||||
api_versions.DEPRECATED_VERSION)
|
||||
|
|
|
@ -12,10 +12,325 @@
|
|||
# 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 functools
|
||||
import logging
|
||||
import re
|
||||
import warnings
|
||||
|
||||
import manilaclient
|
||||
from manilaclient.common import constants
|
||||
from manilaclient import exceptions
|
||||
from manilaclient.openstack.common._i18n import _
|
||||
from manilaclient.openstack.common import cliutils
|
||||
from manilaclient import utils
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
if not LOG.handlers:
|
||||
LOG.addHandler(logging.StreamHandler())
|
||||
|
||||
|
||||
MAX_VERSION = '2.6'
|
||||
MIN_VERSION = '2.0'
|
||||
DEPRECATED_VERSION = '1.0'
|
||||
_VERSIONED_METHOD_MAP = {}
|
||||
|
||||
|
||||
class APIVersion(object):
|
||||
"""Top level object to support Manila API Versioning.
|
||||
|
||||
This class represents an API Version with convenience
|
||||
methods for manipulation and comparison of version
|
||||
numbers that we need to do to implement microversions.
|
||||
"""
|
||||
|
||||
TYPE_ERROR_MSG = _("'%(other)s' should be an instance of '%(cls)s'")
|
||||
|
||||
def __init__(self, version_str=None):
|
||||
"""Create an API version object."""
|
||||
self.ver_major = 0
|
||||
self.ver_minor = 0
|
||||
|
||||
if version_str is not None:
|
||||
match = re.match(r"^([1-9]\d*)\.([1-9]\d*|0|)$", version_str)
|
||||
if match:
|
||||
self.ver_major = int(match.group(1))
|
||||
self.ver_minor = int(match.group(2))
|
||||
else:
|
||||
msg = _("Invalid format of client version '%s'. "
|
||||
"Expected format 'X.Y', where X is a major part and Y "
|
||||
"is a minor part of version.") % version_str
|
||||
raise exceptions.UnsupportedVersion(msg)
|
||||
|
||||
def __str__(self):
|
||||
"""Debug/Logging representation of object."""
|
||||
return ("API Version Major: %s, Minor: %s"
|
||||
% (self.ver_major, self.ver_minor))
|
||||
|
||||
def __repr__(self):
|
||||
if self.is_null():
|
||||
return "<APIVersion: null>"
|
||||
else:
|
||||
return "<APIVersion: %s>" % self.get_string()
|
||||
|
||||
def __lt__(self, other):
|
||||
if not isinstance(other, APIVersion):
|
||||
raise TypeError(self.TYPE_ERROR_MSG % {"other": other,
|
||||
"cls": self.__class__})
|
||||
|
||||
return ((self.ver_major, self.ver_minor) <
|
||||
(other.ver_major, other.ver_minor))
|
||||
|
||||
def __eq__(self, other):
|
||||
if not isinstance(other, APIVersion):
|
||||
raise TypeError(self.TYPE_ERROR_MSG % {"other": other,
|
||||
"cls": self.__class__})
|
||||
|
||||
return ((self.ver_major, self.ver_minor) ==
|
||||
(other.ver_major, other.ver_minor))
|
||||
|
||||
def __gt__(self, other):
|
||||
if not isinstance(other, APIVersion):
|
||||
raise TypeError(self.TYPE_ERROR_MSG % {"other": other,
|
||||
"cls": self.__class__})
|
||||
return ((self.ver_major, self.ver_minor) >
|
||||
(other.ver_major, other.ver_minor))
|
||||
|
||||
def __le__(self, other):
|
||||
return self < other or self == other
|
||||
|
||||
def __ne__(self, other):
|
||||
return not self.__eq__(other)
|
||||
|
||||
def __ge__(self, other):
|
||||
return self > other or self == other
|
||||
|
||||
def is_null(self):
|
||||
return self.ver_major == 0 and self.ver_minor == 0
|
||||
|
||||
def is_latest(self):
|
||||
return self == manilaclient.API_MAX_VERSION
|
||||
|
||||
def matches(self, min_version, max_version):
|
||||
"""Determines if version is within a range.
|
||||
|
||||
Returns whether the version object represents a version
|
||||
greater than or equal to the minimum version and less than
|
||||
or equal to the maximum version.
|
||||
|
||||
:param min_version: Minimum acceptable version.
|
||||
:param max_version: Maximum acceptable version.
|
||||
:returns: boolean
|
||||
|
||||
If min_version is null then there is no minimum limit.
|
||||
If max_version is null then there is no maximum limit.
|
||||
If self is null then raise ValueError
|
||||
"""
|
||||
|
||||
if self.is_null():
|
||||
raise ValueError(_("Null APIVersion doesn't support 'matches'."))
|
||||
if max_version.is_null() and min_version.is_null():
|
||||
return True
|
||||
elif max_version.is_null():
|
||||
return min_version <= self
|
||||
elif min_version.is_null():
|
||||
return self <= max_version
|
||||
else:
|
||||
return min_version <= self <= max_version
|
||||
|
||||
def get_string(self):
|
||||
"""String representation of an APIVersion object."""
|
||||
if self.is_null():
|
||||
raise ValueError(
|
||||
_("Null APIVersion cannot be converted to string."))
|
||||
return "%s.%s" % (self.ver_major, self.ver_minor)
|
||||
|
||||
def get_major_version(self):
|
||||
return "%s" % self.ver_major
|
||||
|
||||
|
||||
class VersionedMethod(object):
|
||||
|
||||
def __init__(self, name, start_version, end_version, func):
|
||||
"""Versioning information for a single method
|
||||
|
||||
:param name: Name of the method
|
||||
:param start_version: Minimum acceptable version
|
||||
:param end_version: Maximum acceptable_version
|
||||
:param func: Method to call
|
||||
|
||||
Minimum and maximums are inclusive
|
||||
"""
|
||||
self.name = name
|
||||
self.start_version = start_version
|
||||
self.end_version = end_version
|
||||
self.func = func
|
||||
|
||||
def __str__(self):
|
||||
return ("Version Method %s: min: %s, max: %s"
|
||||
% (self.name, self.start_version, self.end_version))
|
||||
|
||||
def __repr__(self):
|
||||
return "<VersionedMethod %s>" % self.name
|
||||
|
||||
|
||||
def check_version_supported(api_version):
|
||||
"""Returns True if the API version is supported.
|
||||
|
||||
:warn Sends warning if version is not supported.
|
||||
"""
|
||||
if (check_version_matches_min_max(api_version) or
|
||||
check_version_deprecated(api_version)):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def check_version_matches_min_max(api_version):
|
||||
"""Returns True if the API version is within the supported range."""
|
||||
if (not api_version.matches(
|
||||
manilaclient.API_MIN_VERSION,
|
||||
manilaclient.API_MAX_VERSION)):
|
||||
msg = _("Invalid client version '%(version)s'. "
|
||||
"Current version range is '%(min)s' through "
|
||||
" '%(max)s'") % {
|
||||
"version": api_version.get_string(),
|
||||
"min": manilaclient.API_MIN_VERSION.get_string(),
|
||||
"max": manilaclient.API_MAX_VERSION.get_string()}
|
||||
warnings.warn(msg)
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def check_version_deprecated(api_version):
|
||||
"""Returns True if API version is deprecated."""
|
||||
if api_version == manilaclient.API_DEPRECATED_VERSION:
|
||||
msg = _("Client version '%(version)s' is deprecated.") % {
|
||||
"version": api_version.get_string()}
|
||||
warnings.warn(msg)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def get_api_version(version_string):
|
||||
"""Returns checked APIVersion object."""
|
||||
version_string = str(version_string)
|
||||
|
||||
api_version = APIVersion(version_string)
|
||||
check_version_supported(api_version)
|
||||
return api_version
|
||||
|
||||
|
||||
def _get_server_version_range(client):
|
||||
"""Obtain version range from server."""
|
||||
response = client.services.server_api_version('')
|
||||
|
||||
server_version = None
|
||||
for resource in response:
|
||||
if hasattr(resource, 'version'):
|
||||
if resource.status == "CURRENT":
|
||||
server_version = resource
|
||||
break
|
||||
|
||||
if not hasattr(server_version, 'version') or not server_version.version:
|
||||
return APIVersion(), APIVersion()
|
||||
|
||||
min_version = APIVersion(server_version.min_version)
|
||||
max_version = APIVersion(server_version.version)
|
||||
|
||||
return min_version, max_version
|
||||
|
||||
|
||||
def discover_version(client, requested_version):
|
||||
"""Discovers the most recent version for client and API.
|
||||
|
||||
Checks 'requested_version' and returns the most recent version
|
||||
supported by both the API and the client.
|
||||
|
||||
:param client: client object
|
||||
:param requested_version: requested version represented by APIVersion obj
|
||||
:returns: APIVersion
|
||||
"""
|
||||
server_start_version, server_end_version = _get_server_version_range(
|
||||
client)
|
||||
|
||||
valid_version = requested_version
|
||||
if server_start_version.is_null() and server_end_version.is_null():
|
||||
LOG.warn("Server does not support microversions. Changing server "
|
||||
"version to %(min_version)s" %
|
||||
{"min_version": DEPRECATED_VERSION})
|
||||
valid_version = APIVersion(DEPRECATED_VERSION)
|
||||
else:
|
||||
_validate_requested_version(requested_version,
|
||||
server_start_version,
|
||||
server_end_version)
|
||||
|
||||
_validate_server_version(server_start_version, server_end_version)
|
||||
return valid_version
|
||||
|
||||
|
||||
def _validate_requested_version(requested_version,
|
||||
server_start_version,
|
||||
server_end_version):
|
||||
"""Validates the requested version.
|
||||
|
||||
Checks 'requested_version' is within the min/max range supported by the
|
||||
server.
|
||||
|
||||
:param requested_version: requestedversion represented by APIVersion obj
|
||||
:param server_start_version: APIVersion object representing server min
|
||||
:param server_end_version: APIVersion object representing server max
|
||||
"""
|
||||
if not requested_version.matches(server_start_version, server_end_version):
|
||||
raise exceptions.UnsupportedVersion(
|
||||
_("The specified version isn't supported by server. The valid "
|
||||
"version range is '%(min)s' to '%(max)s'") % {
|
||||
"min": server_start_version.get_string(),
|
||||
"max": server_end_version.get_string()})
|
||||
|
||||
|
||||
def _validate_server_version(server_start_version, server_end_version):
|
||||
"""Validates the server version.
|
||||
|
||||
Checks that the 'server_end_version' is greater than the minimum version
|
||||
supported by the client. Then checks that the 'server_start_version' is
|
||||
less than the maximum version supported by the client.
|
||||
|
||||
:param server_start_version:
|
||||
:param server_end_version:
|
||||
:return:
|
||||
"""
|
||||
if manilaclient.API_MIN_VERSION > server_end_version:
|
||||
raise exceptions.UnsupportedVersion(
|
||||
_("Server's version is too old. The client's valid version range "
|
||||
"is '%(client_min)s' to '%(client_max)s'. The server valid "
|
||||
"version range is '%(server_min)s' to '%(server_max)s'.") % {
|
||||
'client_min': manilaclient.API_MIN_VERSION.get_string(),
|
||||
'client_max': manilaclient.API_MAX_VERSION.get_string(),
|
||||
'server_min': server_start_version.get_string(),
|
||||
'server_max': server_end_version.get_string()})
|
||||
elif manilaclient.API_MAX_VERSION < server_start_version:
|
||||
raise exceptions.UnsupportedVersion(
|
||||
_("Server's version is too new. The client's valid version range "
|
||||
"is '%(client_min)s' to '%(client_max)s'. The server valid "
|
||||
"version range is '%(server_min)s' to '%(server_max)s'.") % {
|
||||
'client_min': manilaclient.API_MIN_VERSION.get_string(),
|
||||
'client_max': manilaclient.API_MAX_VERSION.get_string(),
|
||||
'server_min': server_start_version.get_string(),
|
||||
'server_max': server_end_version.get_string()})
|
||||
|
||||
|
||||
def add_versioned_method(versioned_method):
|
||||
_VERSIONED_METHOD_MAP.setdefault(versioned_method.name, [])
|
||||
_VERSIONED_METHOD_MAP[versioned_method.name].append(versioned_method)
|
||||
|
||||
|
||||
def get_versioned_methods(func_name, api_version=None):
|
||||
versioned_methods = _VERSIONED_METHOD_MAP.get(func_name, [])
|
||||
if api_version and not api_version.is_null():
|
||||
return [m for m in versioned_methods
|
||||
if api_version.matches(m.start_version, m.end_version)]
|
||||
return versioned_methods
|
||||
|
||||
|
||||
def experimental_api(f):
|
||||
|
@ -24,9 +339,53 @@ def experimental_api(f):
|
|||
@functools.wraps(f)
|
||||
def _wrapper(*args, **kwargs):
|
||||
client = args[0]
|
||||
if isinstance(client, manilaclient.v1.client.Client):
|
||||
if isinstance(client, manilaclient.v2.client.Client):
|
||||
dh = client.client.default_headers
|
||||
dh[constants.EXPERIMENTAL_HTTP_HEADER] = 'true'
|
||||
|
||||
return f(*args, **kwargs)
|
||||
return _wrapper
|
||||
|
||||
|
||||
def wraps(start_version, end_version=MAX_VERSION):
|
||||
"""Annotation used to return the correct method based on requested version.
|
||||
|
||||
Creates a VersionedMethod based on data from the method using the 'wraps'
|
||||
annotation. The VersionedMethod is stored in the _VERSIONED_METHOD_MAP.
|
||||
Also, adds a 'substitution' method that is used to look up the latest
|
||||
method matching the start_version.
|
||||
|
||||
:param start_version: String obj representing first supported version.
|
||||
:param end_version: String obj representing last supported version.
|
||||
"""
|
||||
start_version = APIVersion(start_version)
|
||||
end_version = APIVersion(end_version)
|
||||
|
||||
def decor(func):
|
||||
func.versioned = True
|
||||
name = utils.get_function_name(func)
|
||||
versioned_method = VersionedMethod(name, start_version,
|
||||
end_version, func)
|
||||
add_versioned_method(versioned_method)
|
||||
|
||||
@functools.wraps(func)
|
||||
def substitution(obj, *args, **kwargs):
|
||||
methods = get_versioned_methods(name, obj.api_version)
|
||||
|
||||
if not methods:
|
||||
raise exceptions.UnsupportedVersion(
|
||||
_("API version '%(version)s' is not supported on "
|
||||
"'%(method)s' method.") % {
|
||||
"version": obj.api_version.get_string(),
|
||||
"method": name,
|
||||
})
|
||||
|
||||
method = max(methods, key=lambda f: f.start_version)
|
||||
|
||||
return method.func(obj, *args, **kwargs)
|
||||
|
||||
if hasattr(func, 'arguments'):
|
||||
for cli_args, cli_kwargs in func.arguments:
|
||||
cliutils.add_arg(substitution, *cli_args, **cli_kwargs)
|
||||
return substitution
|
||||
|
||||
return decor
|
||||
|
|
|
@ -47,6 +47,10 @@ class Manager(utils.HookableMixin):
|
|||
def __init__(self, api):
|
||||
self.api = api
|
||||
|
||||
@property
|
||||
def api_version(self):
|
||||
return self.api.api_version
|
||||
|
||||
def _list(self, url, response_key, obj_class=None, body=None):
|
||||
resp = None
|
||||
if body:
|
||||
|
|
|
@ -28,7 +28,7 @@ from manilaclient import exceptions
|
|||
def get_client_class(version):
|
||||
version_map = {
|
||||
'1': 'manilaclient.v1.client.Client',
|
||||
'2': 'manilaclient.v1.client.Client',
|
||||
'2': 'manilaclient.v2.client.Client',
|
||||
}
|
||||
try:
|
||||
client_path = version_map[str(version)]
|
||||
|
@ -40,10 +40,7 @@ def get_client_class(version):
|
|||
return importutils.import_class(client_path)
|
||||
|
||||
|
||||
def get_major_version(version):
|
||||
return version.split('.')[0]
|
||||
|
||||
|
||||
def Client(version, *args, **kwargs):
|
||||
client_class = get_client_class(get_major_version(version))
|
||||
def Client(api_version, *args, **kwargs):
|
||||
client_class = get_client_class(api_version.get_major_version())
|
||||
kwargs['api_version'] = api_version
|
||||
return client_class(*args, **kwargs)
|
||||
|
|
|
@ -74,7 +74,7 @@ CG_SNAPSHOT_MEMBERS_SORT_KEY_VALUES = (
|
|||
)
|
||||
|
||||
EXPERIMENTAL_HTTP_HEADER = 'X-OpenStack-Manila-API-Experimental'
|
||||
MAX_API_VERSION = '2.6'
|
||||
V1_API_VERSION = '1.0'
|
||||
V1_SERVICE_TYPE = 'share'
|
||||
V2_SERVICE_TYPE = 'sharev2'
|
||||
|
||||
SERVICE_TYPES = {'1': V1_SERVICE_TYPE, '2': V2_SERVICE_TYPE}
|
||||
|
|
|
@ -33,6 +33,15 @@ except ImportError:
|
|||
|
||||
|
||||
class HTTPClient(object):
|
||||
"""HTTP Client class used by multiple clients.
|
||||
|
||||
The imported Requests module caches and reuses objects with the same
|
||||
destination. To avoid the problem of sending duplicate requests it is
|
||||
necessary that the Requests module is only imported once during client
|
||||
execution. This class is shared by multiple client versions so that the
|
||||
client can be changed to another version during execution.
|
||||
"""
|
||||
API_VERSION_HEADER = "X-Openstack-Manila-Api-Version"
|
||||
|
||||
def __init__(self, endpoint_url, token, user_agent, api_version,
|
||||
insecure=False, cacert=None, timeout=None, retries=None,
|
||||
|
@ -47,18 +56,26 @@ class HTTPClient(object):
|
|||
|
||||
self.default_headers = {
|
||||
'X-Auth-Token': token,
|
||||
'X-Openstack-Manila-Api-Version': api_version,
|
||||
self.API_VERSION_HEADER: api_version.get_string(),
|
||||
'User-Agent': user_agent,
|
||||
'Accept': 'application/json',
|
||||
}
|
||||
|
||||
self._add_log_handlers(http_log_debug)
|
||||
|
||||
def _add_log_handlers(self, http_log_debug):
|
||||
self._logger = logging.getLogger(__name__)
|
||||
if self.http_log_debug:
|
||||
|
||||
# check that handler hasn't already been added
|
||||
if http_log_debug and not self._logger.handlers:
|
||||
ch = logging.StreamHandler()
|
||||
ch._name = 'http_client_handler'
|
||||
self._logger.setLevel(logging.DEBUG)
|
||||
self._logger.addHandler(ch)
|
||||
|
||||
if hasattr(requests, 'logging'):
|
||||
requests.logging.getLogger(requests.__name__).addHandler(ch)
|
||||
rql = requests.logging.getLogger(requests.__name__)
|
||||
rql.addHandler(ch)
|
||||
|
||||
def _get_base_url(self, url):
|
||||
"""Truncates url and returns transport, address, and port number."""
|
|
@ -27,3 +27,14 @@ class NoTokenLookupException(ClientException):
|
|||
endpoints from an existing token.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class VersionNotFoundForAPIMethod(Exception):
|
||||
msg_fmt = "API version '%(vers)s' is not supported on '%(method)s' method."
|
||||
|
||||
def __init__(self, version, method):
|
||||
self.version = version
|
||||
self.method = method
|
||||
|
||||
def __str__(self):
|
||||
return self.msg_fmt % {"vers": self.version, "method": self.method}
|
||||
|
|
|
@ -32,16 +32,19 @@ import sys
|
|||
from oslo_utils import encodeutils
|
||||
import six
|
||||
|
||||
from manilaclient import api_versions
|
||||
from manilaclient import client
|
||||
from manilaclient.common import constants
|
||||
from manilaclient import exceptions as exc
|
||||
import manilaclient.extension
|
||||
from manilaclient.openstack.common import cliutils
|
||||
from manilaclient.v1 import shell as shell_v1
|
||||
from manilaclient.v2 import shell as shell_v2
|
||||
|
||||
DEFAULT_OS_SHARE_API_VERSION = constants.MAX_API_VERSION
|
||||
DEFAULT_OS_SHARE_API_VERSION = api_versions.MAX_VERSION
|
||||
DEFAULT_MANILA_ENDPOINT_TYPE = 'publicURL'
|
||||
DEFAULT_MANILA_SERVICE_TYPE = constants.V2_SERVICE_TYPE
|
||||
V1_MAJOR_VERSION = '1'
|
||||
V2_MAJOR_VERSION = '2'
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -320,10 +323,10 @@ class OpenStackManilaShell(object):
|
|||
|
||||
try:
|
||||
actions_module = {
|
||||
'1.1': shell_v1,
|
||||
V2_MAJOR_VERSION: shell_v2,
|
||||
}[version]
|
||||
except KeyError:
|
||||
actions_module = shell_v1
|
||||
actions_module = shell_v2
|
||||
|
||||
self._find_actions(subparsers, actions_module)
|
||||
self._find_actions(subparsers, self)
|
||||
|
@ -335,18 +338,18 @@ class OpenStackManilaShell(object):
|
|||
|
||||
return parser
|
||||
|
||||
def _discover_extensions(self, version):
|
||||
def _discover_extensions(self, api_version):
|
||||
extensions = []
|
||||
for name, module in itertools.chain(
|
||||
self._discover_via_python_path(version),
|
||||
self._discover_via_contrib_path(version)):
|
||||
self._discover_via_python_path(),
|
||||
self._discover_via_contrib_path(api_version)):
|
||||
|
||||
extension = manilaclient.extension.Extension(name, module)
|
||||
extensions.append(extension)
|
||||
|
||||
return extensions
|
||||
|
||||
def _discover_via_python_path(self, version):
|
||||
def _discover_via_python_path(self):
|
||||
for (module_loader, name, ispkg) in pkgutil.iter_modules():
|
||||
if name.endswith('python_manilaclient_ext'):
|
||||
if not hasattr(module_loader, 'load_module'):
|
||||
|
@ -356,11 +359,9 @@ class OpenStackManilaShell(object):
|
|||
module = module_loader.load_module(name)
|
||||
yield name, module
|
||||
|
||||
def _discover_via_contrib_path(self, version):
|
||||
def _discover_via_contrib_path(self, api_version):
|
||||
module_path = os.path.dirname(os.path.abspath(__file__))
|
||||
# NOTE(cfouts) - temporary change to maintain other clients that are
|
||||
# hardcoded to use the v1/client.py
|
||||
version_str = 'v1'
|
||||
version_str = 'v' + api_version.get_major_version()
|
||||
ext_path = os.path.join(module_path, version_str, 'contrib')
|
||||
ext_glob = os.path.join(ext_path, "*.py")
|
||||
|
||||
|
@ -417,28 +418,41 @@ class OpenStackManilaShell(object):
|
|||
logger.setLevel(logging.DEBUG)
|
||||
logger.addHandler(streamhandler)
|
||||
|
||||
def _build_subcommands_and_extensions(self,
|
||||
os_api_version,
|
||||
argv,
|
||||
options):
|
||||
|
||||
self.extensions = self._discover_extensions(os_api_version)
|
||||
self._run_extension_hooks('__pre_parse_args__')
|
||||
|
||||
self.parser = self.get_subcommand_parser(
|
||||
os_api_version.get_major_version())
|
||||
|
||||
if options.help or not argv:
|
||||
self.parser.print_help()
|
||||
return False
|
||||
|
||||
args = self.parser.parse_args(argv)
|
||||
self._run_extension_hooks('__post_parse_args__', args)
|
||||
|
||||
return args
|
||||
|
||||
def main(self, argv):
|
||||
# Parse args once to find version and debug settings
|
||||
parser = self.get_base_parser()
|
||||
(options, args) = parser.parse_known_args(argv)
|
||||
self.setup_debugging(options.debug)
|
||||
|
||||
os_api_version = self._validate_input_api_version(options)
|
||||
|
||||
# build available subcommands based on version
|
||||
self.extensions = self._discover_extensions(
|
||||
options.os_share_api_version)
|
||||
self._run_extension_hooks('__pre_parse_args__')
|
||||
|
||||
subcommand_parser = self.get_subcommand_parser(
|
||||
options.os_share_api_version)
|
||||
self.parser = subcommand_parser
|
||||
|
||||
if options.help or not argv:
|
||||
subcommand_parser.print_help()
|
||||
args = self._build_subcommands_and_extensions(os_api_version,
|
||||
argv,
|
||||
options)
|
||||
if not args:
|
||||
return 0
|
||||
|
||||
args = subcommand_parser.parse_args(argv)
|
||||
self._run_extension_hooks('__post_parse_args__', args)
|
||||
|
||||
# Short-circuit and deal with help right away.
|
||||
if args.func == self.do_help:
|
||||
self.do_help(args)
|
||||
|
@ -447,31 +461,113 @@ class OpenStackManilaShell(object):
|
|||
self.do_bash_completion(args)
|
||||
return 0
|
||||
|
||||
(os_username, os_password, os_tenant_name, os_auth_url,
|
||||
os_region_name, os_tenant_id, endpoint_type, insecure,
|
||||
service_type, service_name, share_service_name,
|
||||
cacert, os_cache, os_reset_cache, os_user_id, os_user_domain_id,
|
||||
os_user_domain_name, os_project_domain_id, os_project_domain_name,
|
||||
os_project_name, os_project_id, os_cert) = (
|
||||
args.os_username, args.os_password, args.os_tenant_name,
|
||||
args.os_auth_url, args.os_region_name, args.os_tenant_id,
|
||||
args.endpoint_type, args.insecure, args.service_type,
|
||||
args.service_name, args.share_service_name,
|
||||
args.os_cacert, args.os_cache, args.os_reset_cache,
|
||||
args.os_user_id, args.os_user_domain_id, args.os_user_domain_name,
|
||||
args.os_project_domain_id, args.os_project_domain_name,
|
||||
args.os_project_name, args.os_project_id, args.os_cert,
|
||||
os_service_type = args.service_type
|
||||
os_endpoint_type = args.endpoint_type
|
||||
|
||||
client_args = dict(
|
||||
username=args.os_username,
|
||||
password=args.os_password,
|
||||
project_name=args.os_project_name or args.os_tenant_name,
|
||||
auth_url=args.os_auth_url,
|
||||
insecure=args.insecure,
|
||||
region_name=args.os_region_name,
|
||||
tenant_id=args.os_project_id or args.os_tenant_id,
|
||||
endpoint_type=DEFAULT_MANILA_ENDPOINT_TYPE,
|
||||
extensions=self.extensions,
|
||||
service_type=constants.V1_SERVICE_TYPE,
|
||||
service_name=args.service_name,
|
||||
retries=options.retries,
|
||||
http_log_debug=args.debug,
|
||||
cacert=args.os_cacert,
|
||||
use_keyring=args.os_cache,
|
||||
force_new_token=args.os_reset_cache,
|
||||
user_id=args.os_user_id,
|
||||
user_domain_id=args.os_user_domain_id,
|
||||
user_domain_name=args.os_user_domain_name,
|
||||
project_domain_id=args.os_project_domain_id,
|
||||
project_domain_name=args.os_project_domain_name,
|
||||
cert=args.os_cert,
|
||||
)
|
||||
|
||||
if share_service_name:
|
||||
service_name = share_service_name
|
||||
# Handle deprecated parameters
|
||||
if args.share_service_name:
|
||||
client_args['share_service_name'] = args.share_service_name
|
||||
|
||||
if not endpoint_type:
|
||||
endpoint_type = DEFAULT_MANILA_ENDPOINT_TYPE
|
||||
self._validate_required_options(args.os_tenant_name,
|
||||
args.os_tenant_id,
|
||||
args.os_project_name,
|
||||
args.os_project_id,
|
||||
client_args['auth_url'])
|
||||
|
||||
if not service_type:
|
||||
service_type = DEFAULT_MANILA_SERVICE_TYPE
|
||||
service_type = cliutils.get_service_type(args.func) or service_type
|
||||
# This client is needed to discover the server api version.
|
||||
temp_client = client.Client(manilaclient.API_MAX_VERSION,
|
||||
**client_args)
|
||||
|
||||
self.cs, discovered_version = self._discover_client(temp_client,
|
||||
os_api_version,
|
||||
os_endpoint_type,
|
||||
os_service_type,
|
||||
client_args)
|
||||
|
||||
args = self._build_subcommands_and_extensions(discovered_version,
|
||||
argv,
|
||||
options)
|
||||
|
||||
args.func(self.cs, args)
|
||||
|
||||
def _discover_client(self,
|
||||
current_client,
|
||||
os_api_version,
|
||||
os_endpoint_type,
|
||||
os_service_type,
|
||||
client_args):
|
||||
|
||||
if os_api_version == manilaclient.API_DEPRECATED_VERSION:
|
||||
discovered_version = manilaclient.API_DEPRECATED_VERSION
|
||||
os_service_type = constants.V1_SERVICE_TYPE
|
||||
else:
|
||||
discovered_version = api_versions.discover_version(
|
||||
current_client,
|
||||
os_api_version
|
||||
)
|
||||
|
||||
if not os_endpoint_type:
|
||||
os_endpoint_type = DEFAULT_MANILA_ENDPOINT_TYPE
|
||||
|
||||
if not os_service_type:
|
||||
os_service_type = self._discover_service_type(discovered_version)
|
||||
|
||||
if (discovered_version != manilaclient.API_MAX_VERSION or
|
||||
os_service_type != constants.V1_SERVICE_TYPE or
|
||||
os_endpoint_type != DEFAULT_MANILA_ENDPOINT_TYPE):
|
||||
client_args['version'] = discovered_version
|
||||
client_args['service_type'] = os_service_type
|
||||
client_args['endpoint_type'] = os_endpoint_type
|
||||
|
||||
return (client.Client(discovered_version, **client_args),
|
||||
discovered_version)
|
||||
else:
|
||||
return current_client, discovered_version
|
||||
|
||||
def _discover_service_type(self, discovered_version):
|
||||
major_version = discovered_version.get_major_version()
|
||||
service_type = constants.SERVICE_TYPES[major_version]
|
||||
return service_type
|
||||
|
||||
def _validate_input_api_version(self, options):
|
||||
if not options.os_share_api_version:
|
||||
api_version = manilaclient.API_MAX_VERSION
|
||||
else:
|
||||
api_version = api_versions.get_api_version(
|
||||
options.os_share_api_version)
|
||||
return api_version
|
||||
|
||||
def _validate_required_options(self,
|
||||
os_tenant_name,
|
||||
os_tenant_id,
|
||||
os_project_name,
|
||||
os_project_id,
|
||||
os_auth_url):
|
||||
|
||||
if not (os_tenant_name or os_tenant_id or os_project_name or
|
||||
os_project_id):
|
||||
|
@ -492,33 +588,6 @@ class OpenStackManilaShell(object):
|
|||
"You must provide an auth url "
|
||||
"via either --os-auth-url or env[OS_AUTH_URL]")
|
||||
|
||||
self.cs = client.Client(options.os_share_api_version,
|
||||
username=os_username,
|
||||
password=os_password,
|
||||
project_name=os_project_name or os_tenant_name,
|
||||
auth_url=os_auth_url,
|
||||
insecure=insecure,
|
||||
region_name=os_region_name,
|
||||
tenant_id=os_project_id or os_tenant_id,
|
||||
endpoint_type=endpoint_type,
|
||||
extensions=self.extensions,
|
||||
service_type=service_type,
|
||||
service_name=service_name,
|
||||
retries=options.retries,
|
||||
http_log_debug=args.debug,
|
||||
cacert=cacert,
|
||||
use_keyring=os_cache,
|
||||
force_new_token=os_reset_cache,
|
||||
api_version=options.os_share_api_version,
|
||||
user_id=os_user_id,
|
||||
user_domain_id=os_user_domain_id,
|
||||
user_domain_name=os_user_domain_name,
|
||||
project_domain_id=os_project_domain_id,
|
||||
project_domain_name=os_project_domain_name,
|
||||
cert=os_cert)
|
||||
|
||||
args.func(self.cs, args)
|
||||
|
||||
def _run_extension_hooks(self, hook_type, *args, **kwargs):
|
||||
"""Run hooks for all registered extensions."""
|
||||
for extension in self.extensions:
|
||||
|
|
|
@ -13,9 +13,9 @@
|
|||
import mock
|
||||
import requests
|
||||
|
||||
from manilaclient.common import constants
|
||||
import manilaclient
|
||||
from manilaclient.common import httpclient
|
||||
from manilaclient import exceptions
|
||||
from manilaclient import httpclient
|
||||
from manilaclient.tests.unit import utils
|
||||
|
||||
fake_user_agent = "fake"
|
||||
|
@ -74,12 +74,17 @@ retry_after_non_supporting_mock_request = mock.Mock(
|
|||
def get_authed_client(retries=0):
|
||||
cl = httpclient.HTTPClient("http://example.com", "token", fake_user_agent,
|
||||
retries=retries, http_log_debug=True,
|
||||
api_version=constants.MAX_API_VERSION)
|
||||
api_version=manilaclient.API_MAX_VERSION)
|
||||
return cl
|
||||
|
||||
|
||||
class ClientTest(utils.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(ClientTest, self).setUp()
|
||||
self.max_version = manilaclient.API_MAX_VERSION
|
||||
self.max_version_str = self.max_version.get_string()
|
||||
|
||||
def test_get(self):
|
||||
cl = get_authed_client()
|
||||
|
||||
|
@ -90,7 +95,7 @@ class ClientTest(utils.TestCase):
|
|||
headers = {
|
||||
"X-Auth-Token": "token",
|
||||
"User-Agent": fake_user_agent,
|
||||
"X-Openstack-Manila-Api-Version": constants.MAX_API_VERSION,
|
||||
cl.API_VERSION_HEADER: self.max_version_str,
|
||||
'Accept': 'application/json',
|
||||
}
|
||||
mock_request.assert_called_with(
|
||||
|
@ -181,7 +186,7 @@ class ClientTest(utils.TestCase):
|
|||
"X-Auth-Token": "token",
|
||||
"Content-Type": "application/json",
|
||||
'Accept': 'application/json',
|
||||
"X-Openstack-Manila-Api-Version": constants.MAX_API_VERSION,
|
||||
"X-Openstack-Manila-Api-Version": self.max_version_str,
|
||||
"User-Agent": fake_user_agent
|
||||
}
|
||||
mock_request.assert_called_with(
|
|
@ -0,0 +1,328 @@
|
|||
# Copyright 2015 Chuck Fouts
|
||||
#
|
||||
# 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 ddt
|
||||
import mock
|
||||
|
||||
import manilaclient
|
||||
from manilaclient import api_versions
|
||||
from manilaclient import exceptions
|
||||
from manilaclient.openstack.common import cliutils
|
||||
from manilaclient.tests.unit import utils
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class APIVersionTestCase(utils.TestCase):
|
||||
def test_valid_version_strings(self):
|
||||
def _test_string(version, exp_major, exp_minor):
|
||||
v = api_versions.APIVersion(version)
|
||||
self.assertEqual(v.ver_major, exp_major)
|
||||
self.assertEqual(v.ver_minor, exp_minor)
|
||||
|
||||
_test_string("1.1", 1, 1)
|
||||
_test_string("2.10", 2, 10)
|
||||
_test_string("5.234", 5, 234)
|
||||
_test_string("12.5", 12, 5)
|
||||
_test_string("2.0", 2, 0)
|
||||
_test_string("2.200", 2, 200)
|
||||
|
||||
def test_null_version(self):
|
||||
v = api_versions.APIVersion()
|
||||
self.assertTrue(v.is_null())
|
||||
|
||||
@ddt.data(
|
||||
"2",
|
||||
"200",
|
||||
"2.1.4",
|
||||
"200.23.66.3",
|
||||
"5 .3",
|
||||
"5. 3",
|
||||
"5.03",
|
||||
"02.1",
|
||||
"2.001",
|
||||
"",
|
||||
" 2.1",
|
||||
"2.1 ",
|
||||
)
|
||||
def test_invalid_version_strings(self, version):
|
||||
self.assertRaises(exceptions.UnsupportedVersion,
|
||||
api_versions.APIVersion, version)
|
||||
|
||||
def test_version_comparisons(self):
|
||||
v1 = api_versions.APIVersion("2.0")
|
||||
v2 = api_versions.APIVersion("2.5")
|
||||
v3 = api_versions.APIVersion("5.23")
|
||||
v4 = api_versions.APIVersion("2.0")
|
||||
v5 = api_versions.APIVersion("1.0")
|
||||
v_null = api_versions.APIVersion()
|
||||
|
||||
self.assertTrue(v1 < v2)
|
||||
self.assertTrue(v3 > v2)
|
||||
self.assertTrue(v1 != v2)
|
||||
self.assertTrue(v1 == v4)
|
||||
self.assertTrue(v1 != v_null)
|
||||
self.assertTrue(v5 < v1)
|
||||
self.assertTrue(v5 < v2)
|
||||
self.assertTrue(v_null == v_null)
|
||||
self.assertRaises(TypeError, v1.__le__, "2.1")
|
||||
|
||||
def test_version_matches(self):
|
||||
v1 = api_versions.APIVersion("2.0")
|
||||
v2 = api_versions.APIVersion("2.5")
|
||||
v3 = api_versions.APIVersion("2.45")
|
||||
v4 = api_versions.APIVersion("3.3")
|
||||
v5 = api_versions.APIVersion("3.23")
|
||||
v6 = api_versions.APIVersion("2.0")
|
||||
v7 = api_versions.APIVersion("3.3")
|
||||
v8 = api_versions.APIVersion("4.0")
|
||||
v_null = api_versions.APIVersion()
|
||||
|
||||
self.assertTrue(v2.matches(v1, v3))
|
||||
self.assertTrue(v2.matches(v1, v_null))
|
||||
self.assertTrue(v1.matches(v6, v2))
|
||||
self.assertTrue(v4.matches(v2, v7))
|
||||
self.assertTrue(v4.matches(v_null, v7))
|
||||
self.assertTrue(v4.matches(v_null, v8))
|
||||
self.assertFalse(v1.matches(v2, v3))
|
||||
self.assertFalse(v5.matches(v2, v4))
|
||||
self.assertFalse(v2.matches(v3, v1))
|
||||
|
||||
self.assertRaises(ValueError, v_null.matches, v1, v3)
|
||||
|
||||
def test_get_string(self):
|
||||
v1_string = "3.23"
|
||||
v1 = api_versions.APIVersion(v1_string)
|
||||
self.assertEqual(v1_string, v1.get_string())
|
||||
|
||||
self.assertRaises(ValueError,
|
||||
api_versions.APIVersion().get_string)
|
||||
|
||||
|
||||
class GetAPIVersionTestCase(utils.TestCase):
|
||||
|
||||
def test_wrong_format(self):
|
||||
self.assertRaises(exceptions.UnsupportedVersion,
|
||||
api_versions.get_api_version, "something_wrong")
|
||||
|
||||
def test_wrong_major_version(self):
|
||||
self.assertRaises(exceptions.UnsupportedVersion,
|
||||
api_versions.get_api_version, "1")
|
||||
|
||||
@mock.patch("manilaclient.api_versions.APIVersion")
|
||||
def test_major_and_minor_parts_is_presented(self, mock_apiversion):
|
||||
version = "2.7"
|
||||
self.assertEqual(mock_apiversion.return_value,
|
||||
api_versions.get_api_version(version))
|
||||
mock_apiversion.assert_called_once_with(version)
|
||||
|
||||
|
||||
class WrapsTestCase(utils.TestCase):
|
||||
|
||||
def _get_obj_with_vers(self, vers):
|
||||
return mock.MagicMock(api_version=api_versions.APIVersion(vers))
|
||||
|
||||
def _side_effect_of_vers_method(self, *args, **kwargs):
|
||||
m = mock.MagicMock(start_version=args[1], end_version=args[2])
|
||||
m.name = args[0]
|
||||
return m
|
||||
|
||||
@mock.patch("manilaclient.utils.get_function_name")
|
||||
@mock.patch("manilaclient.api_versions.VersionedMethod")
|
||||
def test_end_version_is_none(self, mock_versioned_method, mock_name):
|
||||
func_name = 'foo'
|
||||
mock_name.return_value = func_name
|
||||
mock_versioned_method.side_effect = self._side_effect_of_vers_method
|
||||
|
||||
@api_versions.wraps('2.2')
|
||||
def foo(*args, **kwargs):
|
||||
pass
|
||||
|
||||
foo(self._get_obj_with_vers('2.4'))
|
||||
|
||||
mock_versioned_method.assert_called_once_with(
|
||||
func_name, api_versions.APIVersion('2.2'),
|
||||
api_versions.APIVersion(api_versions.MAX_VERSION), mock.ANY)
|
||||
|
||||
@mock.patch("manilaclient.utils.get_function_name")
|
||||
@mock.patch("manilaclient.api_versions.VersionedMethod")
|
||||
def test_start_and_end_version_are_presented(self, mock_versioned_method,
|
||||
mock_name):
|
||||
func_name = "foo"
|
||||
mock_name.return_value = func_name
|
||||
mock_versioned_method.side_effect = self._side_effect_of_vers_method
|
||||
|
||||
@api_versions.wraps("2.2", "2.6")
|
||||
def foo(*args, **kwargs):
|
||||
pass
|
||||
|
||||
foo(self._get_obj_with_vers("2.4"))
|
||||
|
||||
mock_versioned_method.assert_called_once_with(
|
||||
func_name, api_versions.APIVersion("2.2"),
|
||||
api_versions.APIVersion("2.6"), mock.ANY)
|
||||
|
||||
@mock.patch("manilaclient.utils.get_function_name")
|
||||
@mock.patch("manilaclient.api_versions.VersionedMethod")
|
||||
def test_api_version_doesnt_match(self, mock_versioned_method, mock_name):
|
||||
func_name = "foo"
|
||||
mock_name.return_value = func_name
|
||||
mock_versioned_method.side_effect = self._side_effect_of_vers_method
|
||||
|
||||
@api_versions.wraps("2.2", "2.6")
|
||||
def foo(*args, **kwargs):
|
||||
pass
|
||||
|
||||
self.assertRaises(exceptions.UnsupportedVersion,
|
||||
foo, self._get_obj_with_vers("2.1"))
|
||||
|
||||
mock_versioned_method.assert_called_once_with(
|
||||
func_name, api_versions.APIVersion("2.2"),
|
||||
api_versions.APIVersion("2.6"), mock.ANY)
|
||||
|
||||
def test_define_method_is_actually_called(self):
|
||||
checker = mock.MagicMock()
|
||||
|
||||
@api_versions.wraps("2.2", "2.6")
|
||||
def some_func(*args, **kwargs):
|
||||
checker(*args, **kwargs)
|
||||
|
||||
obj = self._get_obj_with_vers("2.4")
|
||||
some_args = ("arg_1", "arg_2")
|
||||
some_kwargs = {"key1": "value1", "key2": "value2"}
|
||||
|
||||
some_func(obj, *some_args, **some_kwargs)
|
||||
|
||||
checker.assert_called_once_with(*((obj,) + some_args), **some_kwargs)
|
||||
|
||||
def test_cli_args_are_copied(self):
|
||||
|
||||
@api_versions.wraps("2.2", "2.6")
|
||||
@cliutils.arg("name_1", help="Name of the something")
|
||||
@cliutils.arg("action_1", help="Some action")
|
||||
def some_func_1(cs, args):
|
||||
pass
|
||||
|
||||
@cliutils.arg("name_2", help="Name of the something")
|
||||
@cliutils.arg("action_2", help="Some action")
|
||||
@api_versions.wraps("2.2", "2.6")
|
||||
def some_func_2(cs, args):
|
||||
pass
|
||||
|
||||
args_1 = [(('name_1',), {'help': 'Name of the something'}),
|
||||
(('action_1',), {'help': 'Some action'})]
|
||||
self.assertEqual(args_1, some_func_1.arguments)
|
||||
|
||||
args_2 = [(('name_2',), {'help': 'Name of the something'}),
|
||||
(('action_2',), {'help': 'Some action'})]
|
||||
self.assertEqual(args_2, some_func_2.arguments)
|
||||
|
||||
|
||||
class DiscoverVersionTestCase(utils.TestCase):
|
||||
def setUp(self):
|
||||
super(DiscoverVersionTestCase, self).setUp()
|
||||
self.orig_max = manilaclient.API_MAX_VERSION
|
||||
self.orig_min = manilaclient.API_MIN_VERSION
|
||||
self.addCleanup(self._clear_fake_version)
|
||||
self.fake_client = mock.MagicMock()
|
||||
|
||||
def _clear_fake_version(self):
|
||||
manilaclient.API_MAX_VERSION = self.orig_max
|
||||
manilaclient.API_MIN_VERSION = self.orig_min
|
||||
|
||||
def _mock_returned_server_version(self, server_version,
|
||||
server_min_version):
|
||||
version_mock = mock.MagicMock(version=server_version,
|
||||
min_version=server_min_version,
|
||||
status='CURRENT')
|
||||
val = [version_mock]
|
||||
self.fake_client.services.server_api_version.return_value = val
|
||||
|
||||
def test_server_is_too_new(self):
|
||||
self._mock_returned_server_version('2.7', '2.4')
|
||||
manilaclient.API_MAX_VERSION = api_versions.APIVersion("2.3")
|
||||
manilaclient.API_MIN_VERSION = api_versions.APIVersion("2.1")
|
||||
|
||||
self.assertRaisesRegexp(exceptions.UnsupportedVersion,
|
||||
".*range is '2.4' to '2.7'.*",
|
||||
api_versions.discover_version,
|
||||
self.fake_client,
|
||||
api_versions.APIVersion("2.3"))
|
||||
self.assertTrue(self.fake_client.services.server_api_version.called)
|
||||
|
||||
def test_server_is_too_old(self):
|
||||
self._mock_returned_server_version('2.2', '2.0')
|
||||
manilaclient.API_MAX_VERSION = api_versions.APIVersion("2.10")
|
||||
manilaclient.API_MIN_VERSION = api_versions.APIVersion("2.9")
|
||||
|
||||
self.assertRaises(exceptions.UnsupportedVersion,
|
||||
api_versions.discover_version,
|
||||
self.fake_client,
|
||||
api_versions.APIVersion("2.10"))
|
||||
self.assertTrue(self.fake_client.services.server_api_version.called)
|
||||
|
||||
def test_server_and_client_max_are_same(self):
|
||||
self._mock_returned_server_version('2.5', '2.0')
|
||||
manilaclient.API_MAX_VERSION = api_versions.APIVersion("2.5")
|
||||
manilaclient.API_MIN_VERSION = api_versions.APIVersion("2.5")
|
||||
|
||||
discovered_version = api_versions.discover_version(
|
||||
self.fake_client,
|
||||
manilaclient.API_MAX_VERSION)
|
||||
self.assertEqual("2.5", discovered_version.get_string())
|
||||
self.assertTrue(self.fake_client.services.server_api_version.called)
|
||||
|
||||
def test_pre_microversion_server(self):
|
||||
self.fake_client.services.server_api_version.return_value = []
|
||||
manilaclient.API_MAX_VERSION = api_versions.APIVersion("2.5")
|
||||
manilaclient.API_MIN_VERSION = api_versions.APIVersion("2.5")
|
||||
discovered_version = api_versions.discover_version(
|
||||
self.fake_client,
|
||||
manilaclient.API_MAX_VERSION)
|
||||
self.assertEqual("1.0", discovered_version.get_string())
|
||||
self.assertTrue(self.fake_client.services.server_api_version.called)
|
||||
|
||||
def test_requested_version_in_range(self):
|
||||
self._mock_returned_server_version('2.7', '2.4')
|
||||
manilaclient.API_MAX_VERSION = api_versions.APIVersion("2.11")
|
||||
manilaclient.API_MIN_VERSION = api_versions.APIVersion("2.1")
|
||||
|
||||
discovered_version = api_versions.discover_version(
|
||||
self.fake_client,
|
||||
api_versions.APIVersion('2.7'))
|
||||
self.assertEqual('2.7', discovered_version.get_string())
|
||||
self.assertTrue(self.fake_client.services.server_api_version.called)
|
||||
|
||||
def test_server_without_microversion(self):
|
||||
self._mock_returned_server_version(None, None)
|
||||
manilaclient.API_MAX_VERSION = api_versions.APIVersion("2.11")
|
||||
manilaclient.API_MIN_VERSION = api_versions.APIVersion("2.1")
|
||||
|
||||
discovered_version = api_versions.discover_version(
|
||||
self.fake_client,
|
||||
api_versions.APIVersion('2.7'))
|
||||
self.assertEqual(api_versions.DEPRECATED_VERSION,
|
||||
discovered_version.get_string())
|
||||
|
||||
self.assertTrue(self.fake_client.services.server_api_version.called)
|
||||
|
||||
def test_requested_version_is_too_old(self):
|
||||
self._mock_returned_server_version('2.5', '2.0')
|
||||
manilaclient.API_MAX_VERSION = api_versions.APIVersion("2.5")
|
||||
manilaclient.API_MIN_VERSION = api_versions.APIVersion("2.5")
|
||||
|
||||
self.assertRaisesRegexp(exceptions.UnsupportedVersion,
|
||||
".*range is '2.0' to '2.5'.*",
|
||||
api_versions.discover_version,
|
||||
self.fake_client,
|
||||
api_versions.APIVersion("1.0"))
|
|
@ -13,8 +13,8 @@
|
|||
from manilaclient import exceptions
|
||||
from manilaclient.openstack.common.apiclient import base as common_base
|
||||
from manilaclient.tests.unit import utils
|
||||
from manilaclient.tests.unit.v1 import fakes
|
||||
from manilaclient.v1 import shares
|
||||
from manilaclient.tests.unit.v2 import fakes
|
||||
from manilaclient.v2 import shares
|
||||
|
||||
|
||||
cs = fakes.FakeClient()
|
||||
|
|
|
@ -12,14 +12,14 @@
|
|||
|
||||
import manilaclient.client
|
||||
from manilaclient.tests.unit import utils
|
||||
import manilaclient.v1.client
|
||||
import manilaclient.v2.client
|
||||
|
||||
|
||||
class ClientTest(utils.TestCase):
|
||||
|
||||
def test_get_client_class_v1(self):
|
||||
def test_get_client_class_v2(self):
|
||||
output = manilaclient.client.get_client_class('2')
|
||||
self.assertEqual(output, manilaclient.v1.client.Client)
|
||||
self.assertEqual(output, manilaclient.v2.client.Client)
|
||||
|
||||
def test_get_client_class_unknown(self):
|
||||
self.assertRaises(manilaclient.exceptions.UnsupportedVersion,
|
||||
|
|
|
@ -19,6 +19,7 @@ import mock
|
|||
from six import moves
|
||||
from testtools import matchers
|
||||
|
||||
import manilaclient
|
||||
from manilaclient.common import constants
|
||||
from manilaclient import exceptions
|
||||
from manilaclient import shell
|
||||
|
@ -40,11 +41,20 @@ class OpenstackManilaShellTest(utils.TestCase):
|
|||
for k, v in env_vars.items():
|
||||
self.useFixture(fixtures.EnvironmentVariable(k, v))
|
||||
|
||||
def shell_discover_client(self,
|
||||
current_client,
|
||||
os_api_version,
|
||||
os_endpoint_type,
|
||||
os_service_type,
|
||||
client_args):
|
||||
return current_client, manilaclient.API_MAX_VERSION
|
||||
|
||||
def shell(self, argstr):
|
||||
orig = sys.stdout
|
||||
try:
|
||||
sys.stdout = moves.StringIO()
|
||||
_shell = shell.OpenStackManilaShell()
|
||||
_shell._discover_client = self.shell_discover_client
|
||||
_shell.main(argstr.split())
|
||||
except SystemExit:
|
||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||
|
@ -93,8 +103,8 @@ class OpenstackManilaShellTest(utils.TestCase):
|
|||
|
||||
self.shell('list')
|
||||
|
||||
mock_client.Client.assert_called_once_with(
|
||||
constants.MAX_API_VERSION,
|
||||
mock_client.Client.assert_called_with(
|
||||
manilaclient.API_MAX_VERSION,
|
||||
username=env_vars['OS_USERNAME'],
|
||||
password=env_vars['OS_PASSWORD'],
|
||||
project_name=env_vars['OS_PROJECT_NAME'],
|
||||
|
@ -104,14 +114,13 @@ class OpenstackManilaShellTest(utils.TestCase):
|
|||
tenant_id=env_vars['OS_PROJECT_ID'],
|
||||
endpoint_type='publicURL',
|
||||
extensions=mock.ANY,
|
||||
service_type='sharev2',
|
||||
service_type=constants.V1_SERVICE_TYPE,
|
||||
service_name='',
|
||||
retries=0,
|
||||
http_log_debug=False,
|
||||
cacert=None,
|
||||
use_keyring=False,
|
||||
force_new_token=False,
|
||||
api_version=constants.MAX_API_VERSION,
|
||||
user_id=env_vars['OS_USER_ID'],
|
||||
user_domain_id=env_vars['OS_USER_DOMAIN_ID'],
|
||||
user_domain_name=env_vars['OS_USER_DOMAIN_NAME'],
|
||||
|
|
|
@ -14,11 +14,12 @@
|
|||
|
||||
from six.moves.urllib import parse
|
||||
|
||||
from manilaclient.common import constants
|
||||
from manilaclient import httpclient
|
||||
import manilaclient
|
||||
from manilaclient import api_versions
|
||||
from manilaclient.common import httpclient
|
||||
from manilaclient.tests.unit import fakes
|
||||
from manilaclient.tests.unit import utils
|
||||
from manilaclient.v1 import client
|
||||
from manilaclient.v2 import client
|
||||
|
||||
|
||||
class FakeClient(fakes.FakeClient, client.Client):
|
||||
|
@ -26,7 +27,8 @@ class FakeClient(fakes.FakeClient, client.Client):
|
|||
def __init__(self, *args, **kwargs):
|
||||
client.Client.__init__(self, 'username', 'password',
|
||||
'project_id', 'auth_url',
|
||||
extensions=kwargs.get('extensions'))
|
||||
extensions=kwargs.get('extensions'),
|
||||
version=manilaclient.API_MAX_VERSION,)
|
||||
self.client = FakeHTTPClient(**kwargs)
|
||||
|
||||
|
||||
|
@ -40,7 +42,7 @@ class FakeHTTPClient(httpclient.HTTPClient):
|
|||
self.base_url = 'localhost'
|
||||
self.default_headers = {
|
||||
'X-Auth-Token': 'xabc123',
|
||||
'X-Openstack-Manila-Api-Version': constants.MAX_API_VERSION,
|
||||
'X-Openstack-Manila-Api-Version': api_versions.MAX_VERSION,
|
||||
'Accept': 'application/json',
|
||||
}
|
||||
|
|
@ -15,18 +15,21 @@
|
|||
|
||||
from __future__ import print_function
|
||||
|
||||
from manilaclient.tests.unit.v1 import fake_clients as fakes
|
||||
from manilaclient.v1 import client
|
||||
import manilaclient
|
||||
from manilaclient.tests.unit.v2 import fake_clients as fakes
|
||||
from manilaclient.v2 import client
|
||||
|
||||
|
||||
class FakeClient(fakes.FakeClient):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
client.Client.__init__(self, 'username', 'password',
|
||||
client.Client.__init__(self, manilaclient.API_MAX_VERSION,
|
||||
'username', 'password',
|
||||
'project_id', 'auth_url',
|
||||
input_auth_token='token',
|
||||
extensions=kwargs.get('extensions'),
|
||||
service_catalog_url='http://localhost:8786')
|
||||
service_catalog_url='http://localhost:8786',
|
||||
api_version=manilaclient.API_MAX_VERSION)
|
||||
self.client = FakeHTTPClient(**kwargs)
|
||||
|
||||
fake_share_instance = {
|
||||
|
@ -54,13 +57,13 @@ class FakeHTTPClient(fakes.FakeHTTPClient):
|
|||
"rel": "describedby",
|
||||
},
|
||||
{
|
||||
"href": "http://localhost:8786/v1/",
|
||||
"href": "http://localhost:8786/v2/",
|
||||
"rel": "self",
|
||||
}
|
||||
],
|
||||
"min_version": "1.0",
|
||||
"version": "1.1",
|
||||
"id": "v1.0",
|
||||
"min_version": "2.0",
|
||||
"version": "2.5",
|
||||
"id": "v2.0",
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -95,6 +98,31 @@ class FakeHTTPClient(fakes.FakeHTTPClient):
|
|||
return (200, {}, {'host': 'foo', 'binary': 'manila-share',
|
||||
'disabled': True})
|
||||
|
||||
def get_v2(self, **kw):
|
||||
body = {
|
||||
"versions": [
|
||||
{
|
||||
"status": "CURRENT",
|
||||
"updated": "2015-07-30T11:33:21Z",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://docs.openstack.org/",
|
||||
"type": "text/html",
|
||||
"rel": "describedby",
|
||||
},
|
||||
{
|
||||
"href": "http://localhost:8786/v2/",
|
||||
"rel": "self",
|
||||
}
|
||||
],
|
||||
"min_version": "2.0",
|
||||
"version": "2.5",
|
||||
"id": "v1.0",
|
||||
}
|
||||
]
|
||||
}
|
||||
return (200, {}, body)
|
||||
|
||||
def get_shares_1234(self, **kw):
|
||||
share = {'share': {'id': 1234, 'name': 'sharename'}}
|
||||
return (200, {}, share)
|
||||
|
@ -104,7 +132,7 @@ class FakeHTTPClient(fakes.FakeHTTPClient):
|
|||
return (200, {}, share)
|
||||
|
||||
def get_shares(self, **kw):
|
||||
endpoint = "http://127.0.0.1:8786/v1"
|
||||
endpoint = "http://127.0.0.1:8786/v2"
|
||||
share_id = '1234'
|
||||
shares = {
|
||||
'shares': [
|
||||
|
@ -121,7 +149,7 @@ class FakeHTTPClient(fakes.FakeHTTPClient):
|
|||
return (200, {}, shares)
|
||||
|
||||
def get_shares_detail(self, **kw):
|
||||
endpoint = "http://127.0.0.1:8786/v1"
|
||||
endpoint = "http://127.0.0.1:8786/v2"
|
||||
share_id = '1234'
|
||||
shares = {
|
||||
'shares': [
|
|
@ -15,10 +15,10 @@ import uuid
|
|||
import ddt
|
||||
import mock
|
||||
|
||||
from manilaclient.common import constants
|
||||
import manilaclient
|
||||
from manilaclient import exceptions
|
||||
from manilaclient.tests.unit import utils
|
||||
from manilaclient.v1 import client
|
||||
from manilaclient.v2 import client
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
|
@ -37,7 +37,8 @@ class ClientTest(utils.TestCase):
|
|||
base_url = uuid.uuid4().hex
|
||||
|
||||
s = client.session.Session()
|
||||
c = client.Client(session=s, api_version=constants.MAX_API_VERSION,
|
||||
c = client.Client(session=s,
|
||||
api_version=manilaclient.API_MAX_VERSION,
|
||||
service_catalog_url=base_url, retries=retries,
|
||||
input_auth_token='token')
|
||||
|
||||
|
@ -46,7 +47,7 @@ class ClientTest(utils.TestCase):
|
|||
|
||||
def test_auth_via_token_invalid(self):
|
||||
self.assertRaises(exceptions.ClientException, client.Client,
|
||||
api_version=constants.MAX_API_VERSION,
|
||||
api_version=manilaclient.API_MAX_VERSION,
|
||||
input_auth_token="token")
|
||||
|
||||
def test_auth_via_token_and_session(self):
|
||||
|
@ -54,7 +55,7 @@ class ClientTest(utils.TestCase):
|
|||
base_url = uuid.uuid4().hex
|
||||
c = client.Client(input_auth_token='token',
|
||||
service_catalog_url=base_url, session=s,
|
||||
api_version=constants.MAX_API_VERSION)
|
||||
api_version=manilaclient.API_MAX_VERSION)
|
||||
|
||||
self.assertIsNotNone(c.client)
|
||||
self.assertIsNone(c.keystone_client)
|
||||
|
@ -64,22 +65,23 @@ class ClientTest(utils.TestCase):
|
|||
|
||||
c = client.Client(input_auth_token='token',
|
||||
service_catalog_url=base_url,
|
||||
api_version=constants.MAX_API_VERSION)
|
||||
api_version=manilaclient.API_MAX_VERSION)
|
||||
|
||||
self.assertIsNotNone(c.client)
|
||||
self.assertIsNone(c.keystone_client)
|
||||
|
||||
@mock.patch.object(client.Client, '_get_keystone_client', mock.Mock())
|
||||
def test_valid_region_name(self):
|
||||
def test_valid_region_name_v1(self):
|
||||
self.mock_object(client.httpclient, 'HTTPClient')
|
||||
kc = client.Client._get_keystone_client.return_value
|
||||
kc.service_catalog = mock.Mock()
|
||||
kc.service_catalog.get_endpoints = mock.Mock(return_value=self.catalog)
|
||||
c = client.Client(api_version=constants.MAX_API_VERSION,
|
||||
c = client.Client(api_version=manilaclient.API_DEPRECATED_VERSION,
|
||||
service_type="share",
|
||||
region_name='TestRegion')
|
||||
self.assertTrue(client.Client._get_keystone_client.called)
|
||||
kc.service_catalog.get_endpoints.assert_called_once_with('share')
|
||||
client.httpclient.HTTPClient.assert_called_once_with(
|
||||
kc.service_catalog.get_endpoints.assert_called_with('share')
|
||||
client.httpclient.HTTPClient.assert_called_with(
|
||||
'http://1.2.3.4',
|
||||
mock.ANY,
|
||||
'python-manilaclient',
|
||||
|
@ -88,7 +90,7 @@ class ClientTest(utils.TestCase):
|
|||
timeout=None,
|
||||
retries=None,
|
||||
http_log_debug=False,
|
||||
api_version=constants.MAX_API_VERSION)
|
||||
api_version=manilaclient.API_DEPRECATED_VERSION)
|
||||
self.assertIsNotNone(c.client)
|
||||
|
||||
@mock.patch.object(client.Client, '_get_keystone_client', mock.Mock())
|
||||
|
@ -97,17 +99,16 @@ class ClientTest(utils.TestCase):
|
|||
kc.service_catalog = mock.Mock()
|
||||
kc.service_catalog.get_endpoints = mock.Mock(return_value=self.catalog)
|
||||
self.assertRaises(RuntimeError, client.Client,
|
||||
api_version=constants.MAX_API_VERSION,
|
||||
api_version=manilaclient.API_MAX_VERSION,
|
||||
region_name='FakeRegion')
|
||||
|
||||
self.assertTrue(client.Client._get_keystone_client.called)
|
||||
kc.service_catalog.get_endpoints.assert_called_once_with('share')
|
||||
kc.service_catalog.get_endpoints.assert_called_with('sharev2')
|
||||
|
||||
@mock.patch.object(client.Client, '_get_keystone_client', mock.Mock())
|
||||
def test_regions_with_same_name(self):
|
||||
self.mock_object(client.httpclient, 'HTTPClient')
|
||||
catalog = {
|
||||
'share': [
|
||||
'sharev2': [
|
||||
{'region': 'FirstRegion', 'publicURL': 'http://1.2.3.4'},
|
||||
{'region': 'secondregion', 'publicURL': 'http://1.1.1.1'},
|
||||
{'region': 'SecondRegion', 'publicURL': 'http://2.2.2.2'},
|
||||
|
@ -116,11 +117,12 @@ class ClientTest(utils.TestCase):
|
|||
kc = client.Client._get_keystone_client.return_value
|
||||
kc.service_catalog = mock.Mock()
|
||||
kc.service_catalog.get_endpoints = mock.Mock(return_value=catalog)
|
||||
c = client.Client(api_version=constants.MAX_API_VERSION,
|
||||
c = client.Client(api_version=manilaclient.API_MIN_VERSION,
|
||||
service_type='sharev2',
|
||||
region_name='SecondRegion')
|
||||
self.assertTrue(client.Client._get_keystone_client.called)
|
||||
kc.service_catalog.get_endpoints.assert_called_once_with('share')
|
||||
client.httpclient.HTTPClient.assert_called_once_with(
|
||||
kc.service_catalog.get_endpoints.assert_called_with('sharev2')
|
||||
client.httpclient.HTTPClient.assert_called_with(
|
||||
'http://2.2.2.2',
|
||||
mock.ANY,
|
||||
'python-manilaclient',
|
||||
|
@ -129,13 +131,13 @@ class ClientTest(utils.TestCase):
|
|||
timeout=None,
|
||||
retries=None,
|
||||
http_log_debug=False,
|
||||
api_version=constants.MAX_API_VERSION)
|
||||
api_version=manilaclient.API_MIN_VERSION)
|
||||
self.assertIsNotNone(c.client)
|
||||
|
||||
def _get_client_args(self, **kwargs):
|
||||
client_args = {
|
||||
'auth_url': 'both',
|
||||
'api_version': constants.MAX_API_VERSION,
|
||||
'api_version': manilaclient.API_DEPRECATED_VERSION,
|
||||
'username': 'fake_username',
|
||||
'service_type': 'sharev2',
|
||||
'region_name': 'SecondRegion',
|
||||
|
@ -174,6 +176,7 @@ class ClientTest(utils.TestCase):
|
|||
self.mock_object(client.discover, 'Discover')
|
||||
self.mock_object(client.session, 'Session')
|
||||
client_args = self._get_client_args(**kwargs)
|
||||
client_args['api_version'] = manilaclient.API_MIN_VERSION
|
||||
self.auth_url = client_args['auth_url']
|
||||
catalog = {
|
||||
'share': [
|
||||
|
@ -203,11 +206,12 @@ class ClientTest(utils.TestCase):
|
|||
|
||||
client.Client(**client_args)
|
||||
|
||||
client.httpclient.HTTPClient.assert_called_once_with(
|
||||
client.httpclient.HTTPClient.assert_called_with(
|
||||
'http://3.3.3.3', mock.ANY, 'python-manilaclient', insecure=False,
|
||||
cacert=None, timeout=None, retries=None, http_log_debug=False,
|
||||
api_version=constants.MAX_API_VERSION)
|
||||
client.ks_client.Client.assert_called_once_with(
|
||||
api_version=manilaclient.API_MIN_VERSION)
|
||||
|
||||
client.ks_client.Client.assert_called_with(
|
||||
version=(3, 0), auth_url='url_v3.0',
|
||||
username=client_args['username'],
|
||||
password=client_args.get('password', client_args.get('api_key')),
|
||||
|
@ -221,9 +225,9 @@ class ClientTest(utils.TestCase):
|
|||
project_domain_id=client_args['project_domain_id'],
|
||||
region_name=client_args['region_name'],
|
||||
)
|
||||
mocked_ks_client.service_catalog.get_endpoints.assert_called_once_with(
|
||||
mocked_ks_client.service_catalog.get_endpoints.assert_called_with(
|
||||
client_args['service_type'])
|
||||
mocked_ks_client.authenticate.assert_called_once_with()
|
||||
mocked_ks_client.authenticate.assert_called_with()
|
||||
|
||||
@ddt.data(
|
||||
{'auth_url': 'only_v2', 'api_key': 'foo', 'project_id': 'bar'},
|
||||
|
@ -235,6 +239,7 @@ class ClientTest(utils.TestCase):
|
|||
self.mock_object(client.discover, 'Discover')
|
||||
self.mock_object(client.session, 'Session')
|
||||
client_args = self._get_client_args(**kwargs)
|
||||
client_args['api_version'] = manilaclient.API_MIN_VERSION
|
||||
self.auth_url = client_args['auth_url']
|
||||
catalog = {
|
||||
'share': [
|
||||
|
@ -256,11 +261,11 @@ class ClientTest(utils.TestCase):
|
|||
|
||||
client.Client(**client_args)
|
||||
|
||||
client.httpclient.HTTPClient.assert_called_once_with(
|
||||
client.httpclient.HTTPClient.assert_called_with(
|
||||
'http://3.3.3.3', mock.ANY, 'python-manilaclient', insecure=False,
|
||||
cacert=None, timeout=None, retries=None, http_log_debug=False,
|
||||
api_version=constants.MAX_API_VERSION)
|
||||
client.ks_client.Client.assert_called_once_with(
|
||||
api_version=manilaclient.API_MIN_VERSION)
|
||||
client.ks_client.Client.assert_called_with(
|
||||
version=(2, 0), auth_url='url_v2.0',
|
||||
username=client_args['username'],
|
||||
password=client_args.get('password', client_args.get('api_key')),
|
||||
|
@ -269,9 +274,9 @@ class ClientTest(utils.TestCase):
|
|||
tenant_name=client_args['project_name'],
|
||||
region_name=client_args['region_name'], cert=client_args['cert'],
|
||||
use_keyring=False, force_new_token=False, stale_duration=300)
|
||||
mocked_ks_client.service_catalog.get_endpoints.assert_called_once_with(
|
||||
mocked_ks_client.service_catalog.get_endpoints.assert_called_with(
|
||||
client_args['service_type'])
|
||||
mocked_ks_client.authenticate.assert_called_once_with()
|
||||
mocked_ks_client.authenticate.assert_called_with()
|
||||
|
||||
@mock.patch.object(client.ks_client, 'Client', mock.Mock())
|
||||
@mock.patch.object(client.discover, 'Discover', mock.Mock())
|
|
@ -14,9 +14,11 @@
|
|||
# under the License.
|
||||
import mock
|
||||
|
||||
import manilaclient
|
||||
from manilaclient import exceptions
|
||||
from manilaclient.tests.unit import utils
|
||||
from manilaclient.tests.unit.v1 import fakes
|
||||
from manilaclient.v1 import consistency_group_snapshots as cg_snapshots
|
||||
from manilaclient.tests.unit.v2 import fakes
|
||||
from manilaclient.v2 import consistency_group_snapshots as cg_snapshots
|
||||
|
||||
FAKE_CG = 'fake cg snapshot'
|
||||
FAKE_CG_ID = 'fake-cg-id'
|
||||
|
@ -27,9 +29,13 @@ class ConsistencyGroupSnapshotsTest(utils.TestCase):
|
|||
class _FakeConsistencyGroupSnapshot(object):
|
||||
id = 'fake_cg_snapshot_id'
|
||||
|
||||
class _FakeClient(object):
|
||||
api_version = manilaclient.API_MAX_VERSION
|
||||
|
||||
def setUp(self):
|
||||
super(ConsistencyGroupSnapshotsTest, self).setUp()
|
||||
self.manager = cg_snapshots.ConsistencyGroupSnapshotManager(api=None)
|
||||
self.manager = cg_snapshots.ConsistencyGroupSnapshotManager(
|
||||
api=self._FakeClient())
|
||||
self.values = {
|
||||
'consistency_group_id': 'fake_cg_id',
|
||||
'name': 'fake snapshot name',
|
||||
|
@ -52,6 +58,12 @@ class ConsistencyGroupSnapshotsTest(utils.TestCase):
|
|||
self.assertEqual(result['resp_key'], cg_snapshots.RESOURCE_NAME)
|
||||
self.assertEqual(result['body'], body_expected)
|
||||
|
||||
def test_snapshot_create_invalid_version(self):
|
||||
self.manager.api.api_version = manilaclient.API_MIN_VERSION
|
||||
|
||||
self.assertRaises(
|
||||
exceptions.UnsupportedVersion, self.manager.create, **self.values)
|
||||
|
||||
def test_snapshot_get(self):
|
||||
with mock.patch.object(self.manager, '_get', mock.Mock()):
|
||||
self.manager.get(FAKE_CG_ID)
|
|
@ -14,9 +14,11 @@
|
|||
# under the License.
|
||||
import mock
|
||||
|
||||
import manilaclient
|
||||
from manilaclient import exceptions
|
||||
from manilaclient.tests.unit import utils
|
||||
from manilaclient.tests.unit.v1 import fakes
|
||||
from manilaclient.v1 import consistency_groups
|
||||
from manilaclient.tests.unit.v2 import fakes
|
||||
from manilaclient.v2 import consistency_groups
|
||||
|
||||
FAKE_CG = 'fake consistency group'
|
||||
|
||||
|
@ -26,9 +28,13 @@ class ConsistencyGroupsTest(utils.TestCase):
|
|||
class _FakeConsistencyGroupSnapshot(object):
|
||||
id = 'fake_cg_snapshot_id'
|
||||
|
||||
class _FakeClient(object):
|
||||
api_version = manilaclient.API_MAX_VERSION
|
||||
|
||||
def setUp(self):
|
||||
super(ConsistencyGroupsTest, self).setUp()
|
||||
self.manager = consistency_groups.ConsistencyGroupManager(api=None)
|
||||
self.manager = consistency_groups.ConsistencyGroupManager(
|
||||
api=self._FakeClient())
|
||||
self.values = {'name': 'fake name', 'description': 'new cg'}
|
||||
|
||||
def test_create(self):
|
||||
|
@ -49,6 +55,12 @@ class ConsistencyGroupsTest(utils.TestCase):
|
|||
consistency_groups.RESOURCE_NAME)
|
||||
self.assertEqual(result['body'], body_expected)
|
||||
|
||||
def test_invalid_create(self):
|
||||
self.manager.api.api_version = manilaclient.API_MIN_VERSION
|
||||
|
||||
self.assertRaises(
|
||||
exceptions.UnsupportedVersion, self.manager.create, **self.values)
|
||||
|
||||
def test_create_with_share_network_id(self):
|
||||
resource_name = consistency_groups.RESOURCE_NAME
|
||||
body_expected = {resource_name: dict(list(self.values.items()))}
|
|
@ -14,7 +14,7 @@
|
|||
# under the License.
|
||||
|
||||
from manilaclient.tests.unit import utils
|
||||
from manilaclient.tests.unit.v1 import fakes
|
||||
from manilaclient.tests.unit.v2 import fakes
|
||||
|
||||
|
||||
cs = fakes.FakeClient()
|
|
@ -14,7 +14,7 @@
|
|||
# under the License.
|
||||
|
||||
from manilaclient.tests.unit import utils
|
||||
from manilaclient.tests.unit.v1 import fakes
|
||||
from manilaclient.tests.unit.v2 import fakes
|
||||
|
||||
|
||||
cs = fakes.FakeClient()
|
|
@ -15,7 +15,7 @@
|
|||
import mock
|
||||
|
||||
from manilaclient.tests.unit import utils
|
||||
from manilaclient.v1 import scheduler_stats
|
||||
from manilaclient.v2 import scheduler_stats
|
||||
|
||||
|
||||
class PoolTest(utils.TestCase):
|
|
@ -16,8 +16,8 @@ import mock
|
|||
|
||||
from manilaclient import exceptions
|
||||
from manilaclient.tests.unit import utils
|
||||
from manilaclient.tests.unit.v1 import fakes
|
||||
from manilaclient.v1 import security_services
|
||||
from manilaclient.tests.unit.v2 import fakes
|
||||
from manilaclient.v2 import security_services
|
||||
|
||||
|
||||
class SecurityServiceTest(utils.TestCase):
|
|
@ -16,7 +16,7 @@
|
|||
import mock
|
||||
|
||||
from manilaclient.tests.unit import utils
|
||||
from manilaclient.v1 import services
|
||||
from manilaclient.v2 import services
|
||||
|
||||
|
||||
class ServicesTest(utils.TestCase):
|
|
@ -17,8 +17,8 @@ import ddt
|
|||
|
||||
from manilaclient import extension
|
||||
from manilaclient.tests.unit import utils
|
||||
from manilaclient.tests.unit.v1 import fakes
|
||||
from manilaclient.v1 import share_instances
|
||||
from manilaclient.tests.unit.v2 import fakes
|
||||
from manilaclient.v2 import share_instances
|
||||
|
||||
|
||||
extensions = [
|
|
@ -16,8 +16,8 @@ import mock
|
|||
|
||||
from manilaclient import exceptions
|
||||
from manilaclient.tests.unit import utils
|
||||
from manilaclient.tests.unit.v1 import fakes
|
||||
from manilaclient.v1 import share_networks
|
||||
from manilaclient.tests.unit.v2 import fakes
|
||||
from manilaclient.v2 import share_networks
|
||||
|
||||
|
||||
class ShareNetworkTest(utils.TestCase):
|
|
@ -16,7 +16,7 @@
|
|||
import mock
|
||||
|
||||
from manilaclient.tests.unit import utils
|
||||
from manilaclient.v1 import share_servers
|
||||
from manilaclient.v2 import share_servers
|
||||
|
||||
|
||||
class FakeShareServer(object):
|
|
@ -19,8 +19,8 @@ import ddt
|
|||
|
||||
from manilaclient import extension
|
||||
from manilaclient.tests.unit import utils
|
||||
from manilaclient.tests.unit.v1 import fakes
|
||||
from manilaclient.v1 import share_snapshots
|
||||
from manilaclient.tests.unit.v2 import fakes
|
||||
from manilaclient.v2 import share_snapshots
|
||||
|
||||
|
||||
extensions = [
|
|
@ -21,8 +21,8 @@ import mock
|
|||
from manilaclient import exceptions
|
||||
from manilaclient import extension
|
||||
from manilaclient.tests.unit import utils
|
||||
from manilaclient.tests.unit.v1 import fakes
|
||||
from manilaclient.v1 import shares
|
||||
from manilaclient.tests.unit.v2 import fakes
|
||||
from manilaclient.v2 import shares
|
||||
|
||||
|
||||
extensions = [
|
|
@ -28,9 +28,9 @@ from manilaclient.openstack.common.apiclient import utils as apiclient_utils
|
|||
from manilaclient.openstack.common import cliutils
|
||||
from manilaclient import shell
|
||||
from manilaclient.tests.unit import utils as test_utils
|
||||
from manilaclient.tests.unit.v1 import fakes
|
||||
from manilaclient.v1 import share_instances
|
||||
from manilaclient.v1 import shell as shell_v1
|
||||
from manilaclient.tests.unit.v2 import fakes
|
||||
from manilaclient.v2 import share_instances
|
||||
from manilaclient.v2 import shell as shell_v2
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
|
@ -40,7 +40,7 @@ class ShellTest(test_utils.TestCase):
|
|||
'MANILA_USERNAME': 'username',
|
||||
'MANILA_PASSWORD': 'password',
|
||||
'MANILA_PROJECT_ID': 'project_id',
|
||||
'OS_SHARE_API_VERSION': '2',
|
||||
'OS_SHARE_API_VERSION': '2.5',
|
||||
'MANILA_URL': 'http://no.where',
|
||||
}
|
||||
|
||||
|
@ -99,69 +99,6 @@ class ShellTest(test_utils.TestCase):
|
|||
def assert_called_anytime(self, method, url, body=None):
|
||||
return self.shell.cs.assert_called_anytime(method, url, body)
|
||||
|
||||
@ddt.data(
|
||||
{'serviceCatalog': [{'name': 'foo', 'endpoints': ['bar']}]},
|
||||
{'catalog': [{'name': 'foo', 'endpoints': ['bar']}]},
|
||||
{'serviceCatalog': [{'name': 'foo', 'endpoints': ['bar']}],
|
||||
'catalog': 'fake'},
|
||||
)
|
||||
def test_do_endpoints(self, catalog):
|
||||
cs = type('Fake', (object, ), {'keystone_client': type(
|
||||
'FakeKeystoneClient', (object, ), {
|
||||
'service_catalog': type('FakeCatalog', (object, ), {
|
||||
'catalog': catalog})})})
|
||||
|
||||
with mock.patch.object(
|
||||
shell_v1.cliutils, 'print_dict') as mock_print_dict:
|
||||
shell_v1.do_endpoints(cs, ('no', 'args'))
|
||||
|
||||
mock_print_dict.assert_has_calls([
|
||||
mock.call('bar', 'foo'),
|
||||
])
|
||||
|
||||
@ddt.data(
|
||||
{'version': 'v3',
|
||||
'user': 'foo_user',
|
||||
'issued_at': 'foo_issued_at',
|
||||
'expires_at': 'foo_expires',
|
||||
'auth_token': 'foo_ids',
|
||||
'audit_ids': 'foo_audit_ids',
|
||||
'project': 'foo_tenant_project',
|
||||
'redundant_key': 'should not be used',
|
||||
},
|
||||
{'version': 'v2.0',
|
||||
'user': 'foo_user',
|
||||
'token': {
|
||||
'issued_at': 'foo_issued_at',
|
||||
'expires': 'foo_expires',
|
||||
'id': 'foo_ids',
|
||||
'audit_ids': 'foo_audit_ids',
|
||||
'tenant': 'foo_tenant_project',
|
||||
},
|
||||
},
|
||||
)
|
||||
def test_do_credentials(self, catalog):
|
||||
cs = type('Fake', (object, ), {'keystone_client': type(
|
||||
'FakeKeystoneClient', (object, ), {
|
||||
'service_catalog': type('FakeCatalog', (object, ), {
|
||||
'catalog': catalog})})})
|
||||
expected_call_data = {
|
||||
'issued_at': 'foo_issued_at',
|
||||
'expires': 'foo_expires',
|
||||
'id': 'foo_ids',
|
||||
'audit_ids': 'foo_audit_ids',
|
||||
'tenant': 'foo_tenant_project',
|
||||
}
|
||||
|
||||
with mock.patch.object(
|
||||
shell_v1.cliutils, 'print_dict') as mock_print_dict:
|
||||
shell_v1.do_credentials(cs, ('no', 'args'))
|
||||
|
||||
mock_print_dict.assert_has_calls([
|
||||
mock.call('foo_user', 'User Credentials'),
|
||||
mock.call(expected_call_data, 'Token'),
|
||||
])
|
||||
|
||||
def test_service_list(self):
|
||||
self.run_command('service-list')
|
||||
self.assert_called('GET', '/os-services')
|
||||
|
@ -387,7 +324,7 @@ class ShellTest(test_utils.TestCase):
|
|||
share_instance = share_instances.ShareInstance(
|
||||
manager_mock, {'id': 'fake'}, True)
|
||||
|
||||
with mock.patch.object(shell_v1, '_find_share_instance',
|
||||
with mock.patch.object(shell_v2, '_find_share_instance',
|
||||
mock.Mock(return_value=share_instance)):
|
||||
self.run_command('share-instance-force-delete 1234')
|
||||
manager_mock.force_delete.assert_called_once_with(share_instance)
|
||||
|
@ -513,16 +450,16 @@ class ShellTest(test_utils.TestCase):
|
|||
|
||||
@ddt.data(
|
||||
'--cg 1234', '--consistency-group 1234', '--consistency_group 1234')
|
||||
@mock.patch.object(shell_v1, '_find_consistency_group', mock.Mock())
|
||||
@mock.patch.object(shell_v2, '_find_consistency_group', mock.Mock())
|
||||
def test_delete_with_cg(self, cg_cmd):
|
||||
fcg = type(
|
||||
'FakeConsistencyGroup', (object,), {'id': cg_cmd.split()[-1]})
|
||||
shell_v1._find_consistency_group.return_value = fcg
|
||||
shell_v2._find_consistency_group.return_value = fcg
|
||||
|
||||
self.run_command('delete 1234 %s' % cg_cmd)
|
||||
|
||||
self.assert_called('DELETE', '/shares/1234?consistency_group_id=1234')
|
||||
self.assertTrue(shell_v1._find_consistency_group.called)
|
||||
self.assertTrue(shell_v2._find_consistency_group.called)
|
||||
|
||||
def test_delete_not_found(self):
|
||||
self.assertRaises(
|
||||
|
@ -802,7 +739,7 @@ class ShellTest(test_utils.TestCase):
|
|||
|
||||
for input in inputs:
|
||||
args = Arguments(metadata=input[0])
|
||||
self.assertEqual(shell_v1._extract_metadata(args), input[1])
|
||||
self.assertEqual(shell_v2._extract_metadata(args), input[1])
|
||||
|
||||
def test_extend(self):
|
||||
self.run_command('extend 1234 77')
|
||||
|
@ -911,10 +848,10 @@ class ShellTest(test_utils.TestCase):
|
|||
fields=['id', 'name'])
|
||||
|
||||
@mock.patch.object(cliutils, 'print_list', mock.Mock())
|
||||
@mock.patch.object(shell_v1, '_find_security_service', mock.Mock())
|
||||
@mock.patch.object(shell_v2, '_find_security_service', mock.Mock())
|
||||
def test_share_network_list_filter_by_security_service(self):
|
||||
ss = type('FakeSecurityService', (object,), {'id': 'fake-ss-id'})
|
||||
shell_v1._find_security_service.return_value = ss
|
||||
shell_v2._find_security_service.return_value = ss
|
||||
for command in ['--security_service', '--security-service']:
|
||||
self.run_command('share-network-list %(command)s %(ss_id)s' %
|
||||
{'command': command,
|
||||
|
@ -923,7 +860,7 @@ class ShellTest(test_utils.TestCase):
|
|||
'GET',
|
||||
'/share-networks/detail?security_service_id=%s' % ss.id,
|
||||
)
|
||||
shell_v1._find_security_service.assert_called_with(mock.ANY, ss.id)
|
||||
shell_v2._find_security_service.assert_called_with(mock.ANY, ss.id)
|
||||
cliutils.print_list.assert_called_with(
|
||||
mock.ANY,
|
||||
fields=['id', 'name'])
|
||||
|
@ -1132,13 +1069,13 @@ class ShellTest(test_utils.TestCase):
|
|||
def test_create_with_share_network(self):
|
||||
# Except required fields added share network
|
||||
sn = "fake-share-network"
|
||||
with mock.patch.object(shell_v1, "_find_share_network",
|
||||
with mock.patch.object(shell_v2, "_find_share_network",
|
||||
mock.Mock(return_value=sn)):
|
||||
self.run_command("create nfs 1 --share-network %s" % sn)
|
||||
expected = self.create_share_body.copy()
|
||||
expected['share']['share_network_id'] = sn
|
||||
self.assert_called("POST", "/shares", body=expected)
|
||||
shell_v1._find_share_network.assert_called_once_with(mock.ANY, sn)
|
||||
shell_v2._find_share_network.assert_called_once_with(mock.ANY, sn)
|
||||
|
||||
def test_create_with_metadata(self):
|
||||
# Except required fields added metadata
|
||||
|
@ -1236,12 +1173,12 @@ class ShellTest(test_utils.TestCase):
|
|||
fields=['id', 'name', 'status', 'type'])
|
||||
|
||||
@mock.patch.object(cliutils, 'print_list', mock.Mock())
|
||||
@mock.patch.object(shell_v1, '_find_share_network', mock.Mock())
|
||||
@mock.patch.object(shell_v2, '_find_share_network', mock.Mock())
|
||||
def test_security_service_list_filter_share_network(self):
|
||||
class FakeShareNetwork:
|
||||
id = 'fake-sn-id'
|
||||
sn = FakeShareNetwork()
|
||||
shell_v1._find_share_network.return_value = sn
|
||||
shell_v2._find_share_network.return_value = sn
|
||||
for command in ['--share-network', '--share_network']:
|
||||
self.run_command('security-service-list %(command)s %(sn_id)s' %
|
||||
{'command': command,
|
||||
|
@ -1250,7 +1187,7 @@ class ShellTest(test_utils.TestCase):
|
|||
'GET',
|
||||
'/security-services?share_network_id=%s' % sn.id,
|
||||
)
|
||||
shell_v1._find_share_network.assert_called_with(mock.ANY, sn.id)
|
||||
shell_v2._find_share_network.assert_called_with(mock.ANY, sn.id)
|
||||
cliutils.print_list.assert_called_with(
|
||||
mock.ANY,
|
||||
fields=['id', 'name', 'status', 'type'])
|
||||
|
@ -1407,45 +1344,45 @@ class ShellTest(test_utils.TestCase):
|
|||
self.run_command(cmd)
|
||||
self.assert_called('POST', '/consistency-groups')
|
||||
|
||||
@mock.patch.object(shell_v1, '_find_consistency_group', mock.Mock())
|
||||
@mock.patch.object(shell_v2, '_find_consistency_group', mock.Mock())
|
||||
def test_cg_delete(self):
|
||||
fcg = type('FakeConsistencyGroup', (object,), {'id': '1234'})
|
||||
shell_v1._find_consistency_group.return_value = fcg
|
||||
shell_v2._find_consistency_group.return_value = fcg
|
||||
|
||||
self.run_command('cg-delete fake-cg')
|
||||
self.assert_called('DELETE', '/consistency-groups/1234')
|
||||
|
||||
@mock.patch.object(shell_v1, '_find_consistency_group', mock.Mock())
|
||||
@mock.patch.object(shell_v2, '_find_consistency_group', mock.Mock())
|
||||
def test_cg_delete_force(self):
|
||||
fcg = type('FakeConsistencyGroup', (object,), {'id': '1234'})
|
||||
shell_v1._find_consistency_group.return_value = fcg
|
||||
shell_v2._find_consistency_group.return_value = fcg
|
||||
|
||||
self.run_command('cg-delete --force fake-cg')
|
||||
self.assert_called('POST', '/consistency-groups/1234/action',
|
||||
{'os-force_delete': None})
|
||||
|
||||
@mock.patch.object(shell_v1, '_find_consistency_group', mock.Mock())
|
||||
@mock.patch.object(shell_v2, '_find_consistency_group', mock.Mock())
|
||||
def test_cg_reset_state_with_flag(self):
|
||||
fcg = type('FakeConsistencyGroup', (object,), {'id': '1234'})
|
||||
shell_v1._find_consistency_group.return_value = fcg
|
||||
shell_v2._find_consistency_group.return_value = fcg
|
||||
|
||||
self.run_command('cg-reset-state --state error 1234')
|
||||
self.assert_called('POST', '/consistency-groups/1234/action',
|
||||
{'os-reset_status': {'status': 'error'}})
|
||||
|
||||
@mock.patch.object(shell_v1, '_find_cg_snapshot', mock.Mock())
|
||||
@mock.patch.object(shell_v2, '_find_cg_snapshot', mock.Mock())
|
||||
def test_cg_snapshot_reset_state(self):
|
||||
fcg = type('FakeConsistencyGroup', (object,), {'id': '1234'})
|
||||
shell_v1._find_cg_snapshot.return_value = fcg
|
||||
shell_v2._find_cg_snapshot.return_value = fcg
|
||||
|
||||
self.run_command('cg-snapshot-reset-state 1234')
|
||||
self.assert_called('POST', '/cgsnapshots/1234/action',
|
||||
{'os-reset_status': {'status': 'available'}})
|
||||
|
||||
@mock.patch.object(shell_v1, '_find_cg_snapshot', mock.Mock())
|
||||
@mock.patch.object(shell_v2, '_find_cg_snapshot', mock.Mock())
|
||||
def test_cg_snapshot_reset_state_with_flag(self):
|
||||
fcg = type('FakeConsistencyGroup', (object,), {'id': '1234'})
|
||||
shell_v1._find_cg_snapshot.return_value = fcg
|
||||
shell_v2._find_cg_snapshot.return_value = fcg
|
||||
|
||||
self.run_command('cg-snapshot-reset-state --state creating 1234')
|
||||
self.assert_called('POST', '/cgsnapshots/1234/action',
|
||||
|
@ -1460,14 +1397,14 @@ class ShellTest(test_utils.TestCase):
|
|||
mock.ANY, fields=['id', 'name', 'description', 'status'])
|
||||
|
||||
@mock.patch.object(cliutils, 'print_list', mock.Mock())
|
||||
@mock.patch.object(shell_v1, '_find_cg_snapshot', mock.Mock())
|
||||
@mock.patch.object(shell_v2, '_find_cg_snapshot', mock.Mock())
|
||||
def test_cg_snapshot_members(self):
|
||||
fcg = type('FakeConsistencyGroup', (object,), {'id': 'fake-cg-id'})
|
||||
shell_v1._find_cg_snapshot.return_value = fcg
|
||||
shell_v2._find_cg_snapshot.return_value = fcg
|
||||
|
||||
self.run_command('cg-snapshot-members fake-cg-id')
|
||||
self.assert_called('GET', '/cgsnapshots/fake-cg-id/members')
|
||||
shell_v1._find_cg_snapshot.assert_called_with(mock.ANY, fcg.id)
|
||||
shell_v2._find_cg_snapshot.assert_called_with(mock.ANY, fcg.id)
|
||||
|
||||
cliutils.print_list.assert_called_once_with(
|
||||
mock.ANY, fields=['Id', 'Size', 'Created_at',
|
||||
|
@ -1492,30 +1429,30 @@ class ShellTest(test_utils.TestCase):
|
|||
'--name fake_name fake-cg-id',
|
||||
"--description my_fake_description --name fake_name fake-cg-id",
|
||||
)
|
||||
@mock.patch.object(shell_v1, '_find_consistency_group', mock.Mock())
|
||||
@mock.patch.object(shell_v2, '_find_consistency_group', mock.Mock())
|
||||
def test_cg_snapshot_create(self, data):
|
||||
fcg = type('FakeConsistencyGroup', (object,), {'id': 'fake-cg-id'})
|
||||
shell_v1._find_consistency_group.return_value = fcg
|
||||
shell_v2._find_consistency_group.return_value = fcg
|
||||
|
||||
cmd = 'cg-snapshot-create' + ' ' + data
|
||||
|
||||
self.run_command(cmd)
|
||||
|
||||
shell_v1._find_consistency_group.assert_called_with(mock.ANY, fcg.id)
|
||||
shell_v2._find_consistency_group.assert_called_with(mock.ANY, fcg.id)
|
||||
self.assert_called('POST', '/cgsnapshots')
|
||||
|
||||
@mock.patch.object(shell_v1, '_find_cg_snapshot', mock.Mock())
|
||||
@mock.patch.object(shell_v2, '_find_cg_snapshot', mock.Mock())
|
||||
def test_cg_snapshot_delete(self):
|
||||
fcg = type('FakeConsistencyGroup', (object,), {'id': '1234'})
|
||||
shell_v1._find_cg_snapshot.return_value = fcg
|
||||
shell_v2._find_cg_snapshot.return_value = fcg
|
||||
|
||||
self.run_command('cg-snapshot-delete fake-cg')
|
||||
self.assert_called('DELETE', '/cgsnapshots/1234')
|
||||
|
||||
@mock.patch.object(shell_v1, '_find_cg_snapshot', mock.Mock())
|
||||
@mock.patch.object(shell_v2, '_find_cg_snapshot', mock.Mock())
|
||||
def test_cg_snapshot_delete_force(self):
|
||||
fcg = type('FakeConsistencyGroup', (object,), {'id': '1234'})
|
||||
shell_v1._find_cg_snapshot.return_value = fcg
|
||||
shell_v2._find_cg_snapshot.return_value = fcg
|
||||
|
||||
self.run_command('cg-snapshot-delete --force fake-cg')
|
||||
self.assert_called('POST', '/cgsnapshots/1234/action',
|
||||
|
@ -1544,5 +1481,4 @@ class ShellTest(test_utils.TestCase):
|
|||
expected = dict(quota_class_set=expected)
|
||||
|
||||
self.run_command(cmd)
|
||||
self.assert_called('PUT', '/os-quota-class-sets/test',
|
||||
body=expected)
|
||||
self.assert_called('PUT', '/os-quota-class-sets/test', body=expected)
|
|
@ -17,8 +17,8 @@
|
|||
import mock
|
||||
|
||||
from manilaclient.tests.unit import utils
|
||||
from manilaclient.tests.unit.v1 import fakes
|
||||
from manilaclient.v1 import share_type_access
|
||||
from manilaclient.tests.unit.v2 import fakes
|
||||
from manilaclient.v2 import share_type_access
|
||||
|
||||
cs = fakes.FakeClient()
|
||||
|
|
@ -15,8 +15,8 @@ import ddt
|
|||
import mock
|
||||
|
||||
from manilaclient.tests.unit import utils
|
||||
from manilaclient.tests.unit.v1 import fakes
|
||||
from manilaclient.v1 import share_types
|
||||
from manilaclient.tests.unit.v2 import fakes
|
||||
from manilaclient.v2 import share_types
|
||||
|
||||
cs = fakes.FakeClient()
|
||||
|
|
@ -10,6 +10,8 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import six
|
||||
|
||||
|
||||
class HookableMixin(object):
|
||||
"""Mixin so classes can register and run hooks."""
|
||||
|
@ -39,3 +41,13 @@ def safe_issubclass(*args):
|
|||
pass
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def get_function_name(func):
|
||||
if six.PY2:
|
||||
if hasattr(func, "im_class"):
|
||||
return "%s.%s" % (func.im_class, func.__name__)
|
||||
else:
|
||||
return "%s.%s" % (func.__module__, func.__name__)
|
||||
else:
|
||||
return "%s.%s" % (func.__module__, func.__qualname__)
|
||||
|
|
|
@ -14,4 +14,17 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from manilaclient.v1.client import Client # noqa
|
||||
|
||||
import sys
|
||||
|
||||
from manilaclient import v2
|
||||
|
||||
|
||||
class MovedModule(object):
|
||||
def __init__(self, new_module):
|
||||
self.new_module = new_module
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.new_module, attr)
|
||||
|
||||
sys.modules["maniliaclient.v1"] = MovedModule(v2)
|
||||
|
|
|
@ -18,24 +18,22 @@ from keystoneclient import discover
|
|||
from keystoneclient import session
|
||||
import six
|
||||
|
||||
import manilaclient
|
||||
from manilaclient.common import constants
|
||||
from manilaclient.common import httpclient
|
||||
from manilaclient import exceptions
|
||||
from manilaclient import httpclient
|
||||
from manilaclient.v1 import consistency_group_snapshots as cg_snapshots
|
||||
from manilaclient.v1 import consistency_groups
|
||||
from manilaclient.v1 import limits
|
||||
from manilaclient.v1 import quota_classes
|
||||
from manilaclient.v1 import quotas
|
||||
from manilaclient.v1 import scheduler_stats
|
||||
from manilaclient.v1 import security_services
|
||||
from manilaclient.v1 import services
|
||||
from manilaclient.v1 import share_instances
|
||||
from manilaclient.v1 import share_networks
|
||||
from manilaclient.v1 import share_servers
|
||||
from manilaclient.v1 import share_snapshots
|
||||
from manilaclient.v1 import share_type_access
|
||||
from manilaclient.v1 import share_types
|
||||
from manilaclient.v1 import shares
|
||||
from manilaclient.v2 import limits
|
||||
from manilaclient.v2 import quota_classes
|
||||
from manilaclient.v2 import quotas
|
||||
from manilaclient.v2 import scheduler_stats
|
||||
from manilaclient.v2 import security_services
|
||||
from manilaclient.v2 import services
|
||||
from manilaclient.v2 import share_networks
|
||||
from manilaclient.v2 import share_servers
|
||||
from manilaclient.v2 import share_snapshots
|
||||
from manilaclient.v2 import share_type_access
|
||||
from manilaclient.v2 import share_types
|
||||
from manilaclient.v2 import shares
|
||||
|
||||
|
||||
class Client(object):
|
||||
|
@ -73,7 +71,7 @@ class Client(object):
|
|||
service_catalog_url=None, user_agent='python-manilaclient',
|
||||
use_keyring=False, force_new_token=False,
|
||||
cached_token_lifetime=300,
|
||||
api_version=constants.V1_API_VERSION,
|
||||
api_version=manilaclient.API_DEPRECATED_VERSION,
|
||||
user_id=None,
|
||||
user_domain_id=None,
|
||||
user_domain_name=None,
|
||||
|
@ -189,6 +187,7 @@ class Client(object):
|
|||
if not service_catalog_url:
|
||||
raise RuntimeError("Could not find Manila endpoint in catalog")
|
||||
|
||||
self.api_version = api_version
|
||||
self.client = httpclient.HTTPClient(service_catalog_url,
|
||||
input_auth_token,
|
||||
user_agent,
|
||||
|
@ -197,7 +196,7 @@ class Client(object):
|
|||
timeout=timeout,
|
||||
retries=retries,
|
||||
http_log_debug=http_log_debug,
|
||||
api_version=api_version)
|
||||
api_version=self.api_version)
|
||||
|
||||
self.limits = limits.LimitsManager(self)
|
||||
self.services = services.ServiceManager(self)
|
||||
|
@ -208,17 +207,12 @@ class Client(object):
|
|||
self.quotas = quotas.QuotaSetManager(self)
|
||||
|
||||
self.shares = shares.ShareManager(self)
|
||||
self.share_instances = share_instances.ShareInstanceManager(self)
|
||||
self.share_snapshots = share_snapshots.ShareSnapshotManager(self)
|
||||
|
||||
self.share_types = share_types.ShareTypeManager(self)
|
||||
self.share_type_access = share_type_access.ShareTypeAccessManager(self)
|
||||
self.share_servers = share_servers.ShareServerManager(self)
|
||||
self.pools = scheduler_stats.PoolManager(self)
|
||||
self.consistency_groups = (
|
||||
consistency_groups.ConsistencyGroupManager(self))
|
||||
self.cg_snapshots = (
|
||||
cg_snapshots.ConsistencyGroupSnapshotManager(self))
|
||||
|
||||
self._load_extensions(extensions)
|
||||
|
||||
|
|
|
@ -1,92 +1,35 @@
|
|||
# Copyright 2013 OpenStack LLC.
|
||||
# Copyright 2015 Chuck Fouts
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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
|
||||
# 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
|
||||
# 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.
|
||||
# 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 manilaclient import base
|
||||
from manilaclient.openstack.common.apiclient import base as common_base
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from manilaclient.v2 import limits
|
||||
|
||||
warnings.warn("Module manilaclient.v1.limits is deprecated (taken as a basis "
|
||||
"for manilaclient.v2.limits). "
|
||||
"The preferable way to get a client class or object is to use "
|
||||
"the manilaclient.client module.")
|
||||
|
||||
|
||||
class Limits(common_base.Resource):
|
||||
"""A collection of RateLimit and AbsoluteLimit objects."""
|
||||
class MovedModule(object):
|
||||
def __init__(self, new_module):
|
||||
self.new_module = new_module
|
||||
|
||||
def __repr__(self):
|
||||
return "<Limits>"
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.new_module, attr)
|
||||
|
||||
@property
|
||||
def absolute(self):
|
||||
for (name, value) in list(self._info['absolute'].items()):
|
||||
yield AbsoluteLimit(name, value)
|
||||
|
||||
@property
|
||||
def rate(self):
|
||||
for group in self._info['rate']:
|
||||
uri = group['uri']
|
||||
regex = group['regex']
|
||||
for rate in group['limit']:
|
||||
yield RateLimit(rate['verb'], uri, regex, rate['value'],
|
||||
rate['remaining'], rate['unit'],
|
||||
rate['next-available'])
|
||||
|
||||
|
||||
class RateLimit(object):
|
||||
"""Data model that represents a flattened view of a single rate limit."""
|
||||
|
||||
def __init__(self, verb, uri, regex, value, remain,
|
||||
unit, next_available):
|
||||
self.verb = verb
|
||||
self.uri = uri
|
||||
self.regex = regex
|
||||
self.value = value
|
||||
self.remain = remain
|
||||
self.unit = unit
|
||||
self.next_available = next_available
|
||||
|
||||
def __eq__(self, other):
|
||||
return (self.uri == other.uri and
|
||||
self.regex == other.regex and
|
||||
self.value == other.value and
|
||||
self.verb == other.verb and
|
||||
self.remain == other.remain and
|
||||
self.unit == other.unit and
|
||||
self.next_available == other.next_available)
|
||||
|
||||
def __repr__(self):
|
||||
return "<RateLimit: method=%s uri=%s>" % (self.method, self.uri)
|
||||
|
||||
|
||||
class AbsoluteLimit(object):
|
||||
"""Data model that represents a single absolute limit."""
|
||||
|
||||
def __init__(self, name, value):
|
||||
self.name = name
|
||||
self.value = value
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.value == other.value and self.name == other.name
|
||||
|
||||
def __repr__(self):
|
||||
return "<AbsoluteLimit: name=%s>" % (self.name)
|
||||
|
||||
|
||||
class LimitsManager(base.Manager):
|
||||
"""Manager object used to interact with limits resource."""
|
||||
|
||||
resource_class = Limits
|
||||
|
||||
def get(self):
|
||||
"""Get a specific extension.
|
||||
|
||||
:rtype: :class:`Limits`
|
||||
"""
|
||||
return self._get("/limits", "limits")
|
||||
sys.modules["manilaclient.v2.limits"] = MovedModule(limits)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# Copyright 2013 OpenStack LLC.
|
||||
# Copyright 2015 Chuck Fouts
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
|
@ -13,49 +14,22 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from manilaclient import base
|
||||
from manilaclient.openstack.common.apiclient import base as common_base
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from manilaclient.v2 import quota_classes
|
||||
|
||||
warnings.warn("Module manilaclient.v1.quota_classes is deprecated (taken as "
|
||||
"a basis for manilaclient.v2.quota_classes). "
|
||||
"The preferable way to get a client class or object is to use "
|
||||
"the manilaclient.client module.")
|
||||
|
||||
|
||||
class QuotaClassSet(common_base.Resource):
|
||||
class MovedModule(object):
|
||||
def __init__(self, new_module):
|
||||
self.new_module = new_module
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
"""Needed by base.Resource to self-refresh and be indexed."""
|
||||
return self.class_name
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.new_module, attr)
|
||||
|
||||
def update(self, *args, **kwargs):
|
||||
self.manager.update(self.class_name, *args, **kwargs)
|
||||
|
||||
|
||||
class QuotaClassSetManager(base.ManagerWithFind):
|
||||
resource_class = QuotaClassSet
|
||||
|
||||
def get(self, class_name):
|
||||
return self._get("/os-quota-class-sets/%s" % class_name,
|
||||
"quota_class_set")
|
||||
|
||||
def update(self,
|
||||
class_name,
|
||||
shares=None,
|
||||
gigabytes=None,
|
||||
snapshots=None,
|
||||
snapshot_gigabytes=None,
|
||||
share_networks=None):
|
||||
|
||||
body = {
|
||||
'quota_class_set': {
|
||||
'class_name': class_name,
|
||||
'shares': shares,
|
||||
'snapshots': snapshots,
|
||||
'gigabytes': gigabytes,
|
||||
'snapshot_gigabytes': snapshot_gigabytes,
|
||||
'share_networks': share_networks,
|
||||
}
|
||||
}
|
||||
|
||||
for key in list(body['quota_class_set']):
|
||||
if body['quota_class_set'][key] is None:
|
||||
body['quota_class_set'].pop(key)
|
||||
|
||||
self._update('/os-quota-class-sets/%s' % class_name, body)
|
||||
sys.modules["manilaclient.v2.quota_classes"] = MovedModule(quota_classes)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# Copyright 2013 OpenStack LLC.
|
||||
# Copyright 2015 Chuck Fouts
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
|
@ -13,66 +14,22 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from manilaclient import base
|
||||
from manilaclient.openstack.common.apiclient import base as common_base
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from manilaclient.v2 import quotas
|
||||
|
||||
warnings.warn("Module manilaclient.v1.quotas is deprecated (taken as "
|
||||
"a basis for manilaclient.v2.quotas). "
|
||||
"The preferable way to get a client class or object is to use "
|
||||
"the manilaclient.client module.")
|
||||
|
||||
|
||||
class QuotaSet(common_base.Resource):
|
||||
class MovedModule(object):
|
||||
def __init__(self, new_module):
|
||||
self.new_module = new_module
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
"""Needed by Resource to self-refresh and be indexed."""
|
||||
return self.tenant_id
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.new_module, attr)
|
||||
|
||||
def update(self, *args, **kwargs):
|
||||
self.manager.update(self.tenant_id, *args, **kwargs)
|
||||
|
||||
|
||||
class QuotaSetManager(base.ManagerWithFind):
|
||||
resource_class = QuotaSet
|
||||
|
||||
def get(self, tenant_id, user_id=None):
|
||||
if hasattr(tenant_id, 'tenant_id'):
|
||||
tenant_id = tenant_id.tenant_id
|
||||
if user_id:
|
||||
url = "/os-quota-sets/%s?user_id=%s" % (tenant_id, user_id)
|
||||
else:
|
||||
url = "/os-quota-sets/%s" % tenant_id
|
||||
return self._get(url, "quota_set")
|
||||
|
||||
def update(self, tenant_id, shares=None, snapshots=None,
|
||||
gigabytes=None, snapshot_gigabytes=None,
|
||||
share_networks=None, force=None, user_id=None):
|
||||
|
||||
body = {
|
||||
'quota_set': {
|
||||
'tenant_id': tenant_id,
|
||||
'shares': shares,
|
||||
'snapshots': snapshots,
|
||||
'gigabytes': gigabytes,
|
||||
'snapshot_gigabytes': snapshot_gigabytes,
|
||||
'share_networks': share_networks,
|
||||
'force': force,
|
||||
},
|
||||
}
|
||||
|
||||
for key in list(body['quota_set']):
|
||||
if body['quota_set'][key] is None:
|
||||
body['quota_set'].pop(key)
|
||||
if user_id:
|
||||
url = '/os-quota-sets/%s?user_id=%s' % (tenant_id, user_id)
|
||||
else:
|
||||
url = '/os-quota-sets/%s' % tenant_id
|
||||
|
||||
return self._update(url, body, 'quota_set')
|
||||
|
||||
def defaults(self, tenant_id):
|
||||
return self._get('/os-quota-sets/%s/defaults' % tenant_id,
|
||||
'quota_set')
|
||||
|
||||
def delete(self, tenant_id, user_id=None):
|
||||
if user_id:
|
||||
url = '/os-quota-sets/%s?user_id=%s' % (tenant_id, user_id)
|
||||
else:
|
||||
url = '/os-quota-sets/%s' % tenant_id
|
||||
self._delete(url)
|
||||
sys.modules["manilaclient.v2.quotas"] = MovedModule(quotas)
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# Copyright (c) 2015 Clinton Knight. All rights reserved.
|
||||
# Copyright (c) 2015 Clinton Knight.
|
||||
# Copyright 2015 Chuck Fouts
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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
|
||||
|
@ -12,51 +14,22 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import six.moves.urllib.parse as urlparse
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from manilaclient import base
|
||||
from manilaclient.openstack.common.apiclient import base as common_base
|
||||
from manilaclient.v2 import scheduler_stats
|
||||
|
||||
warnings.warn("Module manilaclient.v1.scheduler_stats is deprecated (taken as "
|
||||
"a basis for manilaclient.v2.scheduler_stats). "
|
||||
"The preferable way to get a client class or object is to use "
|
||||
"the manilaclient.client module.")
|
||||
|
||||
|
||||
RESOURCES_PATH = '/scheduler-stats/pools'
|
||||
RESOURCES_NAME = 'pools'
|
||||
class MovedModule(object):
|
||||
def __init__(self, new_module):
|
||||
self.new_module = new_module
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.new_module, attr)
|
||||
|
||||
class Pool(common_base.Resource):
|
||||
|
||||
def __repr__(self):
|
||||
return "<Pool: %s>" % self.name
|
||||
|
||||
|
||||
class PoolManager(base.Manager):
|
||||
"""Manage :class:`Pool` resources."""
|
||||
resource_class = Pool
|
||||
|
||||
def list(self, detailed=True, search_opts=None):
|
||||
"""Get a list of pools.
|
||||
|
||||
:rtype: list of :class:`Pool`
|
||||
"""
|
||||
if search_opts is None:
|
||||
search_opts = {}
|
||||
|
||||
if search_opts:
|
||||
query_string = urlparse.urlencode(
|
||||
sorted([(k, v) for (k, v) in list(search_opts.items()) if v]))
|
||||
if query_string:
|
||||
query_string = "?%s" % (query_string,)
|
||||
else:
|
||||
query_string = ''
|
||||
|
||||
if detailed:
|
||||
path = '%(resources_path)s/detail%(query)s' % {
|
||||
'resources_path': RESOURCES_PATH,
|
||||
'query': query_string
|
||||
}
|
||||
else:
|
||||
path = '%(resources_path)s%(query)s' % {
|
||||
'resources_path': RESOURCES_PATH,
|
||||
'query': query_string
|
||||
}
|
||||
|
||||
return self._list(path, RESOURCES_NAME)
|
||||
sys.modules["manilaclient.v2.scheduler_stats"] = MovedModule(scheduler_stats)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# Copyright 2013 OpenStack LLC.
|
||||
# Copyright 2015 Chuck Fouts
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
|
@ -13,158 +14,23 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
try:
|
||||
from urllib import urlencode # noqa
|
||||
except ImportError:
|
||||
from urllib.parse import urlencode # noqa
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
import six
|
||||
from manilaclient.v2 import security_services
|
||||
|
||||
from manilaclient import base
|
||||
from manilaclient import exceptions
|
||||
from manilaclient.openstack.common.apiclient import base as common_base
|
||||
|
||||
RESOURCES_PATH = '/security-services'
|
||||
RESOURCE_PATH = "/security-services/%s"
|
||||
RESOURCE_NAME = 'security_service'
|
||||
RESOURCES_NAME = 'security_services'
|
||||
warnings.warn("Module manilaclient.v1.security_services is deprecated (taken "
|
||||
"as a basis for manilaclient.v2.security_services). "
|
||||
"The preferable way to get a client class or object is to use "
|
||||
"the manilaclient.client module.")
|
||||
|
||||
|
||||
class SecurityService(common_base.Resource):
|
||||
"""Security service for Manila shares."""
|
||||
def __repr__(self):
|
||||
return "<SecurityService: %s>" % self.id
|
||||
class MovedModule(object):
|
||||
def __init__(self, new_module):
|
||||
self.new_module = new_module
|
||||
|
||||
def update(self, **kwargs):
|
||||
"""Update this security service."""
|
||||
return self.manager.update(self, **kwargs)
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.new_module, attr)
|
||||
|
||||
def delete(self):
|
||||
""""Delete this security service."""
|
||||
self.manager.delete(self)
|
||||
|
||||
|
||||
class SecurityServiceManager(base.ManagerWithFind):
|
||||
"""Manage :class:`SecurityService` resources."""
|
||||
|
||||
resource_class = SecurityService
|
||||
|
||||
def create(self, type, dns_ip=None, server=None, domain=None, user=None,
|
||||
password=None, name=None, description=None):
|
||||
"""Create security service for NAS.
|
||||
|
||||
:param type: security service type - 'ldap', 'kerberos' or
|
||||
'active_directory'
|
||||
:param dns_ip: dns ip address used inside tenant's network
|
||||
:param server: security service server ip address or hostname
|
||||
:param domain: security service domain
|
||||
:param user: security identifier used by tenant
|
||||
:param password: password used by user
|
||||
:param name: security service name
|
||||
:param description: security service description
|
||||
:rtype: :class:`SecurityService`
|
||||
"""
|
||||
values = {'type': type}
|
||||
if dns_ip:
|
||||
values['dns_ip'] = dns_ip
|
||||
if server:
|
||||
values['server'] = server
|
||||
if domain:
|
||||
values['domain'] = domain
|
||||
if user:
|
||||
values['user'] = user
|
||||
if password:
|
||||
values['password'] = password
|
||||
if name:
|
||||
values['name'] = name
|
||||
if description:
|
||||
values['description'] = description
|
||||
|
||||
body = {RESOURCE_NAME: values}
|
||||
|
||||
return self._create(RESOURCES_PATH, body, RESOURCE_NAME)
|
||||
|
||||
def get(self, security_service):
|
||||
"""Get a security service info.
|
||||
|
||||
:param security_service: security service to get.
|
||||
:rtype: :class:`SecurityService`
|
||||
"""
|
||||
return self._get(
|
||||
RESOURCE_PATH % common_base.getid(security_service),
|
||||
RESOURCE_NAME,
|
||||
)
|
||||
|
||||
def update(self, security_service, dns_ip=None, server=None, domain=None,
|
||||
password=None, user=None, name=None, description=None):
|
||||
"""Updates a security service.
|
||||
|
||||
:param security_service: security service to update.
|
||||
:param dns_ip: dns ip address used inside tenant's network
|
||||
:param server: security service server ip address or hostname
|
||||
:param domain: security service domain
|
||||
:param user: security identifier used by tenant
|
||||
:param password: password used by user
|
||||
:param name: security service name
|
||||
:param description: security service description
|
||||
:rtype: :class:`SecurityService`
|
||||
"""
|
||||
|
||||
values = {}
|
||||
if dns_ip is not None:
|
||||
values['dns_ip'] = dns_ip
|
||||
if server is not None:
|
||||
values['server'] = server
|
||||
if domain is not None:
|
||||
values['domain'] = domain
|
||||
if user is not None:
|
||||
values['user'] = user
|
||||
if password is not None:
|
||||
values['password'] = password
|
||||
if name is not None:
|
||||
values['name'] = name
|
||||
if description is not None:
|
||||
values['description'] = description
|
||||
|
||||
for k, v in six.iteritems(values):
|
||||
if v == '':
|
||||
values[k] = None
|
||||
|
||||
if not values:
|
||||
msg = "Must specify fields to be updated"
|
||||
raise exceptions.CommandError(msg)
|
||||
|
||||
body = {RESOURCE_NAME: values}
|
||||
|
||||
return self._update(
|
||||
RESOURCE_PATH % common_base.getid(security_service),
|
||||
body,
|
||||
RESOURCE_NAME,
|
||||
)
|
||||
|
||||
def delete(self, security_service):
|
||||
"""Delete a security service.
|
||||
|
||||
:param security_service: security service to be deleted.
|
||||
"""
|
||||
self._delete(RESOURCE_PATH % common_base.getid(security_service))
|
||||
|
||||
def list(self, detailed=True, search_opts=None):
|
||||
"""Get a list of all security services.
|
||||
|
||||
:rtype: list of :class:`SecurityService`
|
||||
"""
|
||||
if search_opts:
|
||||
query_string = urlencode(
|
||||
sorted([(k, v) for (k, v) in list(search_opts.items()) if v]))
|
||||
if query_string:
|
||||
query_string = "?%s" % query_string
|
||||
else:
|
||||
query_string = ''
|
||||
|
||||
if detailed:
|
||||
path = RESOURCES_PATH + "/detail" + query_string
|
||||
else:
|
||||
path = RESOURCES_PATH + query_string
|
||||
|
||||
return self._list(path, RESOURCES_NAME)
|
||||
sys.modules[
|
||||
"manilaclient.v2.security_services"] = MovedModule(security_services)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# Copyright 2014 OpenStack LLC.
|
||||
# Copyright 2015 Chuck Fouts
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
|
@ -13,56 +14,22 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import six
|
||||
try:
|
||||
from urllib import urlencode # noqa
|
||||
except ImportError:
|
||||
from urllib.parse import urlencode # noqa
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from manilaclient import base
|
||||
from manilaclient.openstack.common.apiclient import base as common_base
|
||||
from manilaclient.v2 import services
|
||||
|
||||
RESOURCES_PATH = '/os-services'
|
||||
RESOURCES_NAME = 'services'
|
||||
warnings.warn("Module manilaclient.v1.services is deprecated (taken as "
|
||||
"a basis for manilaclient.v2.services). "
|
||||
"The preferable way to get a client class or object is to use "
|
||||
"the manilaclient.client module.")
|
||||
|
||||
|
||||
class Service(common_base.Resource):
|
||||
class MovedModule(object):
|
||||
def __init__(self, new_module):
|
||||
self.new_module = new_module
|
||||
|
||||
def __repr__(self):
|
||||
return "<Service: %s>" % self.id
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.new_module, attr)
|
||||
|
||||
def api_version(self):
|
||||
"""Get api version."""
|
||||
return self.manager.api_version(self)
|
||||
|
||||
|
||||
class ServiceManager(base.Manager):
|
||||
"""Manage :class:`Service` resources."""
|
||||
resource_class = Service
|
||||
|
||||
def list(self, search_opts=None):
|
||||
"""Get a list of all services.
|
||||
|
||||
:rtype: list of :class:`Service`
|
||||
"""
|
||||
query_string = ''
|
||||
if search_opts:
|
||||
query_string = urlencode(
|
||||
sorted([(k, v) for (k, v) in six.iteritems(search_opts) if v]))
|
||||
if query_string:
|
||||
query_string = "?%s" % query_string
|
||||
return self._list(RESOURCES_PATH + query_string, RESOURCES_NAME)
|
||||
|
||||
def enable(self, host, binary):
|
||||
"""Enable the service specified by hostname and binary."""
|
||||
body = {"host": host, "binary": binary}
|
||||
return self._update("/os-services/enable", body)
|
||||
|
||||
def disable(self, host, binary):
|
||||
"""Disable the service specified by hostname and binary."""
|
||||
body = {"host": host, "binary": binary}
|
||||
return self._update("/os-services/disable", body)
|
||||
|
||||
def api_version(self):
|
||||
"""Get api version."""
|
||||
return self._get_with_base_url("", "versions")
|
||||
sys.modules["manilaclient.v2.services"] = MovedModule(services)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# Copyright 2013 OpenStack LLC.
|
||||
# Copyright 2015 Chuck Fouts
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
|
@ -13,169 +14,22 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
try:
|
||||
from urllib import urlencode # noqa
|
||||
except ImportError:
|
||||
from urllib.parse import urlencode # noqa
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
import six
|
||||
from manilaclient.v2 import share_networks
|
||||
|
||||
from manilaclient import base
|
||||
from manilaclient import exceptions
|
||||
from manilaclient.openstack.common.apiclient import base as common_base
|
||||
|
||||
RESOURCES_PATH = '/share-networks'
|
||||
RESOURCE_PATH = "/share-networks/%s"
|
||||
RESOURCE_NAME = 'share_network'
|
||||
RESOURCES_NAME = 'share_networks'
|
||||
warnings.warn("Module manilaclient.v1.share_networks is deprecated (taken as "
|
||||
"a basis for manilaclient.v2.share_networks). "
|
||||
"The preferable way to get a client class or object is to use "
|
||||
"the manilaclient.client module.")
|
||||
|
||||
|
||||
class ShareNetwork(common_base.Resource):
|
||||
"""Network info for Manila shares."""
|
||||
def __repr__(self):
|
||||
return "<ShareNetwork: %s>" % self.id
|
||||
class MovedModule(object):
|
||||
def __init__(self, new_module):
|
||||
self.new_module = new_module
|
||||
|
||||
def update(self, **kwargs):
|
||||
"""Update this share network."""
|
||||
return self.manager.update(self, **kwargs)
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.new_module, attr)
|
||||
|
||||
def delete(self):
|
||||
"""Delete this share network."""
|
||||
self.manager.delete(self)
|
||||
|
||||
|
||||
class ShareNetworkManager(base.ManagerWithFind):
|
||||
"""Manage :class:`ShareNetwork` resources."""
|
||||
resource_class = ShareNetwork
|
||||
|
||||
def create(self, neutron_net_id=None, neutron_subnet_id=None,
|
||||
nova_net_id=None, name=None, description=None):
|
||||
"""Create share network.
|
||||
|
||||
:param neutron_net_id: ID of Neutron network
|
||||
:param neutron_subnet_id: ID of Neutron subnet
|
||||
:param nova_net_id: ID of Nova network
|
||||
:param name: share network name
|
||||
:param description: share network description
|
||||
:rtype: :class:`ShareNetwork`
|
||||
"""
|
||||
values = {}
|
||||
if neutron_net_id:
|
||||
values['neutron_net_id'] = neutron_net_id
|
||||
if neutron_subnet_id:
|
||||
values['neutron_subnet_id'] = neutron_subnet_id
|
||||
if nova_net_id:
|
||||
values['nova_net_id'] = nova_net_id
|
||||
if name:
|
||||
values['name'] = name
|
||||
if description:
|
||||
values['description'] = description
|
||||
|
||||
body = {RESOURCE_NAME: values}
|
||||
|
||||
return self._create(RESOURCES_PATH, body, RESOURCE_NAME)
|
||||
|
||||
def add_security_service(self, share_network, security_service):
|
||||
"""Associate given security service with a share network.
|
||||
|
||||
:param share_network: share network name, id or ShareNetwork instance
|
||||
:param security_service: name, id or SecurityService instance
|
||||
:rtype: :class:`ShareNetwork`
|
||||
"""
|
||||
body = {
|
||||
'add_security_service': {
|
||||
'security_service_id': common_base.getid(security_service),
|
||||
},
|
||||
}
|
||||
return self._create(
|
||||
RESOURCE_PATH % common_base.getid(share_network) + '/action',
|
||||
body,
|
||||
RESOURCE_NAME,
|
||||
)
|
||||
|
||||
def remove_security_service(self, share_network, security_service):
|
||||
"""Dissociate security service from a share network.
|
||||
|
||||
:param share_network: share network name, id or ShareNetwork instance
|
||||
:param security_service: name, id or SecurityService instance
|
||||
:rtype: :class:`ShareNetwork`
|
||||
"""
|
||||
body = {
|
||||
'remove_security_service': {
|
||||
'security_service_id': common_base.getid(security_service),
|
||||
},
|
||||
}
|
||||
return self._create(
|
||||
RESOURCE_PATH % common_base.getid(share_network) + '/action',
|
||||
body,
|
||||
RESOURCE_NAME,
|
||||
)
|
||||
|
||||
def get(self, share_network):
|
||||
"""Get a share network.
|
||||
|
||||
:param policy: share network to get.
|
||||
:rtype: :class:`NetworkInfo`
|
||||
"""
|
||||
return self._get(RESOURCE_PATH % common_base.getid(share_network),
|
||||
RESOURCE_NAME)
|
||||
|
||||
def update(self, share_network, neutron_net_id=None,
|
||||
neutron_subnet_id=None, nova_net_id=None,
|
||||
name=None, description=None):
|
||||
"""Updates a share network.
|
||||
|
||||
:param share_network: share network to update.
|
||||
:rtype: :class:`ShareNetwork`
|
||||
"""
|
||||
values = {}
|
||||
if neutron_net_id is not None:
|
||||
values['neutron_net_id'] = neutron_net_id
|
||||
if neutron_subnet_id is not None:
|
||||
values['neutron_subnet_id'] = neutron_subnet_id
|
||||
if nova_net_id is not None:
|
||||
values['nova_net_id'] = nova_net_id
|
||||
if name is not None:
|
||||
values['name'] = name
|
||||
if description is not None:
|
||||
values['description'] = description
|
||||
|
||||
for k, v in six.iteritems(values):
|
||||
if v == '':
|
||||
values[k] = None
|
||||
|
||||
if not values:
|
||||
msg = "Must specify fields to be updated"
|
||||
raise exceptions.CommandError(msg)
|
||||
|
||||
body = {RESOURCE_NAME: values}
|
||||
return self._update(RESOURCE_PATH % common_base.getid(share_network),
|
||||
body,
|
||||
RESOURCE_NAME)
|
||||
|
||||
def delete(self, share_network):
|
||||
"""Delete a share network.
|
||||
|
||||
:param share_network: share network to be deleted.
|
||||
"""
|
||||
self._delete(RESOURCE_PATH % common_base.getid(share_network))
|
||||
|
||||
def list(self, detailed=True, search_opts=None):
|
||||
"""Get a list of all share network.
|
||||
|
||||
:rtype: list of :class:`NetworkInfo`
|
||||
"""
|
||||
if search_opts:
|
||||
query_string = urlencode(
|
||||
sorted([(k, v) for (k, v) in list(search_opts.items()) if v]))
|
||||
if query_string:
|
||||
query_string = "?%s" % query_string
|
||||
else:
|
||||
query_string = ''
|
||||
|
||||
if detailed:
|
||||
path = RESOURCES_PATH + "/detail" + query_string
|
||||
else:
|
||||
path = RESOURCES_PATH + query_string
|
||||
|
||||
return self._list(path, RESOURCES_NAME)
|
||||
sys.modules["manilaclient.v2.share_networks"] = MovedModule(share_networks)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# Copyright 2014 OpenStack Foundation.
|
||||
# Copyright 2014 OpenStack LLC.
|
||||
# Copyright 2015 Chuck Fouts
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
|
@ -13,80 +14,22 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import six
|
||||
try:
|
||||
from urllib import urlencode # noqa
|
||||
except ImportError:
|
||||
from urllib.parse import urlencode # noqa
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from manilaclient import base
|
||||
from manilaclient.openstack.common.apiclient import base as common_base
|
||||
from manilaclient.v2 import share_servers
|
||||
|
||||
RESOURCES_PATH = '/share-servers'
|
||||
RESOURCE_PATH = '/share-servers/%s'
|
||||
RESOURCES_NAME = 'share_servers'
|
||||
RESOURCE_NAME = 'share_server'
|
||||
warnings.warn("Module manilaclient.v1.share_servers is deprecated (taken as "
|
||||
"a basis for manilaclient.v2.share_servers). "
|
||||
"The preferable way to get a client class or object is to use "
|
||||
"the manilaclient.client module.")
|
||||
|
||||
|
||||
class ShareServer(common_base.Resource):
|
||||
|
||||
def __repr__(self):
|
||||
return "<ShareServer: %s>" % self.id
|
||||
class MovedModule(object):
|
||||
def __init__(self, new_module):
|
||||
self.new_module = new_module
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if attr == 'share_network':
|
||||
attr = 'share_network_name'
|
||||
return super(ShareServer, self).__getattr__(attr)
|
||||
return getattr(self.new_module, attr)
|
||||
|
||||
|
||||
class ShareServerManager(base.Manager):
|
||||
"""Manage :class:`ShareServer` resources."""
|
||||
resource_class = ShareServer
|
||||
|
||||
def get(self, server_id):
|
||||
"""Get a share server.
|
||||
|
||||
:param server_id: The ID of the share server to get.
|
||||
:rtype: :class:`ShareServer`
|
||||
"""
|
||||
server = self._get("%s/%s" % (RESOURCES_PATH, server_id),
|
||||
RESOURCE_NAME)
|
||||
# Split big dict 'backend_details' to separated strings
|
||||
# as next:
|
||||
# +---------------------+------------------------------------+
|
||||
# | Property | Value |
|
||||
# +---------------------+------------------------------------+
|
||||
# | details:instance_id |35203a78-c733-4b1f-b82c-faded312e537|
|
||||
# +---------------------+------------------------------------+
|
||||
for k, v in six.iteritems(server._info["backend_details"]):
|
||||
server._info["details:%s" % k] = v
|
||||
return server
|
||||
|
||||
def details(self, server_id):
|
||||
"""Get a share server details.
|
||||
|
||||
:param server_id: The ID of the share server to get details from.
|
||||
:rtype: list of :class:`ShareServerBackendDetails
|
||||
"""
|
||||
return self._get("%s/%s/details" % (RESOURCES_PATH, server_id),
|
||||
"details")
|
||||
|
||||
def delete(self, server_id):
|
||||
"""Delete share server.
|
||||
|
||||
:param server_id: id of share server to be deleted.
|
||||
"""
|
||||
self._delete(RESOURCE_PATH % server_id)
|
||||
|
||||
def list(self, search_opts=None):
|
||||
"""Get a list of share servers.
|
||||
|
||||
:rtype: list of :class:`ShareServer`
|
||||
"""
|
||||
query_string = ''
|
||||
if search_opts:
|
||||
opts = sorted(
|
||||
[(k, v) for (k, v) in six.iteritems(search_opts) if v])
|
||||
query_string = urlencode(opts)
|
||||
query_string = '?' + query_string if query_string else ''
|
||||
return self._list(RESOURCES_PATH + query_string, RESOURCES_NAME)
|
||||
sys.modules["manilaclient.v2.share_servers"] = MovedModule(share_servers)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# Copyright 2012 NetApp
|
||||
# Copyright 2015 Chuck Fouts
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
|
@ -12,145 +13,23 @@
|
|||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""Interface for shares extension."""
|
||||
|
||||
try:
|
||||
from urllib import urlencode # noqa
|
||||
except ImportError:
|
||||
from urllib.parse import urlencode # noqa
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from manilaclient import base
|
||||
from manilaclient.common import constants
|
||||
from manilaclient.openstack.common.apiclient import base as common_base
|
||||
from manilaclient.v2 import share_snapshots
|
||||
|
||||
warnings.warn("Module manilaclient.v1.share_snapshots is deprecated (taken as "
|
||||
"a basis for manilaclient.v2.share_snapshots). "
|
||||
"The preferable way to get a client class or object is to use "
|
||||
"the manilaclient.client module.")
|
||||
|
||||
|
||||
class ShareSnapshot(common_base.Resource):
|
||||
"""Represent a snapshot of a share."""
|
||||
class MovedModule(object):
|
||||
def __init__(self, new_module):
|
||||
self.new_module = new_module
|
||||
|
||||
def __repr__(self):
|
||||
return "<ShareSnapshot: %s>" % self.id
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.new_module, attr)
|
||||
|
||||
def update(self, **kwargs):
|
||||
"""Update this snapshot."""
|
||||
self.manager.update(self, **kwargs)
|
||||
|
||||
def reset_state(self, state):
|
||||
"""Update the snapshot with the privided state."""
|
||||
self.manager.reset_state(self, state)
|
||||
|
||||
def delete(self):
|
||||
"""Delete this snapshot."""
|
||||
self.manager.delete(self)
|
||||
|
||||
def force_delete(self):
|
||||
"""Delete the specified snapshot ignoring its current state."""
|
||||
self.manager.force_delete(self)
|
||||
|
||||
|
||||
class ShareSnapshotManager(base.ManagerWithFind):
|
||||
"""Manage :class:`ShareSnapshot` resources."""
|
||||
resource_class = ShareSnapshot
|
||||
|
||||
def create(self, share, force=False, name=None, description=None):
|
||||
"""Create a snapshot of the given share.
|
||||
|
||||
:param share_id: The ID of the share to snapshot.
|
||||
:param force: If force is True, create a snapshot even if the
|
||||
share is busy. Default is False.
|
||||
:param name: Name of the snapshot
|
||||
:param description: Description of the snapshot
|
||||
:rtype: :class:`ShareSnapshot`
|
||||
"""
|
||||
body = {'snapshot': {'share_id': common_base.getid(share),
|
||||
'force': force,
|
||||
'name': name,
|
||||
'description': description}}
|
||||
return self._create('/snapshots', body, 'snapshot')
|
||||
|
||||
def get(self, snapshot):
|
||||
"""Get a snapshot.
|
||||
|
||||
:param snapshot: The :class:`ShareSnapshot` instance or string with ID
|
||||
of snapshot to delete.
|
||||
:rtype: :class:`ShareSnapshot`
|
||||
"""
|
||||
snapshot_id = common_base.getid(snapshot)
|
||||
return self._get('/snapshots/%s' % snapshot_id, 'snapshot')
|
||||
|
||||
def list(self, detailed=True, search_opts=None, sort_key=None,
|
||||
sort_dir=None):
|
||||
"""Get a list of snapshots of shares.
|
||||
|
||||
:param search_opts: Search options to filter out shares.
|
||||
:param sort_key: Key to be sorted.
|
||||
:param sort_dir: Sort direction, should be 'desc' or 'asc'.
|
||||
:rtype: list of :class:`ShareSnapshot`
|
||||
"""
|
||||
if search_opts is None:
|
||||
search_opts = {}
|
||||
|
||||
if sort_key is not None:
|
||||
if sort_key in constants.SNAPSHOT_SORT_KEY_VALUES:
|
||||
search_opts['sort_key'] = sort_key
|
||||
else:
|
||||
raise ValueError(
|
||||
'sort_key must be one of the following: %s.'
|
||||
% ', '.join(constants.SNAPSHOT_SORT_KEY_VALUES))
|
||||
|
||||
if sort_dir is not None:
|
||||
if sort_dir in constants.SORT_DIR_VALUES:
|
||||
search_opts['sort_dir'] = sort_dir
|
||||
else:
|
||||
raise ValueError(
|
||||
'sort_dir must be one of the following: %s.'
|
||||
% ', '.join(constants.SORT_DIR_VALUES))
|
||||
|
||||
if search_opts:
|
||||
query_string = urlencode(
|
||||
sorted([(k, v) for (k, v) in list(search_opts.items()) if v]))
|
||||
if query_string:
|
||||
query_string = "?%s" % (query_string,)
|
||||
else:
|
||||
query_string = ''
|
||||
|
||||
if detailed:
|
||||
path = "/snapshots/detail%s" % (query_string,)
|
||||
else:
|
||||
path = "/snapshots%s" % (query_string,)
|
||||
|
||||
return self._list(path, 'snapshots')
|
||||
|
||||
def delete(self, snapshot):
|
||||
"""Delete a snapshot of a share.
|
||||
|
||||
:param snapshot: The :class:`ShareSnapshot` to delete.
|
||||
"""
|
||||
self._delete("/snapshots/%s" % common_base.getid(snapshot))
|
||||
|
||||
def force_delete(self, snapshot):
|
||||
return self._action('os-force_delete', common_base.getid(snapshot))
|
||||
|
||||
def update(self, snapshot, **kwargs):
|
||||
"""Update a snapshot.
|
||||
|
||||
:param snapshot: The :class:`ShareSnapshot` instance or string with ID
|
||||
of snapshot to delete.
|
||||
:rtype: :class:`ShareSnapshot`
|
||||
"""
|
||||
if not kwargs:
|
||||
return
|
||||
|
||||
body = {'snapshot': kwargs, }
|
||||
snapshot_id = common_base.getid(snapshot)
|
||||
return self._update("/snapshots/%s" % snapshot_id, body)
|
||||
|
||||
def reset_state(self, snapshot, state):
|
||||
"""Update the specified share snapshot with the provided state."""
|
||||
return self._action('os-reset_status', snapshot, {'status': state})
|
||||
|
||||
def _action(self, action, snapshot, info=None, **kwargs):
|
||||
"""Perform a snapshot 'action'."""
|
||||
body = {action: info}
|
||||
self.run_hooks('modify_body_for_action', body, **kwargs)
|
||||
url = '/snapshots/%s/action' % common_base.getid(snapshot)
|
||||
return self.api.client.post(url, body=body)
|
||||
sys.modules["manilaclient.v2.share_snapshots"] = MovedModule(share_snapshots)
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
# Copyright 2014 OpenStack Foundation
|
||||
# Copyright 2015 Chuck Fouts
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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
|
||||
|
@ -12,43 +14,23 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Share type access interface."""
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from manilaclient import base
|
||||
from manilaclient.openstack.common.apiclient import base as common_base
|
||||
from manilaclient.v2 import share_type_access
|
||||
|
||||
warnings.warn("Module manilaclient.v1.share_type_access is deprecated (taken "
|
||||
"as a basis for manilaclient.v2.share_type_access). "
|
||||
"The preferable way to get a client class or object is to use "
|
||||
"the manilaclient.client module.")
|
||||
|
||||
|
||||
class ShareTypeAccess(common_base.Resource):
|
||||
def __repr__(self):
|
||||
return "<ShareTypeAccess: %s>" % self.id
|
||||
class MovedModule(object):
|
||||
def __init__(self, new_module):
|
||||
self.new_module = new_module
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.new_module, attr)
|
||||
|
||||
class ShareTypeAccessManager(base.ManagerWithFind):
|
||||
"""Manage :class:`ShareTypeAccess` resources."""
|
||||
|
||||
resource_class = ShareTypeAccess
|
||||
|
||||
def list(self, share_type):
|
||||
if share_type.is_public:
|
||||
return None
|
||||
|
||||
return self._list(
|
||||
'/types/%s/os-share-type-access' % common_base.getid(share_type),
|
||||
'share_type_access')
|
||||
|
||||
def add_project_access(self, share_type, project):
|
||||
"""Add a project to the given share type access list."""
|
||||
info = {'project': project}
|
||||
self._action('addProjectAccess', share_type, info)
|
||||
|
||||
def remove_project_access(self, share_type, project):
|
||||
"""Remove a project from the given share type access list."""
|
||||
info = {'project': project}
|
||||
self._action('removeProjectAccess', share_type, info)
|
||||
|
||||
def _action(self, action, share_type, info, **kwargs):
|
||||
"""Perform a share type action."""
|
||||
body = {action: info}
|
||||
self.run_hooks('modify_body_for_action', body, **kwargs)
|
||||
url = '/types/%s/action' % common_base.getid(share_type)
|
||||
return self.api.client.post(url, body=body)
|
||||
sys.modules[
|
||||
"manilaclient.v2.share_type_access"] = MovedModule(share_type_access)
|
||||
|
|
|
@ -1,153 +1,35 @@
|
|||
# Copyright (c) 2011 Rackspace US, Inc.
|
||||
# Copyright 2015 Chuck Fouts
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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
|
||||
# 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
|
||||
# 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.
|
||||
# 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 sys
|
||||
import warnings
|
||||
|
||||
from manilaclient.v2 import share_types
|
||||
|
||||
warnings.warn("Module manilaclient.v1.share_types is deprecated (taken as "
|
||||
"a basis for manilaclient.v2.share_types). "
|
||||
"The preferable way to get a client class or object is to use "
|
||||
"the manilaclient.client module.")
|
||||
|
||||
|
||||
"""
|
||||
Share Type interface.
|
||||
"""
|
||||
class MovedModule(object):
|
||||
def __init__(self, new_module):
|
||||
self.new_module = new_module
|
||||
|
||||
from manilaclient import base
|
||||
from manilaclient.openstack.common.apiclient import base as common_base
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.new_module, attr)
|
||||
|
||||
|
||||
class ShareType(common_base.Resource):
|
||||
"""A Share Type is the type of share to be created."""
|
||||
|
||||
def __init__(self, manager, info, loaded=False):
|
||||
super(ShareType, self).__init__(manager, info, loaded)
|
||||
self._required_extra_specs = info.get('required_extra_specs', {})
|
||||
self._optional_extra_specs = {
|
||||
'snapshot_support': info.get('extra_specs', {}).get(
|
||||
'snapshot_support', 'unknown'),
|
||||
}
|
||||
|
||||
def __repr__(self):
|
||||
return "<ShareType: %s>" % self.name
|
||||
|
||||
@property
|
||||
def is_public(self):
|
||||
"""Provide a user-friendly accessor to os-share-type-access."""
|
||||
return self._info.get("os-share-type-access:is_public", 'N/A')
|
||||
|
||||
def get_keys(self, prefer_resource_data=True):
|
||||
"""Get extra specs from a share type.
|
||||
|
||||
:param prefer_resource_data: By default extra_specs are retrieved from
|
||||
resource data, but user can force this method to make API call.
|
||||
:return: dict with extra specs
|
||||
"""
|
||||
extra_specs = getattr(self, 'extra_specs', None)
|
||||
|
||||
if prefer_resource_data and extra_specs:
|
||||
return extra_specs
|
||||
|
||||
_resp, body = self.manager.api.client.get(
|
||||
"/types/%s/extra_specs" % common_base.getid(self))
|
||||
|
||||
self.extra_specs = body["extra_specs"]
|
||||
|
||||
return body["extra_specs"]
|
||||
|
||||
def get_required_keys(self):
|
||||
return self._required_extra_specs
|
||||
|
||||
def get_optional_keys(self):
|
||||
return self._optional_extra_specs
|
||||
|
||||
def set_keys(self, metadata):
|
||||
"""Set extra specs on a share type.
|
||||
|
||||
:param type : The :class:`ShareType` to set extra spec on
|
||||
:param metadata: A dict of key/value pairs to be set
|
||||
"""
|
||||
body = {'extra_specs': metadata}
|
||||
return self.manager._create(
|
||||
"/types/%s/extra_specs" % common_base.getid(self),
|
||||
body,
|
||||
"extra_specs",
|
||||
return_raw=True,
|
||||
)
|
||||
|
||||
def unset_keys(self, keys):
|
||||
"""Unset extra specs on a share type.
|
||||
|
||||
:param type_id: The :class:`ShareType` to unset extra spec on
|
||||
:param keys: A list of keys to be unset
|
||||
"""
|
||||
|
||||
# NOTE(jdg): This wasn't actually doing all of the keys before
|
||||
# the return in the loop resulted in ony ONE key being unset.
|
||||
# since on success the return was NONE, we'll only interrupt the loop
|
||||
# and return if there's an error
|
||||
resp = None
|
||||
for k in keys:
|
||||
resp = self.manager._delete(
|
||||
"/types/%s/extra_specs/%s" % (common_base.getid(self), k))
|
||||
if resp is not None:
|
||||
return resp
|
||||
|
||||
|
||||
class ShareTypeManager(base.ManagerWithFind):
|
||||
"""Manage :class:`ShareType` resources."""
|
||||
|
||||
resource_class = ShareType
|
||||
|
||||
def list(self, search_opts=None, show_all=True):
|
||||
"""Get a list of all share types.
|
||||
|
||||
:rtype: list of :class:`ShareType`.
|
||||
"""
|
||||
query_string = ''
|
||||
if show_all:
|
||||
query_string = '?is_public=all'
|
||||
return self._list("/types%s" % query_string, "share_types")
|
||||
|
||||
def get(self, share_type="default"):
|
||||
"""Get a specific share type.
|
||||
|
||||
:param share_type: The ID of the :class:`ShareType` to get.
|
||||
:rtype: :class:`ShareType`
|
||||
"""
|
||||
return self._get("/types/%s" % common_base.getid(share_type),
|
||||
"share_type")
|
||||
|
||||
def delete(self, share_type):
|
||||
"""Delete a specific share_type.
|
||||
|
||||
:param share_type: The name or ID of the :class:`ShareType` to get.
|
||||
"""
|
||||
self._delete("/types/%s" % common_base.getid(share_type))
|
||||
|
||||
def create(self, name, spec_driver_handles_share_servers,
|
||||
spec_snapshot_support=True, is_public=True):
|
||||
"""Create a share type.
|
||||
|
||||
:param name: Descriptive name of the share type
|
||||
:rtype: :class:`ShareType`
|
||||
"""
|
||||
|
||||
body = {
|
||||
"share_type": {
|
||||
"name": name,
|
||||
"os-share-type-access:is_public": is_public,
|
||||
"extra_specs": {
|
||||
"driver_handles_share_servers":
|
||||
spec_driver_handles_share_servers,
|
||||
"snapshot_support": spec_snapshot_support,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return self._create("/types", body, "share_type")
|
||||
sys.modules["manilaclient.v2.share_types"] = MovedModule(share_types)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
# Copyright 2012 NetApp
|
||||
# Copyright 2015 Chuck Fouts
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
|
@ -12,457 +13,23 @@
|
|||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""Interface for shares extension."""
|
||||
|
||||
import collections
|
||||
import re
|
||||
try:
|
||||
from urllib import urlencode # noqa
|
||||
except ImportError:
|
||||
from urllib.parse import urlencode # noqa
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from manilaclient import base
|
||||
from manilaclient.common import constants
|
||||
from manilaclient import exceptions
|
||||
from manilaclient.openstack.common.apiclient import base as common_base
|
||||
from manilaclient.v1 import share_instances
|
||||
from manilaclient.v2 import shares
|
||||
|
||||
warnings.warn("Module manilaclient.v1.shares is deprecated (taken as "
|
||||
"a basis for manilaclient.v2.shares). "
|
||||
"The preferable way to get a client class or object is to use "
|
||||
"the manilaclient.client module.")
|
||||
|
||||
class Share(common_base.Resource):
|
||||
"""A share is an extra block level storage to the OpenStack instances."""
|
||||
def __repr__(self):
|
||||
return "<Share: %s>" % self.id
|
||||
|
||||
def update(self, **kwargs):
|
||||
"""Update this share."""
|
||||
self.manager.update(self, **kwargs)
|
||||
class MovedModule(object):
|
||||
def __init__(self, new_module):
|
||||
self.new_module = new_module
|
||||
|
||||
def unmanage(self, **kwargs):
|
||||
"""Unmanage this share."""
|
||||
self.manager.unmanage(self, **kwargs)
|
||||
def __getattr__(self, attr):
|
||||
return getattr(self.new_module, attr)
|
||||
|
||||
def migrate_share(self, host, force_host_copy):
|
||||
"""Migrate the share to a new host."""
|
||||
self.manager.migrate_share(self, host, force_host_copy)
|
||||
|
||||
def delete(self, consistency_group_id=None):
|
||||
"""Delete this share."""
|
||||
self.manager.delete(self, consistency_group_id=consistency_group_id)
|
||||
|
||||
def force_delete(self):
|
||||
"""Delete the specified share ignoring its current state."""
|
||||
self.manager.force_delete(self)
|
||||
|
||||
def allow(self, access_type, access, access_level):
|
||||
"""Allow access to a share."""
|
||||
self._validate_access(access_type, access)
|
||||
return self.manager.allow(self, access_type, access, access_level)
|
||||
|
||||
def deny(self, id):
|
||||
"""Deny access from IP to a share."""
|
||||
return self.manager.deny(self, id)
|
||||
|
||||
def access_list(self):
|
||||
"""Deny access from IP to a share."""
|
||||
return self.manager.access_list(self)
|
||||
|
||||
def _validate_access(self, access_type, access):
|
||||
if access_type == 'ip':
|
||||
self._validate_ip_range(access)
|
||||
elif access_type == 'user':
|
||||
self._validate_username(access)
|
||||
elif access_type == 'cert':
|
||||
# 'access' is used as the certificate's CN (common name)
|
||||
# to which access is allowed or denied by the backend.
|
||||
# The standard allows for just about any string in the
|
||||
# common name. The meaning of a string depends on its
|
||||
# interpretation and is limited to 64 characters.
|
||||
self._validate_common_name(access.strip())
|
||||
else:
|
||||
raise exceptions.CommandError(
|
||||
'Only ip, user, and cert types are supported')
|
||||
|
||||
def update_all_metadata(self, metadata):
|
||||
"""Update all metadata of this share."""
|
||||
return self.manager.update_all_metadata(self, metadata)
|
||||
|
||||
@staticmethod
|
||||
def _validate_common_name(access):
|
||||
if len(access) == 0 or len(access) > 64:
|
||||
exc_str = ('Invalid CN (common name). Must be 1-64 chars long.')
|
||||
raise exceptions.CommandError(exc_str)
|
||||
|
||||
@staticmethod
|
||||
def _validate_username(access):
|
||||
valid_username_re = '[\w\.\-_\`;\'\{\}\[\]\\\\]{4,32}$'
|
||||
username = access
|
||||
if not re.match(valid_username_re, username):
|
||||
exc_str = ('Invalid user or group name. Must be 4-32 characters '
|
||||
'and consist of alphanumeric characters and '
|
||||
'special characters ]{.-_\'`;}[\\')
|
||||
raise exceptions.CommandError(exc_str)
|
||||
|
||||
@staticmethod
|
||||
def _validate_ip_range(ip_range):
|
||||
ip_range = ip_range.split('/')
|
||||
exc_str = ('Supported ip format examples:\n'
|
||||
'\t10.0.0.2, 10.0.0.0/24')
|
||||
if len(ip_range) > 2:
|
||||
raise exceptions.CommandError(exc_str)
|
||||
if len(ip_range) == 2:
|
||||
try:
|
||||
prefix = int(ip_range[1])
|
||||
if prefix < 0 or prefix > 32:
|
||||
raise ValueError()
|
||||
except ValueError:
|
||||
msg = 'IP prefix should be in range from 0 to 32'
|
||||
raise exceptions.CommandError(msg)
|
||||
ip_range = ip_range[0].split('.')
|
||||
if len(ip_range) != 4:
|
||||
raise exceptions.CommandError(exc_str)
|
||||
for item in ip_range:
|
||||
try:
|
||||
if 0 <= int(item) <= 255:
|
||||
continue
|
||||
raise ValueError()
|
||||
except ValueError:
|
||||
raise exceptions.CommandError(exc_str)
|
||||
|
||||
def reset_state(self, state):
|
||||
"""Update the share with the provided state."""
|
||||
self.manager.reset_state(self, state)
|
||||
|
||||
def extend(self, new_size):
|
||||
"""Extend the size of the specified share."""
|
||||
self.manager.extend(self, new_size)
|
||||
|
||||
def shrink(self, new_size):
|
||||
"""Shrink the size of the specified share."""
|
||||
self.manager.shrink(self, new_size)
|
||||
|
||||
def list_instances(self):
|
||||
"""List instances of the specified share."""
|
||||
self.manager.list_instances(self)
|
||||
|
||||
|
||||
class ShareManager(base.ManagerWithFind):
|
||||
"""Manage :class:`Share` resources."""
|
||||
resource_class = Share
|
||||
|
||||
def create(self, share_proto, size, snapshot_id=None, name=None,
|
||||
description=None, metadata=None, share_network=None,
|
||||
share_type=None, is_public=False, availability_zone=None,
|
||||
consistency_group_id=None):
|
||||
"""Create a share.
|
||||
|
||||
:param share_proto: text - share protocol for new share
|
||||
available values are NFS, CIFS, GlusterFS and HDFS.
|
||||
:param size: int - size in GB
|
||||
:param snapshot_id: text - ID of the snapshot
|
||||
:param name: text - name of new share
|
||||
:param description: text - description of a share
|
||||
:param metadata: dict - optional metadata to set on share creation
|
||||
:param share_network: either instance of ShareNetwork or text with ID
|
||||
:param share_type: either instance of ShareType or text with ID
|
||||
:param is_public: bool, whether to set share as public or not.
|
||||
:param consistency_group_id: text - ID of the consistency group to
|
||||
which the share should belong
|
||||
:rtype: :class:`Share`
|
||||
"""
|
||||
share_metadata = metadata if metadata is not None else dict()
|
||||
body = {
|
||||
'size': size,
|
||||
'snapshot_id': snapshot_id,
|
||||
'name': name,
|
||||
'description': description,
|
||||
'metadata': share_metadata,
|
||||
'share_proto': share_proto,
|
||||
'share_network_id': common_base.getid(share_network),
|
||||
'share_type': common_base.getid(share_type),
|
||||
'is_public': is_public,
|
||||
'availability_zone': availability_zone,
|
||||
'consistency_group_id': consistency_group_id,
|
||||
}
|
||||
return self._create('/shares', {'share': body}, 'share')
|
||||
|
||||
def migrate_share(self, share, host, force_host_copy):
|
||||
"""Migrate share to new host and pool.
|
||||
|
||||
:param share: The :class:'share' to migrate
|
||||
:param host: The destination host and pool
|
||||
:param force_host_copy: Skip driver optimizations
|
||||
"""
|
||||
|
||||
return self._action('os-migrate_share',
|
||||
share, {'host': host,
|
||||
'force_host_copy': force_host_copy})
|
||||
|
||||
def manage(self, service_host, protocol, export_path,
|
||||
driver_options=None, share_type=None,
|
||||
name=None, description=None):
|
||||
"""Manage some existing share.
|
||||
|
||||
:param service_host: text - host of share service where share is runing
|
||||
:param protocol: text - share protocol that is used
|
||||
:param export_path: text - export path of share
|
||||
:param driver_options: dict - custom set of key-values.
|
||||
:param share_type: text - share type that should be used for share
|
||||
:param name: text - name of new share
|
||||
:param description: - description for new share
|
||||
"""
|
||||
driver_options = driver_options if driver_options else dict()
|
||||
body = {
|
||||
'service_host': service_host,
|
||||
'share_type': share_type,
|
||||
'protocol': protocol,
|
||||
'export_path': export_path,
|
||||
'driver_options': driver_options,
|
||||
'name': name,
|
||||
'description': description
|
||||
}
|
||||
return self._create('/os-share-manage', {'share': body}, 'share')
|
||||
|
||||
def unmanage(self, share):
|
||||
"""Unmanage a share.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
"""
|
||||
return self.api.client.post(
|
||||
"/os-share-unmanage/%s/unmanage" % common_base.getid(share))
|
||||
|
||||
def get(self, share):
|
||||
"""Get a share.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
:rtype: :class:`Share`
|
||||
"""
|
||||
share_id = common_base.getid(share)
|
||||
return self._get("/shares/%s" % share_id, "share")
|
||||
|
||||
def update(self, share, **kwargs):
|
||||
"""Updates a share.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
:rtype: :class:`Share`
|
||||
"""
|
||||
if not kwargs:
|
||||
return
|
||||
|
||||
body = {'share': kwargs, }
|
||||
share_id = common_base.getid(share)
|
||||
return self._update("/shares/%s" % share_id, body)
|
||||
|
||||
def list(self, detailed=True, search_opts=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
"""Get a list of all shares.
|
||||
|
||||
:param detailed: Whether to return detailed share info or not.
|
||||
:param search_opts: dict with search options to filter out shares.
|
||||
available keys are below (('name1', 'name2', ...), 'type'):
|
||||
- ('all_tenants', int)
|
||||
- ('is_public', bool)
|
||||
- ('metadata', dict)
|
||||
- ('extra_specs', dict)
|
||||
- ('limit', int)
|
||||
- ('offset', int)
|
||||
- ('name', text)
|
||||
- ('status', text)
|
||||
- ('host', text)
|
||||
- ('share_server_id', text)
|
||||
- (('share_network_id', 'share_network'), text)
|
||||
- (('share_type_id', 'share_type'), text)
|
||||
- (('snapshot_id', 'snapshot'), text)
|
||||
Note, that member context will have restricted set of
|
||||
available search opts. For admin context filtering also available
|
||||
by each share attr from its Model. So, this list is not full for
|
||||
admin context.
|
||||
:param sort_key: Key to be sorted (i.e. 'created_at' or 'status').
|
||||
:param sort_dir: Sort direction, should be 'desc' or 'asc'.
|
||||
:rtype: list of :class:`Share`
|
||||
"""
|
||||
if search_opts is None:
|
||||
search_opts = {}
|
||||
|
||||
if sort_key is not None:
|
||||
if sort_key in constants.SHARE_SORT_KEY_VALUES:
|
||||
search_opts['sort_key'] = sort_key
|
||||
# NOTE(vponomaryov): Replace aliases with appropriate keys
|
||||
if sort_key == 'share_type':
|
||||
search_opts['sort_key'] = 'share_type_id'
|
||||
elif sort_key == 'snapshot':
|
||||
search_opts['sort_key'] = 'snapshot_id'
|
||||
elif sort_key == 'share_network':
|
||||
search_opts['sort_key'] = 'share_network_id'
|
||||
else:
|
||||
raise ValueError('sort_key must be one of the following: %s.'
|
||||
% ', '.join(constants.SHARE_SORT_KEY_VALUES))
|
||||
|
||||
if sort_dir is not None:
|
||||
if sort_dir in constants.SORT_DIR_VALUES:
|
||||
search_opts['sort_dir'] = sort_dir
|
||||
else:
|
||||
raise ValueError('sort_dir must be one of the following: %s.'
|
||||
% ', '.join(constants.SORT_DIR_VALUES))
|
||||
|
||||
if 'is_public' not in search_opts:
|
||||
search_opts['is_public'] = True
|
||||
|
||||
if search_opts:
|
||||
query_string = urlencode(
|
||||
sorted([(k, v) for (k, v) in list(search_opts.items()) if v]))
|
||||
if query_string:
|
||||
query_string = "?%s" % (query_string,)
|
||||
else:
|
||||
query_string = ''
|
||||
|
||||
if detailed:
|
||||
path = "/shares/detail%s" % (query_string,)
|
||||
else:
|
||||
path = "/shares%s" % (query_string,)
|
||||
|
||||
return self._list(path, 'shares')
|
||||
|
||||
def delete(self, share, consistency_group_id=None):
|
||||
"""Delete a share.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
:param consistency_group_id: text - ID of the consistency group to
|
||||
which the share belongs to.
|
||||
"""
|
||||
url = "/shares/%s" % common_base.getid(share)
|
||||
if consistency_group_id:
|
||||
url += "?consistency_group_id=%s" % consistency_group_id
|
||||
self._delete(url)
|
||||
|
||||
def force_delete(self, share):
|
||||
"""Delete a share forcibly - share status will be avoided.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
"""
|
||||
return self._action('os-force_delete', common_base.getid(share))
|
||||
|
||||
def allow(self, share, access_type, access, access_level):
|
||||
"""Allow access to a share.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
:param access_type: string that represents access type ('ip','domain')
|
||||
:param access: string that represents access ('127.0.0.1')
|
||||
:param access_level: string that represents access level ('rw', 'ro')
|
||||
"""
|
||||
access_params = {
|
||||
'access_type': access_type,
|
||||
'access_to': access,
|
||||
}
|
||||
if access_level:
|
||||
access_params['access_level'] = access_level
|
||||
access = self._action('os-allow_access', share,
|
||||
access_params)[1]["access"]
|
||||
|
||||
return access
|
||||
|
||||
def deny(self, share, access_id):
|
||||
"""Deny access to a share.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
:param access_id: ID of share access rule
|
||||
"""
|
||||
return self._action('os-deny_access', share, {'access_id': access_id})
|
||||
|
||||
def access_list(self, share):
|
||||
"""Get access list to a share.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
"""
|
||||
access_list = self._action("os-access_list", share)[1]["access_list"]
|
||||
if access_list:
|
||||
t = collections.namedtuple('Access', list(access_list[0]))
|
||||
return [t(*value.values()) for value in access_list]
|
||||
else:
|
||||
return []
|
||||
|
||||
def get_metadata(self, share):
|
||||
"""Get metadata of a share.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
"""
|
||||
return self._get("/shares/%s/metadata" % common_base.getid(share),
|
||||
"metadata")
|
||||
|
||||
def set_metadata(self, share, metadata):
|
||||
"""Set or update metadata for share.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
:param metadata: A list of keys to be set.
|
||||
"""
|
||||
body = {'metadata': metadata}
|
||||
return self._create("/shares/%s/metadata" % common_base.getid(share),
|
||||
body, "metadata")
|
||||
|
||||
def delete_metadata(self, share, keys):
|
||||
"""Delete specified keys from shares metadata.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
:param keys: A list of keys to be removed.
|
||||
"""
|
||||
share_id = common_base.getid(share)
|
||||
for key in keys:
|
||||
self._delete("/shares/%(share_id)s/metadata/%(key)s" % {
|
||||
'share_id': share_id, 'key': key})
|
||||
|
||||
def update_all_metadata(self, share, metadata):
|
||||
"""Update all metadata of a share.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
:param metadata: A list of keys to be updated.
|
||||
"""
|
||||
body = {'metadata': metadata}
|
||||
return self._update("/shares/%s/metadata" % common_base.getid(share),
|
||||
body)
|
||||
|
||||
def _action(self, action, share, info=None, **kwargs):
|
||||
"""Perform a share 'action'.
|
||||
|
||||
:param action: text with action name.
|
||||
:param share: either share object or text with its ID.
|
||||
:param info: dict with data for specified 'action'.
|
||||
:param kwargs: dict with data to be provided for action hooks.
|
||||
"""
|
||||
body = {action: info}
|
||||
self.run_hooks('modify_body_for_action', body, **kwargs)
|
||||
url = '/shares/%s/action' % common_base.getid(share)
|
||||
return self.api.client.post(url, body=body)
|
||||
|
||||
def reset_state(self, share, state):
|
||||
"""Update the provided share with the provided state.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
:param state: text with new state to set for share.
|
||||
"""
|
||||
return self._action('os-reset_status', share, {'status': state})
|
||||
|
||||
def extend(self, share, new_size):
|
||||
"""Extend the size of the specified share.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
:param new_size: The desired size to extend share to.
|
||||
"""
|
||||
return self._action('os-extend', share, {'new_size': new_size})
|
||||
|
||||
def shrink(self, share, new_size):
|
||||
"""Shrink the size of the specified share.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
:param new_size: The desired size to shrink share to.
|
||||
"""
|
||||
return self._action('os-shrink', share, {'new_size': new_size})
|
||||
|
||||
def list_instances(self, share):
|
||||
"""List instances of the specified share.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
"""
|
||||
return self._list(
|
||||
'/shares/%s/instances' % common_base.getid(share),
|
||||
'share_instances',
|
||||
obj_class=share_instances.ShareInstance
|
||||
)
|
||||
sys.modules["manilaclient.v2.shares"] = MovedModule(shares)
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
# Copyright 2015 Chuck Fouts
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 manilaclient.v2.client import Client # noqa
|
|
@ -0,0 +1,297 @@
|
|||
# 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 warnings
|
||||
|
||||
from keystoneclient import adapter
|
||||
from keystoneclient import client as ks_client
|
||||
from keystoneclient import discover
|
||||
from keystoneclient import session
|
||||
import six
|
||||
|
||||
import manilaclient
|
||||
from manilaclient.common import constants
|
||||
from manilaclient.common import httpclient
|
||||
from manilaclient import exceptions
|
||||
from manilaclient.v2 import consistency_group_snapshots as cg_snapshots
|
||||
from manilaclient.v2 import consistency_groups
|
||||
from manilaclient.v2 import limits
|
||||
from manilaclient.v2 import quota_classes
|
||||
from manilaclient.v2 import quotas
|
||||
from manilaclient.v2 import scheduler_stats
|
||||
from manilaclient.v2 import security_services
|
||||
from manilaclient.v2 import services
|
||||
from manilaclient.v2 import share_instances
|
||||
from manilaclient.v2 import share_networks
|
||||
from manilaclient.v2 import share_servers
|
||||
from manilaclient.v2 import share_snapshots
|
||||
from manilaclient.v2 import share_type_access
|
||||
from manilaclient.v2 import share_types
|
||||
from manilaclient.v2 import shares
|
||||
|
||||
|
||||
class Client(object):
|
||||
"""Top-level object to access the OpenStack Manila API.
|
||||
|
||||
Create an instance with your creds::
|
||||
|
||||
>>> client = Client(USERNAME, PASSWORD, PROJECT_ID, AUTH_URL)
|
||||
|
||||
Or, alternatively, you can create a client instance using the
|
||||
keystoneclient.session API::
|
||||
|
||||
>>> from keystoneclient.auth.identity import v2
|
||||
>>> from keystoneclient import session
|
||||
>>> from manilaclient import client
|
||||
>>> auth = v2.Password(auth_url=AUTH_URL,
|
||||
username=USERNAME,
|
||||
password=PASSWORD,
|
||||
tenant_name=PROJECT_ID)
|
||||
>>> sess = session.Session(auth=auth)
|
||||
>>> manila = client.Client(VERSION, session=sess)
|
||||
|
||||
Then call methods on its managers::
|
||||
|
||||
>>> client.shares.list()
|
||||
...
|
||||
"""
|
||||
def __init__(self, username=None, api_key=None,
|
||||
project_id=None, auth_url=None, insecure=False, timeout=None,
|
||||
tenant_id=None, project_name=None, region_name=None,
|
||||
endpoint_type='publicURL', extensions=None,
|
||||
service_type=constants.V2_SERVICE_TYPE, service_name=None,
|
||||
retries=None, http_log_debug=False, input_auth_token=None,
|
||||
session=None, auth=None, cacert=None,
|
||||
service_catalog_url=None, user_agent='python-manilaclient',
|
||||
use_keyring=False, force_new_token=False,
|
||||
cached_token_lifetime=300,
|
||||
api_version=manilaclient.API_MIN_VERSION,
|
||||
user_id=None,
|
||||
user_domain_id=None,
|
||||
user_domain_name=None,
|
||||
project_domain_id=None,
|
||||
project_domain_name=None,
|
||||
cert=None,
|
||||
password=None,
|
||||
**kwargs):
|
||||
|
||||
self.username = username
|
||||
self.password = password or api_key
|
||||
self.tenant_id = tenant_id or project_id
|
||||
self.tenant_name = project_name
|
||||
|
||||
self.user_id = user_id
|
||||
self.project_id = project_id or tenant_id
|
||||
self.project_name = project_name
|
||||
self.user_domain_id = user_domain_id
|
||||
self.user_domain_name = user_domain_name
|
||||
self.project_domain_id = project_domain_id
|
||||
self.project_domain_name = project_domain_name
|
||||
|
||||
self.endpoint_type = endpoint_type
|
||||
self.auth_url = auth_url
|
||||
self.region_name = region_name
|
||||
|
||||
self.cacert = cacert
|
||||
self.cert = cert
|
||||
self.insecure = insecure
|
||||
|
||||
self.use_keyring = use_keyring
|
||||
self.force_new_token = force_new_token
|
||||
self.cached_token_lifetime = cached_token_lifetime
|
||||
|
||||
service_name = kwargs.get("share_service_name", service_name)
|
||||
|
||||
def check_deprecated_arguments():
|
||||
deprecated = {
|
||||
'share_service_name': 'service_name',
|
||||
'proxy_tenant_id': None,
|
||||
'proxy_token': None,
|
||||
'os_cache': 'use_keyring',
|
||||
'api_key': 'password',
|
||||
}
|
||||
|
||||
for arg, replacement in six.iteritems(deprecated):
|
||||
if kwargs.get(arg, None) is None:
|
||||
continue
|
||||
|
||||
replacement_msg = ""
|
||||
|
||||
if replacement is not None:
|
||||
replacement_msg = " Use %s instead." % replacement
|
||||
|
||||
msg = "Argument %(arg)s is deprecated.%(repl)s" % {
|
||||
'arg': arg,
|
||||
'repl': replacement_msg
|
||||
}
|
||||
warnings.warn(msg)
|
||||
|
||||
check_deprecated_arguments()
|
||||
|
||||
if input_auth_token and not service_catalog_url:
|
||||
msg = ("For token-based authentication you should "
|
||||
"provide 'input_auth_token' and 'service_catalog_url'.")
|
||||
raise exceptions.ClientException(msg)
|
||||
|
||||
self.project_id = tenant_id if tenant_id is not None else project_id
|
||||
self.keystone_client = None
|
||||
self.session = session
|
||||
|
||||
# NOTE(u_glide): token authorization has highest priority.
|
||||
# That's why session and/or password will be ignored
|
||||
# if token is provided.
|
||||
if not input_auth_token:
|
||||
if session:
|
||||
self.keystone_client = adapter.LegacyJsonAdapter(
|
||||
session=session,
|
||||
auth=auth,
|
||||
interface=endpoint_type,
|
||||
service_type=service_type,
|
||||
service_name=service_name,
|
||||
region_name=region_name)
|
||||
input_auth_token = self.keystone_client.session.get_token(auth)
|
||||
|
||||
else:
|
||||
self.keystone_client = self._get_keystone_client()
|
||||
input_auth_token = self.keystone_client.auth_token
|
||||
|
||||
if not input_auth_token:
|
||||
raise RuntimeError("Not Authorized")
|
||||
|
||||
if session and not service_catalog_url:
|
||||
service_catalog_url = self.keystone_client.session.get_endpoint(
|
||||
auth, interface=endpoint_type,
|
||||
service_type=service_type)
|
||||
elif not service_catalog_url:
|
||||
catalog = self.keystone_client.service_catalog.get_endpoints(
|
||||
service_type)
|
||||
for catalog_entry in catalog.get(service_type, []):
|
||||
if (catalog_entry.get("interface") == (
|
||||
endpoint_type.lower().split("url")[0]) or
|
||||
catalog_entry.get(endpoint_type)):
|
||||
if (region_name and not region_name == (
|
||||
catalog_entry.get(
|
||||
"region",
|
||||
catalog_entry.get("region_id")))):
|
||||
continue
|
||||
service_catalog_url = catalog_entry.get(
|
||||
"url", catalog_entry.get(endpoint_type))
|
||||
break
|
||||
|
||||
if not service_catalog_url:
|
||||
raise RuntimeError("Could not find Manila endpoint in catalog")
|
||||
|
||||
self.api_version = api_version
|
||||
self.client = httpclient.HTTPClient(service_catalog_url,
|
||||
input_auth_token,
|
||||
user_agent,
|
||||
insecure=insecure,
|
||||
cacert=cacert,
|
||||
timeout=timeout,
|
||||
retries=retries,
|
||||
http_log_debug=http_log_debug,
|
||||
api_version=self.api_version)
|
||||
|
||||
self.limits = limits.LimitsManager(self)
|
||||
self.services = services.ServiceManager(self)
|
||||
self.security_services = security_services.SecurityServiceManager(self)
|
||||
self.share_networks = share_networks.ShareNetworkManager(self)
|
||||
|
||||
self.quota_classes = quota_classes.QuotaClassSetManager(self)
|
||||
self.quotas = quotas.QuotaSetManager(self)
|
||||
|
||||
self.shares = shares.ShareManager(self)
|
||||
self.share_instances = share_instances.ShareInstanceManager(self)
|
||||
self.share_snapshots = share_snapshots.ShareSnapshotManager(self)
|
||||
|
||||
self.share_types = share_types.ShareTypeManager(self)
|
||||
self.share_type_access = share_type_access.ShareTypeAccessManager(self)
|
||||
self.share_servers = share_servers.ShareServerManager(self)
|
||||
self.pools = scheduler_stats.PoolManager(self)
|
||||
self.consistency_groups = (
|
||||
consistency_groups.ConsistencyGroupManager(self))
|
||||
self.cg_snapshots = (
|
||||
cg_snapshots.ConsistencyGroupSnapshotManager(self))
|
||||
|
||||
self._load_extensions(extensions)
|
||||
|
||||
def _load_extensions(self, extensions):
|
||||
if not extensions:
|
||||
return
|
||||
|
||||
for extension in extensions:
|
||||
if extension.manager_class:
|
||||
setattr(self, extension.name, extension.manager_class(self))
|
||||
|
||||
def authenticate(self):
|
||||
"""Authenticate against the server.
|
||||
|
||||
Normally this is called automatically when you first access the API,
|
||||
but you can call this method to force authentication right now.
|
||||
|
||||
Returns on success; raises :exc:`exceptions.Unauthorized` if the
|
||||
credentials are wrong.
|
||||
"""
|
||||
warnings.warn("authenticate() method is deprecated. "
|
||||
"Client automatically makes authentication call "
|
||||
"in the constructor.")
|
||||
|
||||
def _get_keystone_client(self):
|
||||
# First create a Keystone session
|
||||
if self.insecure:
|
||||
verify = False
|
||||
else:
|
||||
verify = self.cacert or True
|
||||
ks_session = session.Session(verify=verify, cert=self.cert)
|
||||
|
||||
# Discover the supported keystone versions using the given url
|
||||
ks_discover = discover.Discover(
|
||||
session=ks_session, auth_url=self.auth_url)
|
||||
|
||||
# Inspect the auth_url to see the supported version. If both v3 and v2
|
||||
# are supported, then use the highest version if possible.
|
||||
v2_auth_url = ks_discover.url_for('v2.0')
|
||||
v3_auth_url = ks_discover.url_for('v3.0')
|
||||
|
||||
if v3_auth_url:
|
||||
keystone_client = ks_client.Client(
|
||||
version=(3, 0),
|
||||
auth_url=v3_auth_url,
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
user_id=self.user_id,
|
||||
user_domain_name=self.user_domain_name,
|
||||
user_domain_id=self.user_domain_id,
|
||||
project_id=self.project_id or self.tenant_id,
|
||||
project_name=self.project_name,
|
||||
project_domain_name=self.project_domain_name,
|
||||
project_domain_id=self.project_domain_id,
|
||||
region_name=self.region_name)
|
||||
elif v2_auth_url:
|
||||
keystone_client = ks_client.Client(
|
||||
version=(2, 0),
|
||||
auth_url=v2_auth_url,
|
||||
username=self.username,
|
||||
password=self.password,
|
||||
tenant_id=self.tenant_id,
|
||||
tenant_name=self.tenant_name,
|
||||
region_name=self.region_name,
|
||||
cert=self.cert,
|
||||
use_keyring=self.use_keyring,
|
||||
force_new_token=self.force_new_token,
|
||||
stale_duration=self.cached_token_lifetime)
|
||||
else:
|
||||
raise exceptions.CommandError(
|
||||
'Unable to determine the Keystone version to authenticate '
|
||||
'with using the given auth_url.')
|
||||
keystone_client.authenticate()
|
||||
return keystone_client
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
from six.moves.urllib import parse
|
||||
|
||||
from manilaclient import api_versions
|
||||
from manilaclient import base
|
||||
from manilaclient.openstack.common.apiclient import base as common_base
|
||||
|
||||
|
@ -49,6 +50,7 @@ class ConsistencyGroupSnapshot(common_base.Resource):
|
|||
class ConsistencyGroupSnapshotManager(base.ManagerWithFind):
|
||||
resource_class = ConsistencyGroupSnapshot
|
||||
|
||||
@api_versions.wraps("2.4")
|
||||
def create(self, consistency_group_id, name=None, description=None):
|
||||
"""Create a consistency group snapshot.
|
||||
|
||||
|
@ -65,6 +67,7 @@ class ConsistencyGroupSnapshotManager(base.ManagerWithFind):
|
|||
{RESOURCE_NAME: body},
|
||||
RESOURCE_NAME)
|
||||
|
||||
@api_versions.wraps("2.4")
|
||||
def get(self, cg_snapshot):
|
||||
"""Get a consistency group snapshot.
|
||||
|
||||
|
@ -76,6 +79,7 @@ class ConsistencyGroupSnapshotManager(base.ManagerWithFind):
|
|||
return self._get(RESOURCE_PATH % consistency_group_id,
|
||||
RESOURCE_NAME)
|
||||
|
||||
@api_versions.wraps("2.4")
|
||||
def update(self, cg_snapshot, **kwargs):
|
||||
"""Updates a consistency group snapshot.
|
||||
|
||||
|
@ -92,6 +96,7 @@ class ConsistencyGroupSnapshotManager(base.ManagerWithFind):
|
|||
body,
|
||||
RESOURCE_NAME)
|
||||
|
||||
@api_versions.wraps("2.4")
|
||||
def list(self, detailed=True, search_opts=None):
|
||||
"""Get a list of all consistency group snapshots.
|
||||
|
||||
|
@ -118,6 +123,7 @@ class ConsistencyGroupSnapshotManager(base.ManagerWithFind):
|
|||
|
||||
return self._list(path, RESOURCES_NAME)
|
||||
|
||||
@api_versions.wraps("2.4")
|
||||
def delete(self, cg_snapshot, force=False):
|
||||
"""Delete a consistency group snapshot.
|
||||
|
||||
|
@ -134,6 +140,7 @@ class ConsistencyGroupSnapshotManager(base.ManagerWithFind):
|
|||
else:
|
||||
self._delete(RESOURCE_PATH % cg_id)
|
||||
|
||||
@api_versions.wraps("2.4")
|
||||
def members(self, cg_snapshot, search_opts=None):
|
||||
"""Get a list of consistency group snapshot members.
|
||||
|
||||
|
@ -154,6 +161,7 @@ class ConsistencyGroupSnapshotManager(base.ManagerWithFind):
|
|||
|
||||
return self._list(path, MEMBERS_RESOURCE_NAME)
|
||||
|
||||
@api_versions.wraps("2.4")
|
||||
def reset_state(self, cg_snapshot, state):
|
||||
"""Update the specified consistency group with the provided state."""
|
||||
body = {'os-reset_status': {'status': state}}
|
|
@ -1,4 +1,5 @@
|
|||
# Copyright 2015 Andrew Kerr
|
||||
# Copyright 2015 Chuck Fouts
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
|
@ -16,6 +17,7 @@
|
|||
|
||||
from six.moves.urllib import parse
|
||||
|
||||
from manilaclient import api_versions
|
||||
from manilaclient import base
|
||||
from manilaclient.openstack.common.apiclient import base as common_base
|
||||
|
||||
|
@ -48,6 +50,7 @@ class ConsistencyGroupManager(base.ManagerWithFind):
|
|||
"""Manage :class:`ConsistencyGroup` resources."""
|
||||
resource_class = ConsistencyGroup
|
||||
|
||||
@api_versions.wraps("2.4")
|
||||
def create(self, share_network=None, name=None, description=None,
|
||||
source_cgsnapshot_id=None, share_types=None):
|
||||
"""Create a Consistency Group.
|
||||
|
@ -83,6 +86,7 @@ class ConsistencyGroupManager(base.ManagerWithFind):
|
|||
return self._create(RESOURCES_PATH,
|
||||
{RESOURCE_NAME: body}, RESOURCE_NAME)
|
||||
|
||||
@api_versions.wraps("2.4")
|
||||
def get(self, consistency_group):
|
||||
"""Get a consistency group.
|
||||
|
||||
|
@ -94,6 +98,7 @@ class ConsistencyGroupManager(base.ManagerWithFind):
|
|||
return self._get(RESOURCE_PATH % consistency_group_id,
|
||||
RESOURCE_NAME)
|
||||
|
||||
@api_versions.wraps("2.4")
|
||||
def update(self, consistency_group, **kwargs):
|
||||
"""Updates a consistency group.
|
||||
|
||||
|
@ -110,6 +115,7 @@ class ConsistencyGroupManager(base.ManagerWithFind):
|
|||
body,
|
||||
RESOURCE_NAME)
|
||||
|
||||
@api_versions.wraps("2.4")
|
||||
def list(self, detailed=True, search_opts=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
"""Get a list of all shares.
|
||||
|
@ -133,6 +139,7 @@ class ConsistencyGroupManager(base.ManagerWithFind):
|
|||
|
||||
return self._list(path, RESOURCES_NAME)
|
||||
|
||||
@api_versions.wraps("2.4")
|
||||
def delete(self, consistency_group, force=False):
|
||||
"""Delete a consistency group.
|
||||
|
||||
|
@ -151,6 +158,7 @@ class ConsistencyGroupManager(base.ManagerWithFind):
|
|||
else:
|
||||
self._delete(url)
|
||||
|
||||
@api_versions.wraps("2.4")
|
||||
def reset_state(self, consistency_group, state):
|
||||
"""Update the specified consistency group with the provided state."""
|
||||
body = {'os-reset_status': {'status': state}}
|
|
@ -0,0 +1,93 @@
|
|||
# Copyright 2013 OpenStack LLC.
|
||||
# Copyright 2015 Chuck Fouts
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 manilaclient import base
|
||||
from manilaclient.openstack.common.apiclient import base as common_base
|
||||
|
||||
|
||||
class Limits(common_base.Resource):
|
||||
"""A collection of RateLimit and AbsoluteLimit objects."""
|
||||
|
||||
def __repr__(self):
|
||||
return "<Limits>"
|
||||
|
||||
@property
|
||||
def absolute(self):
|
||||
for (name, value) in list(self._info['absolute'].items()):
|
||||
yield AbsoluteLimit(name, value)
|
||||
|
||||
@property
|
||||
def rate(self):
|
||||
for group in self._info['rate']:
|
||||
uri = group['uri']
|
||||
regex = group['regex']
|
||||
for rate in group['limit']:
|
||||
yield RateLimit(rate['verb'], uri, regex, rate['value'],
|
||||
rate['remaining'], rate['unit'],
|
||||
rate['next-available'])
|
||||
|
||||
|
||||
class RateLimit(object):
|
||||
"""Data model that represents a flattened view of a single rate limit."""
|
||||
|
||||
def __init__(self, verb, uri, regex, value, remain,
|
||||
unit, next_available):
|
||||
self.verb = verb
|
||||
self.uri = uri
|
||||
self.regex = regex
|
||||
self.value = value
|
||||
self.remain = remain
|
||||
self.unit = unit
|
||||
self.next_available = next_available
|
||||
|
||||
def __eq__(self, other):
|
||||
return (self.uri == other.uri and
|
||||
self.regex == other.regex and
|
||||
self.value == other.value and
|
||||
self.verb == other.verb and
|
||||
self.remain == other.remain and
|
||||
self.unit == other.unit and
|
||||
self.next_available == other.next_available)
|
||||
|
||||
def __repr__(self):
|
||||
return "<RateLimit: method=%s uri=%s>" % (self.method, self.uri)
|
||||
|
||||
|
||||
class AbsoluteLimit(object):
|
||||
"""Data model that represents a single absolute limit."""
|
||||
|
||||
def __init__(self, name, value):
|
||||
self.name = name
|
||||
self.value = value
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.value == other.value and self.name == other.name
|
||||
|
||||
def __repr__(self):
|
||||
return "<AbsoluteLimit: name=%s>" % (self.name)
|
||||
|
||||
|
||||
class LimitsManager(base.Manager):
|
||||
"""Manager object used to interact with limits resource."""
|
||||
|
||||
resource_class = Limits
|
||||
|
||||
def get(self):
|
||||
"""Get a specific extension.
|
||||
|
||||
:rtype: :class:`Limits`
|
||||
"""
|
||||
return self._get("/limits", "limits")
|
|
@ -0,0 +1,61 @@
|
|||
# Copyright 2013 OpenStack LLC.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 manilaclient import base
|
||||
from manilaclient.openstack.common.apiclient import base as common_base
|
||||
|
||||
|
||||
class QuotaClassSet(common_base.Resource):
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
"""Needed by base.Resource to self-refresh and be indexed."""
|
||||
return self.class_name
|
||||
|
||||
def update(self, *args, **kwargs):
|
||||
self.manager.update(self.class_name, *args, **kwargs)
|
||||
|
||||
|
||||
class QuotaClassSetManager(base.ManagerWithFind):
|
||||
resource_class = QuotaClassSet
|
||||
|
||||
def get(self, class_name):
|
||||
return self._get("/os-quota-class-sets/%s" % class_name,
|
||||
"quota_class_set")
|
||||
|
||||
def update(self,
|
||||
class_name,
|
||||
shares=None,
|
||||
gigabytes=None,
|
||||
snapshots=None,
|
||||
snapshot_gigabytes=None,
|
||||
share_networks=None):
|
||||
|
||||
body = {
|
||||
'quota_class_set': {
|
||||
'class_name': class_name,
|
||||
'shares': shares,
|
||||
'snapshots': snapshots,
|
||||
'gigabytes': gigabytes,
|
||||
'snapshot_gigabytes': snapshot_gigabytes,
|
||||
'share_networks': share_networks,
|
||||
}
|
||||
}
|
||||
|
||||
for key in list(body['quota_class_set']):
|
||||
if body['quota_class_set'][key] is None:
|
||||
body['quota_class_set'].pop(key)
|
||||
|
||||
self._update('/os-quota-class-sets/%s' % class_name, body)
|
|
@ -0,0 +1,78 @@
|
|||
# Copyright 2013 OpenStack LLC.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 manilaclient import base
|
||||
from manilaclient.openstack.common.apiclient import base as common_base
|
||||
|
||||
|
||||
class QuotaSet(common_base.Resource):
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
"""Needed by Resource to self-refresh and be indexed."""
|
||||
return self.tenant_id
|
||||
|
||||
def update(self, *args, **kwargs):
|
||||
self.manager.update(self.tenant_id, *args, **kwargs)
|
||||
|
||||
|
||||
class QuotaSetManager(base.ManagerWithFind):
|
||||
resource_class = QuotaSet
|
||||
|
||||
def get(self, tenant_id, user_id=None):
|
||||
if hasattr(tenant_id, 'tenant_id'):
|
||||
tenant_id = tenant_id.tenant_id
|
||||
if user_id:
|
||||
url = "/os-quota-sets/%s?user_id=%s" % (tenant_id, user_id)
|
||||
else:
|
||||
url = "/os-quota-sets/%s" % tenant_id
|
||||
return self._get(url, "quota_set")
|
||||
|
||||
def update(self, tenant_id, shares=None, snapshots=None,
|
||||
gigabytes=None, snapshot_gigabytes=None,
|
||||
share_networks=None, force=None, user_id=None):
|
||||
|
||||
body = {
|
||||
'quota_set': {
|
||||
'tenant_id': tenant_id,
|
||||
'shares': shares,
|
||||
'snapshots': snapshots,
|
||||
'gigabytes': gigabytes,
|
||||
'snapshot_gigabytes': snapshot_gigabytes,
|
||||
'share_networks': share_networks,
|
||||
'force': force,
|
||||
},
|
||||
}
|
||||
|
||||
for key in list(body['quota_set']):
|
||||
if body['quota_set'][key] is None:
|
||||
body['quota_set'].pop(key)
|
||||
if user_id:
|
||||
url = '/os-quota-sets/%s?user_id=%s' % (tenant_id, user_id)
|
||||
else:
|
||||
url = '/os-quota-sets/%s' % tenant_id
|
||||
|
||||
return self._update(url, body, 'quota_set')
|
||||
|
||||
def defaults(self, tenant_id):
|
||||
return self._get('/os-quota-sets/%s/defaults' % tenant_id,
|
||||
'quota_set')
|
||||
|
||||
def delete(self, tenant_id, user_id=None):
|
||||
if user_id:
|
||||
url = '/os-quota-sets/%s?user_id=%s' % (tenant_id, user_id)
|
||||
else:
|
||||
url = '/os-quota-sets/%s' % tenant_id
|
||||
self._delete(url)
|
|
@ -0,0 +1,62 @@
|
|||
# Copyright (c) 2015 Clinton Knight. All rights reserved.
|
||||
#
|
||||
# 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 six.moves.urllib.parse as urlparse
|
||||
|
||||
from manilaclient import base
|
||||
from manilaclient.openstack.common.apiclient import base as common_base
|
||||
|
||||
|
||||
RESOURCES_PATH = '/scheduler-stats/pools'
|
||||
RESOURCES_NAME = 'pools'
|
||||
|
||||
|
||||
class Pool(common_base.Resource):
|
||||
|
||||
def __repr__(self):
|
||||
return "<Pool: %s>" % self.name
|
||||
|
||||
|
||||
class PoolManager(base.Manager):
|
||||
"""Manage :class:`Pool` resources."""
|
||||
resource_class = Pool
|
||||
|
||||
def list(self, detailed=True, search_opts=None):
|
||||
"""Get a list of pools.
|
||||
|
||||
:rtype: list of :class:`Pool`
|
||||
"""
|
||||
if search_opts is None:
|
||||
search_opts = {}
|
||||
|
||||
if search_opts:
|
||||
query_string = urlparse.urlencode(
|
||||
sorted([(k, v) for (k, v) in list(search_opts.items()) if v]))
|
||||
if query_string:
|
||||
query_string = "?%s" % (query_string,)
|
||||
else:
|
||||
query_string = ''
|
||||
|
||||
if detailed:
|
||||
path = '%(resources_path)s/detail%(query)s' % {
|
||||
'resources_path': RESOURCES_PATH,
|
||||
'query': query_string
|
||||
}
|
||||
else:
|
||||
path = '%(resources_path)s%(query)s' % {
|
||||
'resources_path': RESOURCES_PATH,
|
||||
'query': query_string
|
||||
}
|
||||
|
||||
return self._list(path, RESOURCES_NAME)
|
|
@ -0,0 +1,170 @@
|
|||
# Copyright 2013 OpenStack LLC.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
try:
|
||||
from urllib import urlencode # noqa
|
||||
except ImportError:
|
||||
from urllib.parse import urlencode # noqa
|
||||
|
||||
import six
|
||||
|
||||
from manilaclient import base
|
||||
from manilaclient import exceptions
|
||||
from manilaclient.openstack.common.apiclient import base as common_base
|
||||
|
||||
RESOURCES_PATH = '/security-services'
|
||||
RESOURCE_PATH = "/security-services/%s"
|
||||
RESOURCE_NAME = 'security_service'
|
||||
RESOURCES_NAME = 'security_services'
|
||||
|
||||
|
||||
class SecurityService(common_base.Resource):
|
||||
"""Security service for Manila shares."""
|
||||
def __repr__(self):
|
||||
return "<SecurityService: %s>" % self.id
|
||||
|
||||
def update(self, **kwargs):
|
||||
"""Update this security service."""
|
||||
return self.manager.update(self, **kwargs)
|
||||
|
||||
def delete(self):
|
||||
""""Delete this security service."""
|
||||
self.manager.delete(self)
|
||||
|
||||
|
||||
class SecurityServiceManager(base.ManagerWithFind):
|
||||
"""Manage :class:`SecurityService` resources."""
|
||||
|
||||
resource_class = SecurityService
|
||||
|
||||
def create(self, type, dns_ip=None, server=None, domain=None, user=None,
|
||||
password=None, name=None, description=None):
|
||||
"""Create security service for NAS.
|
||||
|
||||
:param type: security service type - 'ldap', 'kerberos' or
|
||||
'active_directory'
|
||||
:param dns_ip: dns ip address used inside tenant's network
|
||||
:param server: security service server ip address or hostname
|
||||
:param domain: security service domain
|
||||
:param user: security identifier used by tenant
|
||||
:param password: password used by user
|
||||
:param name: security service name
|
||||
:param description: security service description
|
||||
:rtype: :class:`SecurityService`
|
||||
"""
|
||||
values = {'type': type}
|
||||
if dns_ip:
|
||||
values['dns_ip'] = dns_ip
|
||||
if server:
|
||||
values['server'] = server
|
||||
if domain:
|
||||
values['domain'] = domain
|
||||
if user:
|
||||
values['user'] = user
|
||||
if password:
|
||||
values['password'] = password
|
||||
if name:
|
||||
values['name'] = name
|
||||
if description:
|
||||
values['description'] = description
|
||||
|
||||
body = {RESOURCE_NAME: values}
|
||||
|
||||
return self._create(RESOURCES_PATH, body, RESOURCE_NAME)
|
||||
|
||||
def get(self, security_service):
|
||||
"""Get a security service info.
|
||||
|
||||
:param security_service: security service to get.
|
||||
:rtype: :class:`SecurityService`
|
||||
"""
|
||||
return self._get(
|
||||
RESOURCE_PATH % common_base.getid(security_service),
|
||||
RESOURCE_NAME,
|
||||
)
|
||||
|
||||
def update(self, security_service, dns_ip=None, server=None, domain=None,
|
||||
password=None, user=None, name=None, description=None):
|
||||
"""Updates a security service.
|
||||
|
||||
:param security_service: security service to update.
|
||||
:param dns_ip: dns ip address used inside tenant's network
|
||||
:param server: security service server ip address or hostname
|
||||
:param domain: security service domain
|
||||
:param user: security identifier used by tenant
|
||||
:param password: password used by user
|
||||
:param name: security service name
|
||||
:param description: security service description
|
||||
:rtype: :class:`SecurityService`
|
||||
"""
|
||||
|
||||
values = {}
|
||||
if dns_ip is not None:
|
||||
values['dns_ip'] = dns_ip
|
||||
if server is not None:
|
||||
values['server'] = server
|
||||
if domain is not None:
|
||||
values['domain'] = domain
|
||||
if user is not None:
|
||||
values['user'] = user
|
||||
if password is not None:
|
||||
values['password'] = password
|
||||
if name is not None:
|
||||
values['name'] = name
|
||||
if description is not None:
|
||||
values['description'] = description
|
||||
|
||||
for k, v in six.iteritems(values):
|
||||
if v == '':
|
||||
values[k] = None
|
||||
|
||||
if not values:
|
||||
msg = "Must specify fields to be updated"
|
||||
raise exceptions.CommandError(msg)
|
||||
|
||||
body = {RESOURCE_NAME: values}
|
||||
|
||||
return self._update(
|
||||
RESOURCE_PATH % common_base.getid(security_service),
|
||||
body,
|
||||
RESOURCE_NAME,
|
||||
)
|
||||
|
||||
def delete(self, security_service):
|
||||
"""Delete a security service.
|
||||
|
||||
:param security_service: security service to be deleted.
|
||||
"""
|
||||
self._delete(RESOURCE_PATH % common_base.getid(security_service))
|
||||
|
||||
def list(self, detailed=True, search_opts=None):
|
||||
"""Get a list of all security services.
|
||||
|
||||
:rtype: list of :class:`SecurityService`
|
||||
"""
|
||||
if search_opts:
|
||||
query_string = urlencode(
|
||||
sorted([(k, v) for (k, v) in list(search_opts.items()) if v]))
|
||||
if query_string:
|
||||
query_string = "?%s" % query_string
|
||||
else:
|
||||
query_string = ''
|
||||
|
||||
if detailed:
|
||||
path = RESOURCES_PATH + "/detail" + query_string
|
||||
else:
|
||||
path = RESOURCES_PATH + query_string
|
||||
|
||||
return self._list(path, RESOURCES_NAME)
|
|
@ -0,0 +1,76 @@
|
|||
# Copyright 2014 OpenStack LLC.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 six
|
||||
try:
|
||||
from urllib import urlencode # noqa
|
||||
except ImportError:
|
||||
from urllib.parse import urlencode # noqa
|
||||
|
||||
from manilaclient import base
|
||||
from manilaclient.openstack.common.apiclient import base as common_base
|
||||
|
||||
RESOURCES_PATH = '/os-services'
|
||||
RESOURCES_NAME = 'services'
|
||||
|
||||
|
||||
class Service(common_base.Resource):
|
||||
|
||||
def __repr__(self):
|
||||
return "<Service: %s>" % self.id
|
||||
|
||||
def server_api_version(self, **kwargs):
|
||||
"""Get api version."""
|
||||
return self.manager.api_version(self, kwargs)
|
||||
|
||||
|
||||
class ServiceManager(base.Manager):
|
||||
"""Manage :class:`Service` resources."""
|
||||
resource_class = Service
|
||||
|
||||
def list(self, search_opts=None):
|
||||
"""Get a list of all services.
|
||||
|
||||
:rtype: list of :class:`Service`
|
||||
"""
|
||||
query_string = ''
|
||||
if search_opts:
|
||||
query_string = urlencode(
|
||||
sorted([(k, v) for (k, v) in six.iteritems(search_opts) if v]))
|
||||
if query_string:
|
||||
query_string = "?%s" % query_string
|
||||
return self._list(RESOURCES_PATH + query_string, RESOURCES_NAME)
|
||||
|
||||
def enable(self, host, binary):
|
||||
"""Enable the service specified by hostname and binary."""
|
||||
body = {"host": host, "binary": binary}
|
||||
return self._update("/os-services/enable", body)
|
||||
|
||||
def disable(self, host, binary):
|
||||
"""Disable the service specified by hostname and binary."""
|
||||
body = {"host": host, "binary": binary}
|
||||
return self._update("/os-services/disable", body)
|
||||
|
||||
def server_api_version(self, url_append=""):
|
||||
"""Returns the API Version supported by the server.
|
||||
|
||||
:param url_append: String to append to url to obtain specific version
|
||||
:return: Returns response obj for a server that supports microversions.
|
||||
Returns an empty list for Kilo and prior Manila servers.
|
||||
"""
|
||||
try:
|
||||
return self._get_with_base_url(url_append, 'versions')
|
||||
except LookupError:
|
||||
return []
|
|
@ -0,0 +1,181 @@
|
|||
# Copyright 2013 OpenStack LLC.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
|
||||
try:
|
||||
from urllib import urlencode # noqa
|
||||
except ImportError:
|
||||
from urllib.parse import urlencode # noqa
|
||||
|
||||
import six
|
||||
|
||||
from manilaclient import base
|
||||
from manilaclient import exceptions
|
||||
from manilaclient.openstack.common.apiclient import base as common_base
|
||||
|
||||
RESOURCES_PATH = '/share-networks'
|
||||
RESOURCE_PATH = "/share-networks/%s"
|
||||
RESOURCE_NAME = 'share_network'
|
||||
RESOURCES_NAME = 'share_networks'
|
||||
|
||||
|
||||
class ShareNetwork(common_base.Resource):
|
||||
"""Network info for Manila shares."""
|
||||
def __repr__(self):
|
||||
return "<ShareNetwork: %s>" % self.id
|
||||
|
||||
def update(self, **kwargs):
|
||||
"""Update this share network."""
|
||||
return self.manager.update(self, **kwargs)
|
||||
|
||||
def delete(self):
|
||||
"""Delete this share network."""
|
||||
self.manager.delete(self)
|
||||
|
||||
|
||||
class ShareNetworkManager(base.ManagerWithFind):
|
||||
"""Manage :class:`ShareNetwork` resources."""
|
||||
resource_class = ShareNetwork
|
||||
|
||||
def create(self, neutron_net_id=None, neutron_subnet_id=None,
|
||||
nova_net_id=None, name=None, description=None):
|
||||
"""Create share network.
|
||||
|
||||
:param neutron_net_id: ID of Neutron network
|
||||
:param neutron_subnet_id: ID of Neutron subnet
|
||||
:param nova_net_id: ID of Nova network
|
||||
:param name: share network name
|
||||
:param description: share network description
|
||||
:rtype: :class:`ShareNetwork`
|
||||
"""
|
||||
values = {}
|
||||
if neutron_net_id:
|
||||
values['neutron_net_id'] = neutron_net_id
|
||||
if neutron_subnet_id:
|
||||
values['neutron_subnet_id'] = neutron_subnet_id
|
||||
if nova_net_id:
|
||||
values['nova_net_id'] = nova_net_id
|
||||
if name:
|
||||
values['name'] = name
|
||||
if description:
|
||||
values['description'] = description
|
||||
|
||||
body = {RESOURCE_NAME: values}
|
||||
|
||||
return self._create(RESOURCES_PATH, body, RESOURCE_NAME)
|
||||
|
||||
def add_security_service(self, share_network, security_service):
|
||||
"""Associate given security service with a share network.
|
||||
|
||||
:param share_network: share network name, id or ShareNetwork instance
|
||||
:param security_service: name, id or SecurityService instance
|
||||
:rtype: :class:`ShareNetwork`
|
||||
"""
|
||||
body = {
|
||||
'add_security_service': {
|
||||
'security_service_id': common_base.getid(security_service),
|
||||
},
|
||||
}
|
||||
return self._create(
|
||||
RESOURCE_PATH % common_base.getid(share_network) + '/action',
|
||||
body,
|
||||
RESOURCE_NAME,
|
||||
)
|
||||
|
||||
def remove_security_service(self, share_network, security_service):
|
||||
"""Dissociate security service from a share network.
|
||||
|
||||
:param share_network: share network name, id or ShareNetwork instance
|
||||
:param security_service: name, id or SecurityService instance
|
||||
:rtype: :class:`ShareNetwork`
|
||||
"""
|
||||
body = {
|
||||
'remove_security_service': {
|
||||
'security_service_id': common_base.getid(security_service),
|
||||
},
|
||||
}
|
||||
return self._create(
|
||||
RESOURCE_PATH % common_base.getid(share_network) + '/action',
|
||||
body,
|
||||
RESOURCE_NAME,
|
||||
)
|
||||
|
||||
def get(self, share_network):
|
||||
"""Get a share network.
|
||||
|
||||
:param policy: share network to get.
|
||||
:rtype: :class:`NetworkInfo`
|
||||
"""
|
||||
return self._get(RESOURCE_PATH % common_base.getid(share_network),
|
||||
RESOURCE_NAME)
|
||||
|
||||
def update(self, share_network, neutron_net_id=None,
|
||||
neutron_subnet_id=None, nova_net_id=None,
|
||||
name=None, description=None):
|
||||
"""Updates a share network.
|
||||
|
||||
:param share_network: share network to update.
|
||||
:rtype: :class:`ShareNetwork`
|
||||
"""
|
||||
values = {}
|
||||
if neutron_net_id is not None:
|
||||
values['neutron_net_id'] = neutron_net_id
|
||||
if neutron_subnet_id is not None:
|
||||
values['neutron_subnet_id'] = neutron_subnet_id
|
||||
if nova_net_id is not None:
|
||||
values['nova_net_id'] = nova_net_id
|
||||
if name is not None:
|
||||
values['name'] = name
|
||||
if description is not None:
|
||||
values['description'] = description
|
||||
|
||||
for k, v in six.iteritems(values):
|
||||
if v == '':
|
||||
values[k] = None
|
||||
|
||||
if not values:
|
||||
msg = "Must specify fields to be updated"
|
||||
raise exceptions.CommandError(msg)
|
||||
|
||||
body = {RESOURCE_NAME: values}
|
||||
return self._update(RESOURCE_PATH % common_base.getid(share_network),
|
||||
body,
|
||||
RESOURCE_NAME)
|
||||
|
||||
def delete(self, share_network):
|
||||
"""Delete a share network.
|
||||
|
||||
:param share_network: share network to be deleted.
|
||||
"""
|
||||
self._delete(RESOURCE_PATH % common_base.getid(share_network))
|
||||
|
||||
def list(self, detailed=True, search_opts=None):
|
||||
"""Get a list of all share network.
|
||||
|
||||
:rtype: list of :class:`NetworkInfo`
|
||||
"""
|
||||
if search_opts:
|
||||
query_string = urlencode(
|
||||
sorted([(k, v) for (k, v) in list(search_opts.items()) if v]))
|
||||
if query_string:
|
||||
query_string = "?%s" % query_string
|
||||
else:
|
||||
query_string = ''
|
||||
|
||||
if detailed:
|
||||
path = RESOURCES_PATH + "/detail" + query_string
|
||||
else:
|
||||
path = RESOURCES_PATH + query_string
|
||||
|
||||
return self._list(path, RESOURCES_NAME)
|
|
@ -0,0 +1,92 @@
|
|||
# Copyright 2014 OpenStack Foundation.
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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 six
|
||||
try:
|
||||
from urllib import urlencode # noqa
|
||||
except ImportError:
|
||||
from urllib.parse import urlencode # noqa
|
||||
|
||||
from manilaclient import base
|
||||
from manilaclient.openstack.common.apiclient import base as common_base
|
||||
|
||||
RESOURCES_PATH = '/share-servers'
|
||||
RESOURCE_PATH = '/share-servers/%s'
|
||||
RESOURCES_NAME = 'share_servers'
|
||||
RESOURCE_NAME = 'share_server'
|
||||
|
||||
|
||||
class ShareServer(common_base.Resource):
|
||||
|
||||
def __repr__(self):
|
||||
return "<ShareServer: %s>" % self.id
|
||||
|
||||
def __getattr__(self, attr):
|
||||
if attr == 'share_network':
|
||||
attr = 'share_network_name'
|
||||
return super(ShareServer, self).__getattr__(attr)
|
||||
|
||||
|
||||
class ShareServerManager(base.Manager):
|
||||
"""Manage :class:`ShareServer` resources."""
|
||||
resource_class = ShareServer
|
||||
|
||||
def get(self, server_id):
|
||||
"""Get a share server.
|
||||
|
||||
:param server_id: The ID of the share server to get.
|
||||
:rtype: :class:`ShareServer`
|
||||
"""
|
||||
server = self._get("%s/%s" % (RESOURCES_PATH, server_id),
|
||||
RESOURCE_NAME)
|
||||
# Split big dict 'backend_details' to separated strings
|
||||
# as next:
|
||||
# +---------------------+------------------------------------+
|
||||
# | Property | Value |
|
||||
# +---------------------+------------------------------------+
|
||||
# | details:instance_id |35203a78-c733-4b1f-b82c-faded312e537|
|
||||
# +---------------------+------------------------------------+
|
||||
for k, v in six.iteritems(server._info["backend_details"]):
|
||||
server._info["details:%s" % k] = v
|
||||
return server
|
||||
|
||||
def details(self, server_id):
|
||||
"""Get a share server details.
|
||||
|
||||
:param server_id: The ID of the share server to get details from.
|
||||
:rtype: list of :class:`ShareServerBackendDetails
|
||||
"""
|
||||
return self._get("%s/%s/details" % (RESOURCES_PATH, server_id),
|
||||
"details")
|
||||
|
||||
def delete(self, server_id):
|
||||
"""Delete share server.
|
||||
|
||||
:param server_id: id of share server to be deleted.
|
||||
"""
|
||||
self._delete(RESOURCE_PATH % server_id)
|
||||
|
||||
def list(self, search_opts=None):
|
||||
"""Get a list of share servers.
|
||||
|
||||
:rtype: list of :class:`ShareServer`
|
||||
"""
|
||||
query_string = ''
|
||||
if search_opts:
|
||||
opts = sorted(
|
||||
[(k, v) for (k, v) in six.iteritems(search_opts) if v])
|
||||
query_string = urlencode(opts)
|
||||
query_string = '?' + query_string if query_string else ''
|
||||
return self._list(RESOURCES_PATH + query_string, RESOURCES_NAME)
|
|
@ -0,0 +1,157 @@
|
|||
# Copyright 2012 NetApp
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
"""Interface for shares extension."""
|
||||
|
||||
try:
|
||||
from urllib import urlencode # noqa
|
||||
except ImportError:
|
||||
from urllib.parse import urlencode # noqa
|
||||
|
||||
from manilaclient import base
|
||||
from manilaclient.common import constants
|
||||
from manilaclient.openstack.common.apiclient import base as common_base
|
||||
|
||||
|
||||
class ShareSnapshot(common_base.Resource):
|
||||
"""Represent a snapshot of a share."""
|
||||
|
||||
def __repr__(self):
|
||||
return "<ShareSnapshot: %s>" % self.id
|
||||
|
||||
def update(self, **kwargs):
|
||||
"""Update this snapshot."""
|
||||
self.manager.update(self, **kwargs)
|
||||
|
||||
def reset_state(self, state):
|
||||
"""Update the snapshot with the privided state."""
|
||||
self.manager.reset_state(self, state)
|
||||
|
||||
def delete(self):
|
||||
"""Delete this snapshot."""
|
||||
self.manager.delete(self)
|
||||
|
||||
def force_delete(self):
|
||||
"""Delete the specified snapshot ignoring its current state."""
|
||||
self.manager.force_delete(self)
|
||||
|
||||
|
||||
class ShareSnapshotManager(base.ManagerWithFind):
|
||||
"""Manage :class:`ShareSnapshot` resources."""
|
||||
resource_class = ShareSnapshot
|
||||
|
||||
def create(self, share, force=False, name=None, description=None):
|
||||
"""Create a snapshot of the given share.
|
||||
|
||||
:param share_id: The ID of the share to snapshot.
|
||||
:param force: If force is True, create a snapshot even if the
|
||||
share is busy. Default is False.
|
||||
:param name: Name of the snapshot
|
||||
:param description: Description of the snapshot
|
||||
:rtype: :class:`ShareSnapshot`
|
||||
"""
|
||||
body = {'snapshot': {'share_id': common_base.getid(share),
|
||||
'force': force,
|
||||
'name': name,
|
||||
'description': description}}
|
||||
return self._create('/snapshots', body, 'snapshot')
|
||||
|
||||
def get(self, snapshot):
|
||||
"""Get a snapshot.
|
||||
|
||||
:param snapshot: The :class:`ShareSnapshot` instance or string with ID
|
||||
of snapshot to delete.
|
||||
:rtype: :class:`ShareSnapshot`
|
||||
"""
|
||||
snapshot_id = common_base.getid(snapshot)
|
||||
return self._get('/snapshots/%s' % snapshot_id, 'snapshot')
|
||||
|
||||
def list(self, detailed=True, search_opts=None, sort_key=None,
|
||||
sort_dir=None):
|
||||
"""Get a list of snapshots of shares.
|
||||
|
||||
:param search_opts: Search options to filter out shares.
|
||||
:param sort_key: Key to be sorted.
|
||||
:param sort_dir: Sort direction, should be 'desc' or 'asc'.
|
||||
:rtype: list of :class:`ShareSnapshot`
|
||||
"""
|
||||
if search_opts is None:
|
||||
search_opts = {}
|
||||
|
||||
if sort_key is not None:
|
||||
if sort_key in constants.SNAPSHOT_SORT_KEY_VALUES:
|
||||
search_opts['sort_key'] = sort_key
|
||||
else:
|
||||
raise ValueError(
|
||||
'sort_key must be one of the following: %s.'
|
||||
% ', '.join(constants.SNAPSHOT_SORT_KEY_VALUES))
|
||||
|
||||
if sort_dir is not None:
|
||||
if sort_dir in constants.SORT_DIR_VALUES:
|
||||
search_opts['sort_dir'] = sort_dir
|
||||
else:
|
||||
raise ValueError(
|
||||
'sort_dir must be one of the following: %s.'
|
||||
% ', '.join(constants.SORT_DIR_VALUES))
|
||||
|
||||
if search_opts:
|
||||
query_string = urlencode(
|
||||
sorted([(k, v) for (k, v) in list(search_opts.items()) if v]))
|
||||
if query_string:
|
||||
query_string = "?%s" % (query_string,)
|
||||
else:
|
||||
query_string = ''
|
||||
|
||||
if detailed:
|
||||
path = "/snapshots/detail%s" % (query_string,)
|
||||
else:
|
||||
path = "/snapshots%s" % (query_string,)
|
||||
|
||||
return self._list(path, 'snapshots')
|
||||
|
||||
def delete(self, snapshot):
|
||||
"""Delete a snapshot of a share.
|
||||
|
||||
:param snapshot: The :class:`ShareSnapshot` to delete.
|
||||
"""
|
||||
self._delete("/snapshots/%s" % common_base.getid(snapshot))
|
||||
|
||||
def force_delete(self, snapshot):
|
||||
"""Delete the specified snapshot ignoring its current state."""
|
||||
return self._action('os-force_delete', common_base.getid(snapshot))
|
||||
|
||||
def update(self, snapshot, **kwargs):
|
||||
"""Update a snapshot.
|
||||
|
||||
:param snapshot: The :class:`ShareSnapshot` instance or string with ID
|
||||
of snapshot to delete.
|
||||
:rtype: :class:`ShareSnapshot`
|
||||
"""
|
||||
if not kwargs:
|
||||
return
|
||||
|
||||
body = {'snapshot': kwargs, }
|
||||
snapshot_id = common_base.getid(snapshot)
|
||||
return self._update("/snapshots/%s" % snapshot_id, body)
|
||||
|
||||
def reset_state(self, snapshot, state):
|
||||
"""Update the specified share snapshot with the provided state."""
|
||||
return self._action('os-reset_status', snapshot, {'status': state})
|
||||
|
||||
def _action(self, action, snapshot, info=None, **kwargs):
|
||||
"""Perform a snapshot 'action'."""
|
||||
body = {action: info}
|
||||
self.run_hooks('modify_body_for_action', body, **kwargs)
|
||||
url = '/snapshots/%s/action' % common_base.getid(snapshot)
|
||||
return self.api.client.post(url, body=body)
|
|
@ -0,0 +1,54 @@
|
|||
# Copyright 2014 OpenStack Foundation
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Share type access interface."""
|
||||
|
||||
from manilaclient import base
|
||||
from manilaclient.openstack.common.apiclient import base as common_base
|
||||
|
||||
|
||||
class ShareTypeAccess(common_base.Resource):
|
||||
def __repr__(self):
|
||||
return "<ShareTypeAccess: %s>" % self.id
|
||||
|
||||
|
||||
class ShareTypeAccessManager(base.ManagerWithFind):
|
||||
"""Manage :class:`ShareTypeAccess` resources."""
|
||||
|
||||
resource_class = ShareTypeAccess
|
||||
|
||||
def list(self, share_type):
|
||||
if share_type.is_public:
|
||||
return None
|
||||
|
||||
return self._list(
|
||||
'/types/%s/os-share-type-access' % common_base.getid(share_type),
|
||||
'share_type_access')
|
||||
|
||||
def add_project_access(self, share_type, project):
|
||||
"""Add a project to the given share type access list."""
|
||||
info = {'project': project}
|
||||
self._action('addProjectAccess', share_type, info)
|
||||
|
||||
def remove_project_access(self, share_type, project):
|
||||
"""Remove a project from the given share type access list."""
|
||||
info = {'project': project}
|
||||
self._action('removeProjectAccess', share_type, info)
|
||||
|
||||
def _action(self, action, share_type, info, **kwargs):
|
||||
"""Perform a share type action."""
|
||||
body = {action: info}
|
||||
self.run_hooks('modify_body_for_action', body, **kwargs)
|
||||
url = '/types/%s/action' % common_base.getid(share_type)
|
||||
return self.api.client.post(url, body=body)
|
|
@ -0,0 +1,153 @@
|
|||
# Copyright (c) 2011 Rackspace US, 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.
|
||||
|
||||
|
||||
"""
|
||||
Share Type interface.
|
||||
"""
|
||||
|
||||
from manilaclient import base
|
||||
from manilaclient.openstack.common.apiclient import base as common_base
|
||||
|
||||
|
||||
class ShareType(common_base.Resource):
|
||||
"""A Share Type is the type of share to be created."""
|
||||
|
||||
def __init__(self, manager, info, loaded=False):
|
||||
super(ShareType, self).__init__(manager, info, loaded)
|
||||
self._required_extra_specs = info.get('required_extra_specs', {})
|
||||
self._optional_extra_specs = {
|
||||
'snapshot_support': info.get('extra_specs', {}).get(
|
||||
'snapshot_support', 'unknown'),
|
||||
}
|
||||
|
||||
def __repr__(self):
|
||||
return "<ShareType: %s>" % self.name
|
||||
|
||||
@property
|
||||
def is_public(self):
|
||||
"""Provide a user-friendly accessor to os-share-type-access."""
|
||||
return self._info.get("os-share-type-access:is_public", 'N/A')
|
||||
|
||||
def get_keys(self, prefer_resource_data=True):
|
||||
"""Get extra specs from a share type.
|
||||
|
||||
:param prefer_resource_data: By default extra_specs are retrieved from
|
||||
resource data, but user can force this method to make API call.
|
||||
:return: dict with extra specs
|
||||
"""
|
||||
extra_specs = getattr(self, 'extra_specs', None)
|
||||
|
||||
if prefer_resource_data and extra_specs:
|
||||
return extra_specs
|
||||
|
||||
_resp, body = self.manager.api.client.get(
|
||||
"/types/%s/extra_specs" % common_base.getid(self))
|
||||
|
||||
self.extra_specs = body["extra_specs"]
|
||||
|
||||
return body["extra_specs"]
|
||||
|
||||
def get_required_keys(self):
|
||||
return self._required_extra_specs
|
||||
|
||||
def get_optional_keys(self):
|
||||
return self._optional_extra_specs
|
||||
|
||||
def set_keys(self, metadata):
|
||||
"""Set extra specs on a share type.
|
||||
|
||||
:param type : The :class:`ShareType` to set extra spec on
|
||||
:param metadata: A dict of key/value pairs to be set
|
||||
"""
|
||||
body = {'extra_specs': metadata}
|
||||
return self.manager._create(
|
||||
"/types/%s/extra_specs" % common_base.getid(self),
|
||||
body,
|
||||
"extra_specs",
|
||||
return_raw=True,
|
||||
)
|
||||
|
||||
def unset_keys(self, keys):
|
||||
"""Unset extra specs on a share type.
|
||||
|
||||
:param type_id: The :class:`ShareType` to unset extra spec on
|
||||
:param keys: A list of keys to be unset
|
||||
"""
|
||||
|
||||
# NOTE(jdg): This wasn't actually doing all of the keys before
|
||||
# the return in the loop resulted in ony ONE key being unset.
|
||||
# since on success the return was NONE, we'll only interrupt the loop
|
||||
# and return if there's an error
|
||||
resp = None
|
||||
for k in keys:
|
||||
resp = self.manager._delete(
|
||||
"/types/%s/extra_specs/%s" % (common_base.getid(self), k))
|
||||
if resp is not None:
|
||||
return resp
|
||||
|
||||
|
||||
class ShareTypeManager(base.ManagerWithFind):
|
||||
"""Manage :class:`ShareType` resources."""
|
||||
|
||||
resource_class = ShareType
|
||||
|
||||
def list(self, search_opts=None, show_all=True):
|
||||
"""Get a list of all share types.
|
||||
|
||||
:rtype: list of :class:`ShareType`.
|
||||
"""
|
||||
query_string = ''
|
||||
if show_all:
|
||||
query_string = '?is_public=all'
|
||||
return self._list("/types%s" % query_string, "share_types")
|
||||
|
||||
def get(self, share_type="default"):
|
||||
"""Get a specific share type.
|
||||
|
||||
:param share_type: The ID of the :class:`ShareType` to get.
|
||||
:rtype: :class:`ShareType`
|
||||
"""
|
||||
return self._get("/types/%s" % common_base.getid(share_type),
|
||||
"share_type")
|
||||
|
||||
def delete(self, share_type):
|
||||
"""Delete a specific share_type.
|
||||
|
||||
:param share_type: The name or ID of the :class:`ShareType` to get.
|
||||
"""
|
||||
self._delete("/types/%s" % common_base.getid(share_type))
|
||||
|
||||
def create(self, name, spec_driver_handles_share_servers,
|
||||
spec_snapshot_support=True, is_public=True):
|
||||
"""Create a share type.
|
||||
|
||||
:param name: Descriptive name of the share type
|
||||
:rtype: :class:`ShareType`
|
||||
"""
|
||||
|
||||
body = {
|
||||
"share_type": {
|
||||
"name": name,
|
||||
"os-share-type-access:is_public": is_public,
|
||||
"extra_specs": {
|
||||
"driver_handles_share_servers":
|
||||
spec_driver_handles_share_servers,
|
||||
"snapshot_support": spec_snapshot_support,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
return self._create("/types", body, "share_type")
|
|
@ -0,0 +1,468 @@
|
|||
# Copyright 2012 NetApp
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# 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.
|
||||
"""Interface for shares extension."""
|
||||
|
||||
import collections
|
||||
import re
|
||||
try:
|
||||
from urllib import urlencode # noqa
|
||||
except ImportError:
|
||||
from urllib.parse import urlencode # noqa
|
||||
|
||||
from manilaclient import base
|
||||
from manilaclient.common import constants
|
||||
from manilaclient import exceptions
|
||||
from manilaclient.openstack.common.apiclient import base as common_base
|
||||
from manilaclient.v2 import share_instances
|
||||
|
||||
|
||||
class Share(common_base.Resource):
|
||||
"""A share is an extra block level storage to the OpenStack instances."""
|
||||
def __repr__(self):
|
||||
return "<Share: %s>" % self.id
|
||||
|
||||
def update(self, **kwargs):
|
||||
"""Update this share."""
|
||||
self.manager.update(self, **kwargs)
|
||||
|
||||
def unmanage(self, **kwargs):
|
||||
"""Unmanage this share."""
|
||||
self.manager.unmanage(self, **kwargs)
|
||||
|
||||
def migrate_share(self, host, force_host_copy):
|
||||
"""Migrate the share to a new host."""
|
||||
self.manager.migrate_share(self, host, force_host_copy)
|
||||
|
||||
def delete(self, consistency_group_id=None):
|
||||
"""Delete this share."""
|
||||
self.manager.delete(self, consistency_group_id=consistency_group_id)
|
||||
|
||||
def force_delete(self):
|
||||
"""Delete the specified share ignoring its current state."""
|
||||
self.manager.force_delete(self)
|
||||
|
||||
def allow(self, access_type, access, access_level):
|
||||
"""Allow access to a share."""
|
||||
self._validate_access(access_type, access)
|
||||
return self.manager.allow(self, access_type, access, access_level)
|
||||
|
||||
def deny(self, id):
|
||||
"""Deny access from IP to a share."""
|
||||
return self.manager.deny(self, id)
|
||||
|
||||
def access_list(self):
|
||||
"""Deny access from IP to a share."""
|
||||
return self.manager.access_list(self)
|
||||
|
||||
def _validate_access(self, access_type, access):
|
||||
if access_type == 'ip':
|
||||
self._validate_ip_range(access)
|
||||
elif access_type == 'user':
|
||||
self._validate_username(access)
|
||||
elif access_type == 'cert':
|
||||
# 'access' is used as the certificate's CN (common name)
|
||||
# to which access is allowed or denied by the backend.
|
||||
# The standard allows for just about any string in the
|
||||
# common name. The meaning of a string depends on its
|
||||
# interpretation and is limited to 64 characters.
|
||||
self._validate_common_name(access.strip())
|
||||
else:
|
||||
raise exceptions.CommandError(
|
||||
'Only ip, user, and cert types are supported')
|
||||
|
||||
def update_all_metadata(self, metadata):
|
||||
"""Update all metadata of this share."""
|
||||
return self.manager.update_all_metadata(self, metadata)
|
||||
|
||||
@staticmethod
|
||||
def _validate_common_name(access):
|
||||
if len(access) == 0 or len(access) > 64:
|
||||
exc_str = ('Invalid CN (common name). Must be 1-64 chars long.')
|
||||
raise exceptions.CommandError(exc_str)
|
||||
|
||||
@staticmethod
|
||||
def _validate_username(access):
|
||||
valid_username_re = '[\w\.\-_\`;\'\{\}\[\]\\\\]{4,32}$'
|
||||
username = access
|
||||
if not re.match(valid_username_re, username):
|
||||
exc_str = ('Invalid user or group name. Must be 4-32 characters '
|
||||
'and consist of alphanumeric characters and '
|
||||
'special characters ]{.-_\'`;}[\\')
|
||||
raise exceptions.CommandError(exc_str)
|
||||
|
||||
@staticmethod
|
||||
def _validate_ip_range(ip_range):
|
||||
ip_range = ip_range.split('/')
|
||||
exc_str = ('Supported ip format examples:\n'
|
||||
'\t10.0.0.2, 10.0.0.0/24')
|
||||
if len(ip_range) > 2:
|
||||
raise exceptions.CommandError(exc_str)
|
||||
if len(ip_range) == 2:
|
||||
try:
|
||||
prefix = int(ip_range[1])
|
||||
if prefix < 0 or prefix > 32:
|
||||
raise ValueError()
|
||||
except ValueError:
|
||||
msg = 'IP prefix should be in range from 0 to 32'
|
||||
raise exceptions.CommandError(msg)
|
||||
ip_range = ip_range[0].split('.')
|
||||
if len(ip_range) != 4:
|
||||
raise exceptions.CommandError(exc_str)
|
||||
for item in ip_range:
|
||||
try:
|
||||
if 0 <= int(item) <= 255:
|
||||
continue
|
||||
raise ValueError()
|
||||
except ValueError:
|
||||
raise exceptions.CommandError(exc_str)
|
||||
|
||||
def reset_state(self, state):
|
||||
"""Update the share with the provided state."""
|
||||
self.manager.reset_state(self, state)
|
||||
|
||||
def extend(self, new_size):
|
||||
"""Extend the size of the specified share."""
|
||||
self.manager.extend(self, new_size)
|
||||
|
||||
def shrink(self, new_size):
|
||||
"""Shrink the size of the specified share."""
|
||||
self.manager.shrink(self, new_size)
|
||||
|
||||
def list_instances(self):
|
||||
"""List instances of the specified share."""
|
||||
self.manager.list_instances(self)
|
||||
|
||||
|
||||
class ShareManager(base.ManagerWithFind):
|
||||
"""Manage :class:`Share` resources."""
|
||||
resource_class = Share
|
||||
|
||||
def create(self, share_proto, size, snapshot_id=None, name=None,
|
||||
description=None, metadata=None, share_network=None,
|
||||
share_type=None, is_public=False, availability_zone=None,
|
||||
consistency_group_id=None):
|
||||
"""Create a share.
|
||||
|
||||
:param share_proto: text - share protocol for new share
|
||||
available values are NFS, CIFS, GlusterFS and HDFS.
|
||||
:param size: int - size in GB
|
||||
:param snapshot_id: text - ID of the snapshot
|
||||
:param name: text - name of new share
|
||||
:param description: text - description of a share
|
||||
:param metadata: dict - optional metadata to set on share creation
|
||||
:param share_network: either instance of ShareNetwork or text with ID
|
||||
:param share_type: either instance of ShareType or text with ID
|
||||
:param is_public: bool, whether to set share as public or not.
|
||||
:param consistency_group_id: text - ID of the consistency group to
|
||||
which the share should belong
|
||||
:rtype: :class:`Share`
|
||||
"""
|
||||
share_metadata = metadata if metadata is not None else dict()
|
||||
body = {
|
||||
'size': size,
|
||||
'snapshot_id': snapshot_id,
|
||||
'name': name,
|
||||
'description': description,
|
||||
'metadata': share_metadata,
|
||||
'share_proto': share_proto,
|
||||
'share_network_id': common_base.getid(share_network),
|
||||
'share_type': common_base.getid(share_type),
|
||||
'is_public': is_public,
|
||||
'availability_zone': availability_zone,
|
||||
'consistency_group_id': consistency_group_id,
|
||||
}
|
||||
return self._create('/shares', {'share': body}, 'share')
|
||||
|
||||
def migrate_share(self, share, host, force_host_copy):
|
||||
"""Migrate share to new host and pool.
|
||||
|
||||
:param share: The :class:'share' to migrate
|
||||
:param host: The destination host and pool
|
||||
:param force_host_copy: Skip driver optimizations
|
||||
"""
|
||||
|
||||
return self._action('os-migrate_share',
|
||||
share, {'host': host,
|
||||
'force_host_copy': force_host_copy})
|
||||
|
||||
def manage(self, service_host, protocol, export_path,
|
||||
driver_options=None, share_type=None,
|
||||
name=None, description=None):
|
||||
"""Manage some existing share.
|
||||
|
||||
:param service_host: text - host of share service where share is runing
|
||||
:param protocol: text - share protocol that is used
|
||||
:param export_path: text - export path of share
|
||||
:param driver_options: dict - custom set of key-values.
|
||||
:param share_type: text - share type that should be used for share
|
||||
:param name: text - name of new share
|
||||
:param description: - description for new share
|
||||
"""
|
||||
driver_options = driver_options if driver_options else dict()
|
||||
body = {
|
||||
'service_host': service_host,
|
||||
'share_type': share_type,
|
||||
'protocol': protocol,
|
||||
'export_path': export_path,
|
||||
'driver_options': driver_options,
|
||||
'name': name,
|
||||
'description': description
|
||||
}
|
||||
return self._create('/os-share-manage', {'share': body}, 'share')
|
||||
|
||||
def unmanage(self, share):
|
||||
"""Unmanage a share.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
"""
|
||||
return self.api.client.post(
|
||||
"/os-share-unmanage/%s/unmanage" % common_base.getid(share))
|
||||
|
||||
def get(self, share):
|
||||
"""Get a share.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
:rtype: :class:`Share`
|
||||
"""
|
||||
share_id = common_base.getid(share)
|
||||
return self._get("/shares/%s" % share_id, "share")
|
||||
|
||||
def update(self, share, **kwargs):
|
||||
"""Updates a share.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
:rtype: :class:`Share`
|
||||
"""
|
||||
if not kwargs:
|
||||
return
|
||||
|
||||
body = {'share': kwargs, }
|
||||
share_id = common_base.getid(share)
|
||||
return self._update("/shares/%s" % share_id, body)
|
||||
|
||||
def list(self, detailed=True, search_opts=None,
|
||||
sort_key=None, sort_dir=None):
|
||||
"""Get a list of all shares.
|
||||
|
||||
:param detailed: Whether to return detailed share info or not.
|
||||
:param search_opts: dict with search options to filter out shares.
|
||||
available keys are below (('name1', 'name2', ...), 'type'):
|
||||
- ('all_tenants', int)
|
||||
- ('is_public', bool)
|
||||
- ('metadata', dict)
|
||||
- ('extra_specs', dict)
|
||||
- ('limit', int)
|
||||
- ('offset', int)
|
||||
- ('name', text)
|
||||
- ('status', text)
|
||||
- ('host', text)
|
||||
- ('share_server_id', text)
|
||||
- (('share_network_id', 'share_network'), text)
|
||||
- (('share_type_id', 'share_type'), text)
|
||||
- (('snapshot_id', 'snapshot'), text)
|
||||
Note, that member context will have restricted set of
|
||||
available search opts. For admin context filtering also available
|
||||
by each share attr from its Model. So, this list is not full for
|
||||
admin context.
|
||||
:param sort_key: Key to be sorted (i.e. 'created_at' or 'status').
|
||||
:param sort_dir: Sort direction, should be 'desc' or 'asc'.
|
||||
:rtype: list of :class:`Share`
|
||||
"""
|
||||
if search_opts is None:
|
||||
search_opts = {}
|
||||
|
||||
if sort_key is not None:
|
||||
if sort_key in constants.SHARE_SORT_KEY_VALUES:
|
||||
search_opts['sort_key'] = sort_key
|
||||
# NOTE(vponomaryov): Replace aliases with appropriate keys
|
||||
if sort_key == 'share_type':
|
||||
search_opts['sort_key'] = 'share_type_id'
|
||||
elif sort_key == 'snapshot':
|
||||
search_opts['sort_key'] = 'snapshot_id'
|
||||
elif sort_key == 'share_network':
|
||||
search_opts['sort_key'] = 'share_network_id'
|
||||
else:
|
||||
raise ValueError('sort_key must be one of the following: %s.'
|
||||
% ', '.join(constants.SHARE_SORT_KEY_VALUES))
|
||||
|
||||
if sort_dir is not None:
|
||||
if sort_dir in constants.SORT_DIR_VALUES:
|
||||
search_opts['sort_dir'] = sort_dir
|
||||
else:
|
||||
raise ValueError('sort_dir must be one of the following: %s.'
|
||||
% ', '.join(constants.SORT_DIR_VALUES))
|
||||
|
||||
if 'is_public' not in search_opts:
|
||||
search_opts['is_public'] = True
|
||||
|
||||
if search_opts:
|
||||
query_string = urlencode(
|
||||
sorted([(k, v) for (k, v) in list(search_opts.items()) if v]))
|
||||
if query_string:
|
||||
query_string = "?%s" % (query_string,)
|
||||
else:
|
||||
query_string = ''
|
||||
|
||||
if detailed:
|
||||
path = "/shares/detail%s" % (query_string,)
|
||||
else:
|
||||
path = "/shares%s" % (query_string,)
|
||||
|
||||
return self._list(path, 'shares')
|
||||
|
||||
def delete(self, share, consistency_group_id=None):
|
||||
"""Delete a share.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
:param consistency_group_id: text - ID of the consistency group to
|
||||
which the share belongs to.
|
||||
"""
|
||||
url = "/shares/%s" % common_base.getid(share)
|
||||
if consistency_group_id:
|
||||
url += "?consistency_group_id=%s" % consistency_group_id
|
||||
self._delete(url)
|
||||
|
||||
def force_delete(self, share):
|
||||
"""Delete a share forcibly - share status will be avoided.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
"""
|
||||
return self._action('os-force_delete', common_base.getid(share))
|
||||
|
||||
def allow(self, share, access_type, access, access_level):
|
||||
"""Allow access to a share.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
:param access_type: string that represents access type ('ip','domain')
|
||||
:param access: string that represents access ('127.0.0.1')
|
||||
:param access_level: string that represents access level ('rw', 'ro')
|
||||
"""
|
||||
access_params = {
|
||||
'access_type': access_type,
|
||||
'access_to': access,
|
||||
}
|
||||
if access_level:
|
||||
access_params['access_level'] = access_level
|
||||
access = self._action('os-allow_access', share,
|
||||
access_params)[1]["access"]
|
||||
|
||||
return access
|
||||
|
||||
def deny(self, share, access_id):
|
||||
"""Deny access to a share.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
:param access_id: ID of share access rule
|
||||
"""
|
||||
return self._action('os-deny_access', share, {'access_id': access_id})
|
||||
|
||||
def access_list(self, share):
|
||||
"""Get access list to a share.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
"""
|
||||
access_list = self._action("os-access_list", share)[1]["access_list"]
|
||||
if access_list:
|
||||
t = collections.namedtuple('Access', list(access_list[0]))
|
||||
return [t(*value.values()) for value in access_list]
|
||||
else:
|
||||
return []
|
||||
|
||||
def get_metadata(self, share):
|
||||
"""Get metadata of a share.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
"""
|
||||
return self._get("/shares/%s/metadata" % common_base.getid(share),
|
||||
"metadata")
|
||||
|
||||
def set_metadata(self, share, metadata):
|
||||
"""Set or update metadata for share.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
:param metadata: A list of keys to be set.
|
||||
"""
|
||||
body = {'metadata': metadata}
|
||||
return self._create("/shares/%s/metadata" % common_base.getid(share),
|
||||
body, "metadata")
|
||||
|
||||
def delete_metadata(self, share, keys):
|
||||
"""Delete specified keys from shares metadata.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
:param keys: A list of keys to be removed.
|
||||
"""
|
||||
share_id = common_base.getid(share)
|
||||
for key in keys:
|
||||
self._delete("/shares/%(share_id)s/metadata/%(key)s" % {
|
||||
'share_id': share_id, 'key': key})
|
||||
|
||||
def update_all_metadata(self, share, metadata):
|
||||
"""Update all metadata of a share.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
:param metadata: A list of keys to be updated.
|
||||
"""
|
||||
body = {'metadata': metadata}
|
||||
return self._update("/shares/%s/metadata" % common_base.getid(share),
|
||||
body)
|
||||
|
||||
def _action(self, action, share, info=None, **kwargs):
|
||||
"""Perform a share 'action'.
|
||||
|
||||
:param action: text with action name.
|
||||
:param share: either share object or text with its ID.
|
||||
:param info: dict with data for specified 'action'.
|
||||
:param kwargs: dict with data to be provided for action hooks.
|
||||
"""
|
||||
body = {action: info}
|
||||
self.run_hooks('modify_body_for_action', body, **kwargs)
|
||||
url = '/shares/%s/action' % common_base.getid(share)
|
||||
return self.api.client.post(url, body=body)
|
||||
|
||||
def reset_state(self, share, state):
|
||||
"""Update the provided share with the provided state.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
:param state: text with new state to set for share.
|
||||
"""
|
||||
return self._action('os-reset_status', share, {'status': state})
|
||||
|
||||
def extend(self, share, new_size):
|
||||
"""Extend the size of the specified share.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
:param new_size: The desired size to extend share to.
|
||||
"""
|
||||
return self._action('os-extend', share, {'new_size': new_size})
|
||||
|
||||
def shrink(self, share, new_size):
|
||||
"""Shrink the size of the specified share.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
:param new_size: The desired size to shrink share to.
|
||||
"""
|
||||
return self._action('os-shrink', share, {'new_size': new_size})
|
||||
|
||||
def list_instances(self, share):
|
||||
"""List instances of the specified share.
|
||||
|
||||
:param share: either share object or text with its ID.
|
||||
"""
|
||||
return self._list(
|
||||
'/shares/%s/instances' % common_base.getid(share),
|
||||
'share_instances',
|
||||
obj_class=share_instances.ShareInstance
|
||||
)
|
|
@ -28,7 +28,7 @@ from manilaclient.common import constants
|
|||
from manilaclient import exceptions
|
||||
from manilaclient.openstack.common.apiclient import utils as apiclient_utils
|
||||
from manilaclient.openstack.common import cliutils
|
||||
from manilaclient.v1 import quotas
|
||||
from manilaclient.v2 import quotas
|
||||
|
||||
|
||||
def _poll_for_status(poll_fn, obj_id, action, final_ok_states,
|
||||
|
@ -108,6 +108,7 @@ def _print_share_instance(cs, instance):
|
|||
|
||||
|
||||
@api_versions.experimental_api
|
||||
@api_versions.wraps("2.4")
|
||||
def _find_consistency_group(cs, consistency_group):
|
||||
"""Get a consistency group ID."""
|
||||
return apiclient_utils.find_resource(cs.consistency_groups,
|
||||
|
@ -190,12 +191,13 @@ def _extract_key_value_options(args, option_name):
|
|||
return result_dict
|
||||
|
||||
|
||||
@api_versions.wraps("2.0")
|
||||
def do_api_version(cs, args):
|
||||
"""Display the API version information."""
|
||||
columns = ['ID', 'Status', 'Version', 'Min_version']
|
||||
column_labels = ['ID', 'Status', 'Version', 'Minimum Version']
|
||||
versions = cs.services.api_version()
|
||||
cliutils.print_list(versions, columns, field_labels=column_labels)
|
||||
response = cs.services.server_api_version()
|
||||
cliutils.print_list(response, columns, field_labels=column_labels)
|
||||
|
||||
|
||||
def do_endpoints(cs, args):
|
||||
|
@ -337,7 +339,6 @@ def do_quota_defaults(cs, args):
|
|||
default=None,
|
||||
help='Whether force update the quota even if the already used '
|
||||
'and reserved exceeds the new quota.')
|
||||
@cliutils.service_type('sharev2')
|
||||
def do_quota_update(cs, args):
|
||||
"""Update the quotas for a tenant/user."""
|
||||
|
||||
|
@ -368,7 +369,6 @@ def do_quota_delete(cs, args):
|
|||
'class_name',
|
||||
metavar='<class>',
|
||||
help='Name of quota class to list the quotas for.')
|
||||
@cliutils.service_type('sharev2')
|
||||
def do_quota_class_show(cs, args):
|
||||
"""List the quotas for a quota class."""
|
||||
|
||||
|
@ -413,14 +413,12 @@ def do_quota_class_show(cs, args):
|
|||
default=None,
|
||||
action='single_alias',
|
||||
help='New value for the "share_networks" quota.')
|
||||
@cliutils.service_type('sharev2')
|
||||
def do_quota_class_update(cs, args):
|
||||
"""Update the quotas for a quota class."""
|
||||
|
||||
_quota_update(cs.quota_classes, args.class_name, args)
|
||||
|
||||
|
||||
@cliutils.service_type('sharev2')
|
||||
def do_absolute_limits(cs, args):
|
||||
"""Print a list of absolute limits for a user."""
|
||||
limits = cs.limits.get().absolute
|
||||
|
@ -428,7 +426,6 @@ def do_absolute_limits(cs, args):
|
|||
cliutils.print_list(limits, columns)
|
||||
|
||||
|
||||
@cliutils.service_type('sharev2')
|
||||
def do_rate_limits(cs, args):
|
||||
"""Print a list of rate limits for a user."""
|
||||
limits = cs.limits.get().rate
|
||||
|
@ -538,8 +535,8 @@ def do_create(cs, args):
|
|||
'force-migration, which bypasses driver '
|
||||
'optimizations. Default=False.',
|
||||
default=False)
|
||||
@cliutils.service_type('sharev2')
|
||||
@api_versions.experimental_api
|
||||
@api_versions.wraps("2.5")
|
||||
def do_migrate(cs, args):
|
||||
"""Migrates share to a new host."""
|
||||
share = _find_share(cs, args.share)
|
||||
|
@ -561,7 +558,6 @@ def do_migrate(cs, args):
|
|||
nargs='+',
|
||||
default=[],
|
||||
help='Metadata to set or unset (key is only necessary on unset).')
|
||||
@cliutils.service_type('sharev2')
|
||||
def do_metadata(cs, args):
|
||||
"""Set or delete metadata on a share."""
|
||||
share = _find_share(cs, args.share)
|
||||
|
@ -577,7 +573,6 @@ def do_metadata(cs, args):
|
|||
'share',
|
||||
metavar='<share>',
|
||||
help='Name or ID of the share.')
|
||||
@cliutils.service_type('sharev2')
|
||||
def do_metadata_show(cs, args):
|
||||
"""Show metadata of given share."""
|
||||
share = _find_share(cs, args.share)
|
||||
|
@ -595,7 +590,6 @@ def do_metadata_show(cs, args):
|
|||
nargs='+',
|
||||
default=[],
|
||||
help='Metadata entry or entries to update.')
|
||||
@cliutils.service_type('sharev2')
|
||||
def do_metadata_update_all(cs, args):
|
||||
"""Update all metadata of a share."""
|
||||
share = _find_share(cs, args.share)
|
||||
|
@ -729,7 +723,6 @@ def do_force_delete(cs, args):
|
|||
'share',
|
||||
metavar='<share>',
|
||||
help='Name or ID of the NAS share.')
|
||||
@cliutils.service_type('sharev2')
|
||||
def do_show(cs, args):
|
||||
"""Show details about a NAS share."""
|
||||
share = _find_share(cs, args.share)
|
||||
|
@ -758,7 +751,6 @@ def do_show(cs, args):
|
|||
choices=['rw', 'ro'],
|
||||
help='Share access level ("rw" and "ro" access levels are supported). '
|
||||
'Defaults to None.')
|
||||
@cliutils.service_type('sharev2')
|
||||
def do_access_allow(cs, args):
|
||||
"""Allow access to the share."""
|
||||
share = _find_share(cs, args.share)
|
||||
|
@ -774,7 +766,6 @@ def do_access_allow(cs, args):
|
|||
'id',
|
||||
metavar='<id>',
|
||||
help='ID of the access rule to be deleted.')
|
||||
@cliutils.service_type('sharev2')
|
||||
def do_access_deny(cs, args):
|
||||
"""Deny access to a share."""
|
||||
share = _find_share(cs, args.share)
|
||||
|
@ -785,7 +776,6 @@ def do_access_deny(cs, args):
|
|||
'share',
|
||||
metavar='<share>',
|
||||
help='Name or ID of the share.')
|
||||
@cliutils.service_type('sharev2')
|
||||
def do_access_list(cs, args):
|
||||
"""Show access list for share."""
|
||||
share = _find_share(cs, args.share)
|
||||
|
@ -995,7 +985,7 @@ def do_list(cs, args):
|
|||
default=None,
|
||||
action='single_alias',
|
||||
help='Filter results by share ID.')
|
||||
@cliutils.service_type('sharev2')
|
||||
@api_versions.wraps("2.3")
|
||||
def do_share_instance_list(cs, args):
|
||||
"""List share instances."""
|
||||
share = _find_share(cs, args.share_id) if args.share_id else None
|
||||
|
@ -1016,7 +1006,7 @@ def do_share_instance_list(cs, args):
|
|||
'instance',
|
||||
metavar='<instance>',
|
||||
help='Name or ID of the share instance.')
|
||||
@cliutils.service_type('sharev2')
|
||||
@api_versions.wraps("2.3")
|
||||
def do_share_instance_show(cs, args):
|
||||
"""Show details about a share instance."""
|
||||
instance = _find_share_instance(cs, args.instance)
|
||||
|
@ -1028,6 +1018,7 @@ def do_share_instance_show(cs, args):
|
|||
metavar='<instance>',
|
||||
nargs='+',
|
||||
help='Name or ID of the instance(s) to force delete.')
|
||||
@api_versions.wraps("2.3")
|
||||
def do_share_instance_force_delete(cs, args):
|
||||
"""Attempt force-delete of share instance, regardless of state."""
|
||||
failure_count = 0
|
||||
|
@ -1054,7 +1045,7 @@ def do_share_instance_force_delete(cs, args):
|
|||
help=('Indicate which state to assign the instance. Options include '
|
||||
'available, error, creating, deleting, error_deleting. If no '
|
||||
'state is provided, available will be used.'))
|
||||
@cliutils.service_type('sharev2')
|
||||
@api_versions.wraps("2.3")
|
||||
def do_share_instance_reset_state(cs, args):
|
||||
"""Explicitly update the state of a share instance."""
|
||||
instance = _find_share_instance(cs, args.instance)
|
||||
|
@ -1129,7 +1120,6 @@ def do_share_instance_reset_state(cs, args):
|
|||
action='single_alias',
|
||||
help='Sort direction, available values are %(values)s. '
|
||||
'OPTIONAL: Default=None.' % {'values': constants.SORT_DIR_VALUES})
|
||||
@cliutils.service_type('sharev2')
|
||||
def do_snapshot_list(cs, args):
|
||||
"""List all the snapshots."""
|
||||
list_of_keys = [
|
||||
|
@ -1159,7 +1149,6 @@ def do_snapshot_list(cs, args):
|
|||
'snapshot',
|
||||
metavar='<snapshot>',
|
||||
help='Name or ID of the snapshot.')
|
||||
@cliutils.service_type('sharev2')
|
||||
def do_snapshot_show(cs, args):
|
||||
"""Show details about a snapshot."""
|
||||
snapshot = _find_share_snapshot(cs, args.snapshot)
|
||||
|
@ -1187,7 +1176,6 @@ def do_snapshot_show(cs, args):
|
|||
metavar='<description>',
|
||||
default=None,
|
||||
help='Optional snapshot description. (Default=None)')
|
||||
@cliutils.service_type('sharev2')
|
||||
def do_snapshot_create(cs, args):
|
||||
"""Add a new snapshot."""
|
||||
share = _find_share(cs, args.share)
|
||||
|
@ -1220,7 +1208,6 @@ def do_snapshot_create(cs, args):
|
|||
type=str,
|
||||
action="single_alias",
|
||||
help='Public share is visible for all tenants.')
|
||||
@cliutils.service_type('sharev2')
|
||||
def do_update(cs, args):
|
||||
"""Rename a share."""
|
||||
kwargs = {}
|
||||
|
@ -1252,7 +1239,6 @@ def do_update(cs, args):
|
|||
metavar='<description>',
|
||||
help='Optional snapshot description. (Default=None)',
|
||||
default=None)
|
||||
@cliutils.service_type('sharev2')
|
||||
def do_snapshot_rename(cs, args):
|
||||
"""Rename a snapshot."""
|
||||
kwargs = {}
|
||||
|
@ -1271,7 +1257,6 @@ def do_snapshot_rename(cs, args):
|
|||
'snapshot',
|
||||
metavar='<snapshot>',
|
||||
help='Name or ID of the snapshot to delete.')
|
||||
@cliutils.service_type('sharev2')
|
||||
def do_snapshot_delete(cs, args):
|
||||
"""Remove a snapshot."""
|
||||
snapshot = _find_share_snapshot(cs, args.snapshot)
|
||||
|
@ -1282,7 +1267,6 @@ def do_snapshot_delete(cs, args):
|
|||
'snapshot',
|
||||
metavar='<snapshot>',
|
||||
help='Name or ID of the snapshot to force delete.')
|
||||
@cliutils.service_type('sharev2')
|
||||
def do_snapshot_force_delete(cs, args):
|
||||
"""Attempt force-delete of snapshot, regardless of state."""
|
||||
snapshot = _find_share_snapshot(cs, args.snapshot)
|
||||
|
@ -1301,7 +1285,6 @@ def do_snapshot_force_delete(cs, args):
|
|||
'Options include available, error, creating, deleting, '
|
||||
'error_deleting. If no state is provided, '
|
||||
'available will be used.'))
|
||||
@cliutils.service_type('sharev2')
|
||||
def do_snapshot_reset_state(cs, args):
|
||||
"""Explicitly update the state of a snapshot."""
|
||||
snapshot = _find_share_snapshot(cs, args.snapshot)
|
||||
|
@ -1319,7 +1302,6 @@ def do_snapshot_reset_state(cs, args):
|
|||
help=('Indicate which state to assign the share. Options include '
|
||||
'available, error, creating, deleting, error_deleting. If no '
|
||||
'state is provided, available will be used.'))
|
||||
@cliutils.service_type('sharev2')
|
||||
def do_reset_state(cs, args):
|
||||
"""Explicitly update the state of a share."""
|
||||
share = _find_share(cs, args.share)
|
||||
|
@ -2071,7 +2053,6 @@ def _find_share_type(cs, stype):
|
|||
return apiclient_utils.find_resource(cs.share_types, stype)
|
||||
|
||||
|
||||
@cliutils.service_type('sharev2')
|
||||
@cliutils.arg(
|
||||
'--all',
|
||||
dest='all',
|
||||
|
@ -2089,7 +2070,6 @@ def do_type_list(cs, args):
|
|||
_print_share_type_list(stypes, default_share_type=default)
|
||||
|
||||
|
||||
@cliutils.service_type('sharev2')
|
||||
def do_extra_specs_list(cs, args):
|
||||
"""Print a list of current 'share types and extra specs' (Admin Only)."""
|
||||
stypes = cs.share_types.list()
|
||||
|
@ -2119,7 +2099,6 @@ def do_extra_specs_list(cs, args):
|
|||
metavar='<is_public>',
|
||||
action='single_alias',
|
||||
help="Make type accessible to the public (default true).")
|
||||
@cliutils.service_type('sharev2')
|
||||
def do_type_create(cs, args):
|
||||
"""Create a new share type."""
|
||||
kwargs = {
|
||||
|
@ -2151,7 +2130,6 @@ def do_type_create(cs, args):
|
|||
'id',
|
||||
metavar='<id>',
|
||||
help="Name or ID of the share type to delete.")
|
||||
@cliutils.service_type('sharev2')
|
||||
def do_type_delete(cs, args):
|
||||
"""Delete a specific share type."""
|
||||
share_type = _find_share_type(cs, args.id)
|
||||
|
@ -2173,7 +2151,6 @@ def do_type_delete(cs, args):
|
|||
nargs='*',
|
||||
default=None,
|
||||
help='Extra_specs to set or unset (key is only necessary on unset).')
|
||||
@cliutils.service_type('sharev2')
|
||||
def do_type_key(cs, args):
|
||||
"""Set or unset extra_spec for a share type."""
|
||||
stype = _find_share_type(cs, args.stype)
|
||||
|
@ -2205,7 +2182,6 @@ def do_type_key(cs, args):
|
|||
type=str,
|
||||
default='.*',
|
||||
help='Filter results by pool name. Regular expressions are supported.')
|
||||
@cliutils.service_type('sharev2')
|
||||
def do_pool_list(cs, args):
|
||||
"""List all backend storage pools known to the scheduler (Admin only)."""
|
||||
|
||||
|
@ -2223,7 +2199,6 @@ def do_pool_list(cs, args):
|
|||
'share_type',
|
||||
metavar='<share_type>',
|
||||
help="Filter results by share type name or ID.")
|
||||
@cliutils.service_type('sharev2')
|
||||
def do_type_access_list(cs, args):
|
||||
"""Print access information about the given share type."""
|
||||
share_type = _find_share_type(cs, args.share_type)
|
||||
|
@ -2245,7 +2220,6 @@ def do_type_access_list(cs, args):
|
|||
'project_id',
|
||||
metavar='<project_id>',
|
||||
help='Project ID to add share type access for.')
|
||||
@cliutils.service_type('sharev2')
|
||||
def do_type_access_add(cs, args):
|
||||
"""Adds share type access for the given project."""
|
||||
vtype = _find_share_type(cs, args.share_type)
|
||||
|
@ -2261,7 +2235,6 @@ def do_type_access_add(cs, args):
|
|||
'project_id',
|
||||
metavar='<project_id>',
|
||||
help='Project ID to remove share type access for.')
|
||||
@cliutils.service_type('sharev2')
|
||||
def do_type_access_remove(cs, args):
|
||||
"""Removes share type access for the given project."""
|
||||
vtype = _find_share_type(cs, args.share_type)
|
||||
|
@ -2275,7 +2248,6 @@ def do_type_access_remove(cs, args):
|
|||
metavar='<new_size>',
|
||||
type=int,
|
||||
help='New size of share, in GBs.')
|
||||
@cliutils.service_type('sharev2')
|
||||
def do_extend(cs, args):
|
||||
"""Increases the size of an existing share."""
|
||||
share = _find_share(cs, args.share)
|
||||
|
@ -2288,7 +2260,6 @@ def do_extend(cs, args):
|
|||
metavar='<new_size>',
|
||||
type=int,
|
||||
help='New size of share, in GBs.')
|
||||
@cliutils.service_type('sharev2')
|
||||
def do_shrink(cs, args):
|
||||
"""Decreases the size of an existing share."""
|
||||
share = _find_share(cs, args.share)
|
Loading…
Reference in New Issue