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:
cFouts 2015-09-01 19:12:01 -04:00 committed by Chuck Fouts
parent 47b519106a
commit c34e3b7d2c
68 changed files with 3310 additions and 1756 deletions

View File

@ -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)

View File

@ -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

View File

@ -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:

View File

@ -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)

View File

@ -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}

View File

@ -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."""

View File

@ -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}

View File

@ -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:

View File

@ -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(

View File

@ -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"))

View File

@ -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()

View File

@ -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,

View File

@ -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'],

View File

@ -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',
}

View File

@ -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': [

View File

@ -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())

View File

@ -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)

View File

@ -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()))}

View File

@ -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()

View File

@ -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()

View File

@ -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):

View File

@ -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):

View File

@ -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):

View File

@ -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 = [

View File

@ -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):

View File

@ -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):

View File

@ -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 = [

View File

@ -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 = [

View File

@ -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)

View File

@ -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()

View File

@ -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()

View File

@ -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__)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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

297
manilaclient/v2/client.py Normal file
View File

@ -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

View File

@ -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}}

View File

@ -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}}

View File

93
manilaclient/v2/limits.py Normal file
View File

@ -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")

View File

@ -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)

78
manilaclient/v2/quotas.py Normal file
View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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 []

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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)

View File

@ -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")

468
manilaclient/v2/shares.py Normal file
View File

@ -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
)

View File

@ -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)