Merge tag '1.3.1' into debian/liberty
python-cinderclient 1.3.1 release
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -6,6 +6,7 @@
|
||||
subunit.log
|
||||
*,cover
|
||||
cover
|
||||
covhtml
|
||||
*.pyc
|
||||
AUTHORS
|
||||
ChangeLog
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
If you would like to contribute to the development of OpenStack,
|
||||
you must follow the steps in the "If you're a developer"
|
||||
section of this page: [http://wiki.openstack.org/HowToContribute](http://wiki.openstack.org/HowToContribute#If_you.27re_a_developer:)
|
||||
you must follow the steps in this page: [http://docs.openstack.org/infra/manual/developers.html](http://docs.openstack.org/infra/manual/developers.html)
|
||||
|
||||
Once those steps have been completed, changes to OpenStack
|
||||
should be submitted for review via the Gerrit tool, following
|
||||
the workflow documented at [http://wiki.openstack.org/GerritWorkflow](http://wiki.openstack.org/GerritWorkflow).
|
||||
the workflow documented at [http://docs.openstack.org/infra/manual/developers.html#development-workflow](http://docs.openstack.org/infra/manual/developers.html#development-workflow).
|
||||
|
||||
Pull requests submitted through GitHub will be ignored.
|
||||
|
||||
|
||||
10
README.rst
10
README.rst
@@ -9,8 +9,8 @@ See the `OpenStack CLI guide`_ for information on how to use the ``cinder``
|
||||
command-line tool. You may also want to look at the
|
||||
`OpenStack API documentation`_.
|
||||
|
||||
.. _OpenStack CLI Guide: http://docs.openstack.org/cli/quick-start/content/
|
||||
.. _OpenStack API documentation: http://docs.openstack.org/api/
|
||||
.. _OpenStack CLI Guide: http://docs.openstack.org/user-guide/content/ch_cli.html
|
||||
.. _OpenStack API documentation: http://developer.openstack.org/api-ref.html
|
||||
|
||||
The project is hosted on `Launchpad`_, where bugs can be filed. The code is
|
||||
hosted on `Github`_. Patches must be submitted using `Gerrit`_, *not* Github
|
||||
@@ -20,11 +20,11 @@ pull requests.
|
||||
.. _Launchpad: https://launchpad.net/python-cinderclient
|
||||
.. _Gerrit: http://docs.openstack.org/infra/manual/developers.html#development-workflow
|
||||
|
||||
This code a fork of `Jacobian's python-cloudservers`__ If you need API support
|
||||
This code is a fork of `Jacobian's python-cloudservers`__. If you need API support
|
||||
for the Rackspace API solely or the BSD license, you should use that repository.
|
||||
python-cinderclient is licensed under the Apache License like the rest of OpenStack.
|
||||
|
||||
__ http://github.com/jacobian/python-cloudservers
|
||||
__ https://github.com/jacobian-archive/python-cloudservers
|
||||
|
||||
.. contents:: Contents:
|
||||
:local:
|
||||
@@ -110,7 +110,7 @@ You'll find complete documentation on the shell by running
|
||||
list-extensions List all the os-api extensions that are available.
|
||||
|
||||
Optional arguments:
|
||||
-d, --debug Print debugging output
|
||||
-d, --debug Print debugging output
|
||||
--os-username <auth-user-name>
|
||||
Defaults to env[OS_USERNAME].
|
||||
--os-password <auth-password>
|
||||
|
||||
@@ -30,7 +30,6 @@ from keystoneclient import discover
|
||||
import requests
|
||||
|
||||
from cinderclient import exceptions
|
||||
from cinderclient.openstack.common.gettextutils import _
|
||||
from cinderclient.openstack.common import importutils
|
||||
from cinderclient.openstack.common import strutils
|
||||
|
||||
@@ -80,25 +79,12 @@ def get_volume_api_from_url(url):
|
||||
|
||||
class SessionClient(adapter.LegacyJsonAdapter):
|
||||
|
||||
def request(self, url, method, **kwargs):
|
||||
def request(self, *args, **kwargs):
|
||||
kwargs.setdefault('authenticated', False)
|
||||
|
||||
# NOTE(thingee): v1 and v2 require the project id in the url. Prepend
|
||||
# it if we're doing discovery. We figure out if we're doing discovery
|
||||
# if there is no project id already specified in the path. parts is
|
||||
# a list where index 1 is the version discovered and index 2 might be
|
||||
# an empty string or a project id.
|
||||
endpoint = self.get_endpoint()
|
||||
parts = urlparse.urlsplit(endpoint).path.split('/')
|
||||
project_id = self.get_project_id()
|
||||
if (parts[1] in ['v1', 'v2'] and parts[2] == ''
|
||||
and project_id is not None):
|
||||
url = '{0}{1}{2}'.format(endpoint, project_id, url)
|
||||
|
||||
# Note(tpatil): The standard call raises errors from
|
||||
# keystoneclient, here we need to raise the cinderclient errors.
|
||||
raise_exc = kwargs.pop('raise_exc', True)
|
||||
resp, body = super(SessionClient, self).request(url, method,
|
||||
resp, body = super(SessionClient, self).request(*args,
|
||||
raise_exc=False,
|
||||
**kwargs)
|
||||
if raise_exc and resp.status_code >= 400:
|
||||
@@ -124,14 +110,7 @@ class SessionClient(adapter.LegacyJsonAdapter):
|
||||
return self._cs_request(url, 'DELETE', **kwargs)
|
||||
|
||||
def get_volume_api_version_from_endpoint(self):
|
||||
endpoint = self.get_endpoint()
|
||||
if not endpoint:
|
||||
msg = _('The Cinder server does not support %s. Check your '
|
||||
'providers supported versions and try again with '
|
||||
'setting --os-volume-api-version or the environment '
|
||||
'variable OS_VOLUME_API_VERSION.') % self.version
|
||||
raise exceptions.InvalidAPIVersion(msg)
|
||||
return get_volume_api_from_url(endpoint)
|
||||
return get_volume_api_from_url(self.get_endpoint())
|
||||
|
||||
def authenticate(self, auth=None):
|
||||
self.invalidate(auth)
|
||||
@@ -394,6 +373,9 @@ class HTTPClient(object):
|
||||
return self._extract_service_catalog(url, resp, body,
|
||||
extract_token=False)
|
||||
|
||||
def set_management_url(self, url):
|
||||
self.management_url = url
|
||||
|
||||
def authenticate(self):
|
||||
magic_tuple = urlparse.urlsplit(self.auth_url)
|
||||
scheme, netloc, path, query, frag = magic_tuple
|
||||
@@ -569,4 +551,4 @@ def get_client_class(version):
|
||||
|
||||
def Client(version, *args, **kwargs):
|
||||
client_class = get_client_class(version)
|
||||
return client_class(*args, version=version, **kwargs)
|
||||
return client_class(*args, **kwargs)
|
||||
|
||||
@@ -157,14 +157,14 @@ _code_map = dict((c.http_status, c) for c in [BadRequest, Unauthorized,
|
||||
|
||||
def from_response(response, body):
|
||||
"""
|
||||
Return an instance of an ClientException or subclass
|
||||
based on an requests response.
|
||||
Return an instance of a ClientException or subclass
|
||||
based on a requests response.
|
||||
|
||||
Usage::
|
||||
|
||||
resp, body = requests.request(...)
|
||||
if resp.status_code != 200:
|
||||
raise exception_from_response(resp, rest.text)
|
||||
raise exceptions.from_response(resp, resp.text)
|
||||
"""
|
||||
cls = _code_map.get(response.status_code, ClientException)
|
||||
if response.headers:
|
||||
|
||||
@@ -43,12 +43,11 @@ from cinderclient.openstack.common.gettextutils import _
|
||||
from cinderclient.v1 import shell as shell_v1
|
||||
from cinderclient.v2 import shell as shell_v2
|
||||
|
||||
from keystoneclient import adapter
|
||||
from keystoneclient import discover
|
||||
from keystoneclient import session
|
||||
from keystoneclient.auth.identity import v2 as v2_auth
|
||||
from keystoneclient.auth.identity import v3 as v3_auth
|
||||
from keystoneclient import exceptions as keystoneclient_exc
|
||||
from keystoneclient.exceptions import DiscoveryFailure
|
||||
import six.moves.urllib.parse as urlparse
|
||||
|
||||
osprofiler_profiler = importutils.try_import("osprofiler.profiler")
|
||||
@@ -169,6 +168,7 @@ class OpenStackCinderShell(object):
|
||||
default=DEFAULT_CINDER_ENDPOINT_TYPE),
|
||||
help='DEPRECATED! Use --os-endpoint-type.')
|
||||
parser.add_argument('--endpoint_type',
|
||||
dest='os_endpoint_type',
|
||||
help=argparse.SUPPRESS)
|
||||
|
||||
parser.add_argument('--os-endpoint-type',
|
||||
@@ -553,7 +553,6 @@ class OpenStackCinderShell(object):
|
||||
(options, args) = parser.parse_known_args(argv)
|
||||
self.setup_debugging(options.debug)
|
||||
api_version_input = True
|
||||
service_type_input = True
|
||||
self.options = options
|
||||
|
||||
if not options.os_volume_api_version:
|
||||
@@ -563,8 +562,6 @@ class OpenStackCinderShell(object):
|
||||
options.os_volume_api_version = DEFAULT_OS_VOLUME_API_VERSION
|
||||
api_version_input = False
|
||||
|
||||
version = (options.os_volume_api_version,)
|
||||
|
||||
# build available subcommands based on version
|
||||
self.extensions = self._discover_extensions(
|
||||
options.os_volume_api_version)
|
||||
@@ -610,7 +607,6 @@ class OpenStackCinderShell(object):
|
||||
if not service_type:
|
||||
service_type = DEFAULT_CINDER_SERVICE_TYPE
|
||||
service_type = utils.get_service_type(args.func) or service_type
|
||||
service_type_input = False
|
||||
|
||||
# FIXME(usrleon): Here should be restrict for project id same as
|
||||
# for os_username or os_password but for compatibility it is not.
|
||||
@@ -691,64 +687,8 @@ class OpenStackCinderShell(object):
|
||||
if not auth_plugin:
|
||||
auth_session = self._get_keystone_session()
|
||||
|
||||
if auth_session and (not service_type_input or not api_version_input):
|
||||
# NOTE(thingee): Unfortunately the v2 shell is tied to volumev2
|
||||
# service_type. If the service_catalog just contains service_type
|
||||
# volume with x.x.x.x:8776 for discovery, and the user sets version
|
||||
# 2 for the client, it'll default to volumev2 and raise
|
||||
# EndpointNotFound. This is a workaround until we feel comfortable
|
||||
# with removing the volumev2 assumption.
|
||||
keystone_adapter = adapter.Adapter(auth_session)
|
||||
try:
|
||||
# Try just the client's defaults
|
||||
endpoint = keystone_adapter.get_endpoint(
|
||||
service_type=service_type,
|
||||
version=version,
|
||||
interface=endpoint_type)
|
||||
|
||||
# Service was found, but wrong version. Lets try a different
|
||||
# version, if the user did not specify one.
|
||||
if not endpoint and not api_version_input:
|
||||
if version == ('1',):
|
||||
version = ('2',)
|
||||
else:
|
||||
version = ('1',)
|
||||
|
||||
endpoint = keystone_adapter.get_endpoint(
|
||||
service_type=service_type, version=version,
|
||||
interface=endpoint_type)
|
||||
|
||||
except keystoneclient_exc.EndpointNotFound as e:
|
||||
# No endpoint found with that service_type, lets fall back to
|
||||
# other service_types if the user did not specify one.
|
||||
if not service_type_input:
|
||||
if service_type == 'volume':
|
||||
service_type = 'volumev2'
|
||||
else:
|
||||
service_type = 'volume'
|
||||
|
||||
try:
|
||||
endpoint = keystone_adapter.get_endpoint(
|
||||
version=version,
|
||||
service_type=service_type, interface=endpoint_type)
|
||||
|
||||
# Service was found, but wrong version. Lets try
|
||||
# a different version, if the user did not specify one.
|
||||
if not endpoint and not api_version_input:
|
||||
if version == ('1',):
|
||||
version = ('2',)
|
||||
else:
|
||||
version = ('1',)
|
||||
|
||||
endpoint = keystone_adapter.get_endpoint(
|
||||
service_type=service_type, version=version,
|
||||
interface=endpoint_type)
|
||||
|
||||
except keystoneclient_exc.EndpointNotFound:
|
||||
raise e
|
||||
|
||||
self.cs = client.Client(version[0], os_username, os_password,
|
||||
os_tenant_name, os_auth_url,
|
||||
self.cs = client.Client(options.os_volume_api_version, os_username,
|
||||
os_password, os_tenant_name, os_auth_url,
|
||||
region_name=os_region_name,
|
||||
tenant_id=os_tenant_id,
|
||||
endpoint_type=endpoint_type,
|
||||
@@ -756,10 +696,12 @@ class OpenStackCinderShell(object):
|
||||
service_type=service_type,
|
||||
service_name=service_name,
|
||||
volume_service_name=volume_service_name,
|
||||
bypass_url=bypass_url, retries=options.retries,
|
||||
http_log_debug=args.debug, cacert=cacert,
|
||||
auth_system=os_auth_system,
|
||||
auth_plugin=auth_plugin, session=auth_session)
|
||||
bypass_url=bypass_url,
|
||||
retries=options.retries,
|
||||
http_log_debug=args.debug,
|
||||
cacert=cacert, auth_system=os_auth_system,
|
||||
auth_plugin=auth_plugin,
|
||||
session=auth_session)
|
||||
|
||||
try:
|
||||
if not utils.isunauthenticated(args.func):
|
||||
@@ -777,8 +719,7 @@ class OpenStackCinderShell(object):
|
||||
try:
|
||||
endpoint_api_version = \
|
||||
self.cs.get_volume_api_version_from_endpoint()
|
||||
if (endpoint_api_version != options.os_volume_api_version
|
||||
and api_version_input):
|
||||
if endpoint_api_version != options.os_volume_api_version:
|
||||
msg = (("OpenStack Block Storage API version is set to %s "
|
||||
"but you are accessing a %s endpoint. "
|
||||
"Change its value through --os-volume-api-version "
|
||||
@@ -896,7 +837,7 @@ class OpenStackCinderShell(object):
|
||||
ks_discover = discover.Discover(session=session, auth_url=auth_url)
|
||||
v2_auth_url = ks_discover.url_for('2.0')
|
||||
v3_auth_url = ks_discover.url_for('3.0')
|
||||
except keystoneclient_exc.DiscoveryFailure:
|
||||
except DiscoveryFailure:
|
||||
# Discovery response mismatch. Raise the error
|
||||
raise
|
||||
except Exception:
|
||||
|
||||
@@ -29,7 +29,7 @@ function generate_testr_results {
|
||||
fi
|
||||
}
|
||||
|
||||
export CINDERCLIENT_DIR="$BASE/python-cinderclient"
|
||||
export CINDERCLIENT_DIR="$BASE/new/python-cinderclient"
|
||||
|
||||
sudo chown -R jenkins:stack $CINDERCLIENT_DIR
|
||||
|
||||
|
||||
@@ -38,14 +38,14 @@ class CinderClientReadOnlyTests(base.ClientTestBase):
|
||||
def test_backup_list(self):
|
||||
backup_list = self.cinder('backup-list')
|
||||
self.assertTableHeaders(backup_list, ['ID', 'Volume ID', 'Status',
|
||||
'Name', 'Size', 'Object Count',
|
||||
'Container'])
|
||||
'Name', 'Size', 'Object Count',
|
||||
'Container'])
|
||||
|
||||
def test_encryption_type_list(self):
|
||||
encrypt_list = self.cinder('encryption-type-list')
|
||||
self.assertTableHeaders(encrypt_list, ['Volume Type ID', 'Provider',
|
||||
'Cipher', 'Key Size',
|
||||
'Control Location'])
|
||||
'Cipher', 'Key Size',
|
||||
'Control Location'])
|
||||
|
||||
def test_endpoints(self):
|
||||
out = self.cinder('endpoints')
|
||||
@@ -55,11 +55,16 @@ class CinderClientReadOnlyTests(base.ClientTestBase):
|
||||
self.assertTrue(2 >= len(headers))
|
||||
self.assertEqual('Value', headers[1])
|
||||
|
||||
def test_extra_specs_list(self):
|
||||
extra_specs_list = self.cinder('extra-specs-list')
|
||||
self.assertTableHeaders(extra_specs_list, ['ID', 'Name',
|
||||
'extra_specs'])
|
||||
|
||||
def test_list(self):
|
||||
list = self.cinder('list')
|
||||
self.assertTableHeaders(list, ['ID', 'Status', 'Name', 'Size',
|
||||
'Volume Type', 'Bootable',
|
||||
'Attached to'])
|
||||
'Volume Type', 'Bootable',
|
||||
'Attached to'])
|
||||
|
||||
def test_qos_list(self):
|
||||
qos_list = self.cinder('qos-list')
|
||||
@@ -68,17 +73,18 @@ class CinderClientReadOnlyTests(base.ClientTestBase):
|
||||
def test_rate_limits(self):
|
||||
rate_limits = self.cinder('rate-limits')
|
||||
self.assertTableHeaders(rate_limits, ['Verb', 'URI', 'Value', 'Remain',
|
||||
'Unit', 'Next_Available'])
|
||||
'Unit', 'Next_Available'])
|
||||
|
||||
def test_service_list(self):
|
||||
service_list = self.cinder('service-list')
|
||||
self.assertTableHeaders(service_list, ['Binary', 'Host', 'Zone',
|
||||
'Status', 'State', 'Updated_at'])
|
||||
'Status', 'State',
|
||||
'Updated_at'])
|
||||
|
||||
def test_snapshot_list(self):
|
||||
snapshot_list = self.cinder('snapshot-list')
|
||||
self.assertTableHeaders(snapshot_list, ['ID', 'Volume ID', 'Status',
|
||||
'Name', 'Size'])
|
||||
'Name', 'Size'])
|
||||
|
||||
def test_transfer_list(self):
|
||||
transfer_list = self.cinder('transfer-list')
|
||||
@@ -87,3 +93,8 @@ class CinderClientReadOnlyTests(base.ClientTestBase):
|
||||
def test_type_list(self):
|
||||
type_list = self.cinder('type-list')
|
||||
self.assertTableHeaders(type_list, ['ID', 'Name'])
|
||||
|
||||
def test_list_extensions(self):
|
||||
list_extensions = self.cinder('list-extensions')
|
||||
self.assertTableHeaders(list_extensions, ['Name', 'Summary', 'Alias',
|
||||
'Updated'])
|
||||
|
||||
@@ -14,42 +14,6 @@ import fixtures
|
||||
|
||||
IDENTITY_URL = 'http://identityserver:5000/v2.0'
|
||||
VOLUME_URL = 'http://volume.host'
|
||||
TENANT_ID = 'b363706f891f48019483f8bd6503c54b'
|
||||
|
||||
VOLUME_V1_URL = '%(volume_url)s/v1/%(tenant_id)s' % {'volume_url': VOLUME_URL,
|
||||
'tenant_id': TENANT_ID}
|
||||
VOLUME_V2_URL = '%(volume_url)s/v2/%(tenant_id)s' % {'volume_url': VOLUME_URL,
|
||||
'tenant_id': TENANT_ID}
|
||||
|
||||
|
||||
def generate_version_output(v1=True, v2=True):
|
||||
v1_dict = {
|
||||
"status": "SUPPORTED",
|
||||
"updated": "2014-06-28T12:20:21Z",
|
||||
"id": "v1.0",
|
||||
"links": [{
|
||||
"href": "http://127.0.0.1:8776/v1/",
|
||||
"rel": "self"
|
||||
}]
|
||||
}
|
||||
|
||||
v2_dict = {
|
||||
"status": "CURRENT",
|
||||
"updated": "2012-11-21T11:33:21Z",
|
||||
"id": "v2.0", "links": [{
|
||||
"href": "http://127.0.0.1:8776/v2/",
|
||||
"rel": "self"
|
||||
}]
|
||||
}
|
||||
|
||||
versions = []
|
||||
if v1:
|
||||
versions.append(v1_dict)
|
||||
|
||||
if v2:
|
||||
versions.append(v2_dict)
|
||||
|
||||
return {"versions": versions}
|
||||
|
||||
|
||||
class Fixture(fixtures.Fixture):
|
||||
|
||||
@@ -199,8 +199,9 @@ class DeprecatedAuthPluginTest(utils.TestCase):
|
||||
class AuthPluginTest(utils.TestCase):
|
||||
@mock.patch.object(requests, "request")
|
||||
@mock.patch.object(pkg_resources, "iter_entry_points")
|
||||
def test_auth_system_success(self, mock_iter_entry_points, mock_request):
|
||||
"""Test that we can authenticate using the auth system."""
|
||||
def _test_auth_success(self, mock_iter_entry_points, mock_request,
|
||||
**client_kwargs):
|
||||
"""Generic test that we can authenticate using the auth system."""
|
||||
class MockEntrypoint(pkg_resources.EntryPoint):
|
||||
def load(self):
|
||||
return FakePlugin
|
||||
@@ -218,7 +219,7 @@ class AuthPluginTest(utils.TestCase):
|
||||
plugin = auth_plugin.load_plugin("fake")
|
||||
cs = client.Client("username", "password", "project_id",
|
||||
"auth_url/v2.0", auth_system="fake",
|
||||
auth_plugin=plugin)
|
||||
auth_plugin=plugin, **client_kwargs)
|
||||
cs.client.authenticate()
|
||||
|
||||
headers = requested_headers(cs)
|
||||
@@ -232,6 +233,29 @@ class AuthPluginTest(utils.TestCase):
|
||||
allow_redirects=True,
|
||||
**self.TEST_REQUEST_BASE)
|
||||
|
||||
return cs.client
|
||||
|
||||
def test_auth_system_success(self):
|
||||
"""Test that we can authenticate using the auth system."""
|
||||
c = self._test_auth_success()
|
||||
self.assertIsNone(c.bypass_url)
|
||||
self.assertIsNone(c.proxy_token)
|
||||
|
||||
def test_auth_bypass_url(self):
|
||||
"""Test that we can authenticate with bypass URL."""
|
||||
c = self._test_auth_success(bypass_url='auth_url2/v2.0')
|
||||
self.assertEqual('auth_url2/v2.0', c.bypass_url)
|
||||
self.assertEqual('auth_url2/v2.0', c.management_url)
|
||||
self.assertIsNone(c.proxy_token)
|
||||
|
||||
def test_auth_bypass_url_proxy_token(self):
|
||||
"""Test that we can authenticate with bypass URL and proxy token."""
|
||||
c = self._test_auth_success(proxy_token='abc',
|
||||
bypass_url='auth_url2/v2.0')
|
||||
self.assertEqual('auth_url2/v2.0', c.bypass_url)
|
||||
self.assertEqual('auth_url2/v2.0', c.management_url)
|
||||
self.assertEqual('abc', c.proxy_token)
|
||||
|
||||
@mock.patch.object(pkg_resources, "iter_entry_points")
|
||||
def test_discover_auth_system_options(self, mock_iter_entry_points):
|
||||
"""Test that we can load the auth system options."""
|
||||
|
||||
@@ -21,7 +21,6 @@ import cinderclient.client
|
||||
import cinderclient.v1.client
|
||||
import cinderclient.v2.client
|
||||
from cinderclient import exceptions
|
||||
from cinderclient.tests.unit.fixture_data import base as fixture_base
|
||||
from cinderclient.tests.unit import utils
|
||||
from keystoneclient import adapter
|
||||
from keystoneclient import exceptions as keystone_exception
|
||||
@@ -63,12 +62,14 @@ class ClientTest(utils.TestCase):
|
||||
|
||||
output = self.logger.output.split('\n')
|
||||
|
||||
print("JSBRYANT: output is", output)
|
||||
|
||||
self.assertNotIn("fakePassword", output[1])
|
||||
self.assertIn("fakeUser", output[1])
|
||||
|
||||
def test_versions(self):
|
||||
v1_url = fixture_base.VOLUME_V1_URL
|
||||
v2_url = fixture_base.VOLUME_V2_URL
|
||||
v1_url = 'http://fakeurl/v1/tenants'
|
||||
v2_url = 'http://fakeurl/v2/tenants'
|
||||
unknown_url = 'http://fakeurl/v9/tenants'
|
||||
|
||||
self.assertEqual('1',
|
||||
@@ -112,11 +113,8 @@ class ClientTest(utils.TestCase):
|
||||
|
||||
# 'request' method of Adaptor will return 202 response
|
||||
mock_request.return_value = mock_response
|
||||
mock_session = mock.Mock()
|
||||
mock_session.get_endpoint.return_value = fixture_base.VOLUME_V1_URL
|
||||
session_client = cinderclient.client.SessionClient(
|
||||
session=mock_session)
|
||||
response, body = session_client.request(fixture_base.VOLUME_V1_URL,
|
||||
session_client = cinderclient.client.SessionClient(session=mock.Mock())
|
||||
response, body = session_client.request(mock.sentinel.url,
|
||||
'POST', **kwargs)
|
||||
|
||||
# In this case, from_response method will not get called
|
||||
@@ -153,15 +151,13 @@ class ClientTest(utils.TestCase):
|
||||
|
||||
# 'request' method of Adaptor will return 400 response
|
||||
mock_request.return_value = mock_response
|
||||
mock_session = mock.Mock()
|
||||
mock_session.get_endpoint.return_value = fixture_base.VOLUME_V1_URL
|
||||
session_client = cinderclient.client.SessionClient(
|
||||
session=mock_session)
|
||||
session=mock.Mock())
|
||||
|
||||
# 'from_response' method will raise BadRequest because
|
||||
# resp.status_code is 400
|
||||
self.assertRaises(exceptions.BadRequest, session_client.request,
|
||||
fixture_base.VOLUME_V1_URL, 'POST', **kwargs)
|
||||
mock.sentinel.url, 'POST', **kwargs)
|
||||
|
||||
@mock.patch.object(exceptions, 'from_response')
|
||||
def test_keystone_request_raises_auth_failure_exception(
|
||||
@@ -181,13 +177,11 @@ class ClientTest(utils.TestCase):
|
||||
with mock.patch.object(adapter.Adapter, 'request',
|
||||
side_effect=
|
||||
keystone_exception.AuthorizationFailure()):
|
||||
mock_session = mock.Mock()
|
||||
mock_session.get_endpoint.return_value = fixture_base.VOLUME_V1_URL
|
||||
session_client = cinderclient.client.SessionClient(
|
||||
session=mock_session)
|
||||
session=mock.Mock())
|
||||
self.assertRaises(keystone_exception.AuthorizationFailure,
|
||||
session_client.request,
|
||||
fixture_base.VOLUME_V1_URL, 'POST', **kwargs)
|
||||
mock.sentinel.url, 'POST', **kwargs)
|
||||
|
||||
# As keystonesession.request method will raise
|
||||
# AuthorizationFailure exception, check exceptions.from_response
|
||||
|
||||
@@ -16,7 +16,6 @@ import re
|
||||
import sys
|
||||
|
||||
import fixtures
|
||||
from keystoneclient import fixture as keystone_client_fixture
|
||||
import mock
|
||||
import pkg_resources
|
||||
import requests_mock
|
||||
@@ -29,7 +28,6 @@ from cinderclient import auth_plugin
|
||||
from cinderclient import shell
|
||||
from cinderclient.tests.unit.test_auth_plugins import mock_http_request
|
||||
from cinderclient.tests.unit.test_auth_plugins import requested_headers
|
||||
from cinderclient.tests.unit.fixture_data import base as fixture_base
|
||||
from cinderclient.tests.unit.fixture_data import keystone_client
|
||||
from cinderclient.tests.unit import utils
|
||||
import keystoneclient.exceptions as ks_exc
|
||||
@@ -42,7 +40,7 @@ class ShellTest(utils.TestCase):
|
||||
'OS_USERNAME': 'username',
|
||||
'OS_PASSWORD': 'password',
|
||||
'OS_TENANT_NAME': 'tenant_name',
|
||||
'OS_AUTH_URL': '%s/v2.0' % keystone_client.BASE_HOST,
|
||||
'OS_AUTH_URL': 'http://no.where/v2.0',
|
||||
}
|
||||
|
||||
# Patch os.environ to avoid required auth info.
|
||||
@@ -124,203 +122,6 @@ class ShellTest(utils.TestCase):
|
||||
self.assertEqual(v3_url, os_auth_url, "Expected v3 url")
|
||||
self.assertEqual(v2_url, None, "Expected no v2 url")
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_cinder_version_legacy_endpoint_v1_and_v2(self, mocker):
|
||||
"""Verify that legacy endpoint settings still work.
|
||||
|
||||
Legacy endpoints that are not using version discovery is
|
||||
<hostname>:<port>/<version>/(tenant_id)s. For this unit test, we fill
|
||||
in the tenant_id for mocking purposes.
|
||||
"""
|
||||
token = keystone_client_fixture.V2Token()
|
||||
cinder_url = 'http://127.0.0.1:8776/'
|
||||
|
||||
volume_service = token.add_service('volume', 'Cinder v1')
|
||||
volume_service.add_endpoint(public=cinder_url, region='RegionOne')
|
||||
|
||||
volumev2_service = token.add_service('volumev2', 'Cinder v2')
|
||||
volumev2_service.add_endpoint(public=cinder_url, region='RegionOne')
|
||||
|
||||
mocker.post(keystone_client.BASE_HOST + '/v2.0/tokens',
|
||||
json=token)
|
||||
mocker.get(cinder_url, json=fixture_base.generate_version_output())
|
||||
volume_request = mocker.get('http://127.0.0.1:8776/v1/volumes/detail',
|
||||
json={'volumes': {}})
|
||||
|
||||
self.shell('list')
|
||||
self.assertTrue(volume_request.called)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_cinder_version_legacy_endpoint_only_v1(self, mocker):
|
||||
"""Verify that v1 legacy endpoint settings still work.
|
||||
|
||||
Legacy endpoints that are not using version discovery is
|
||||
<hostname>:<port>/<version>/(tenant_id)s. For this unit test, we fill
|
||||
in the tenant_id for mocking purposes.
|
||||
"""
|
||||
token = keystone_client_fixture.V2Token()
|
||||
cinder_url = 'http://127.0.0.1:8776/'
|
||||
|
||||
volume_service = token.add_service('volume', 'Cinder v1')
|
||||
volume_service.add_endpoint(
|
||||
public=cinder_url,
|
||||
region='RegionOne'
|
||||
)
|
||||
|
||||
mocker.get(
|
||||
cinder_url,
|
||||
json=fixture_base.generate_version_output(v1=True, v2=False)
|
||||
)
|
||||
mocker.post(keystone_client.BASE_HOST + '/v2.0/tokens',
|
||||
json=token)
|
||||
volume_request = mocker.get('http://127.0.0.1:8776/v1/volumes/detail',
|
||||
json={'volumes': {}})
|
||||
|
||||
self.shell('list')
|
||||
self.assertTrue(volume_request.called)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_cinder_version_legacy_endpoint_only_v2(self, mocker):
|
||||
"""Verify that v2 legacy endpoint settings still work.
|
||||
|
||||
Legacy endpoints that are not using version discovery is
|
||||
<hostname>:<port>/<version>/(tenant_id)s. For this unit test, we fill
|
||||
in the tenant_id for mocking purposes.
|
||||
"""
|
||||
token = keystone_client_fixture.V2Token()
|
||||
cinder_url = 'http://127.0.0.1:8776/'
|
||||
|
||||
volumev2_service = token.add_service('volumev2', 'Cinder v2')
|
||||
volumev2_service.add_endpoint(
|
||||
public=cinder_url,
|
||||
region='RegionOne'
|
||||
)
|
||||
|
||||
mocker.post(keystone_client.BASE_HOST + '/v2.0/tokens',
|
||||
json=token)
|
||||
|
||||
mocker.get(
|
||||
cinder_url,
|
||||
json=fixture_base.generate_version_output(v1=False, v2=True)
|
||||
)
|
||||
volume_request = mocker.get('http://127.0.0.1:8776/v2/volumes/detail',
|
||||
json={'volumes': {}})
|
||||
|
||||
self.shell('list')
|
||||
self.assertTrue(volume_request.called)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_cinder_version_discovery(self, mocker):
|
||||
"""Verify client works two endpoints enabled under one service."""
|
||||
token = keystone_client_fixture.V2Token()
|
||||
|
||||
volume_service = token.add_service('volume', 'Cinder')
|
||||
volume_service.add_endpoint(public='http://127.0.0.1:8776',
|
||||
region='RegionOne')
|
||||
|
||||
mocker.post(keystone_client.BASE_HOST + '/v2.0/tokens',
|
||||
json=token)
|
||||
mocker.get(
|
||||
'http://127.0.0.1:8776/',
|
||||
json=fixture_base.generate_version_output(v1=True, v2=True)
|
||||
)
|
||||
|
||||
v1_request = mocker.get('http://127.0.0.1:8776/v1/volumes/detail',
|
||||
json={'volumes': {}})
|
||||
v2_request = mocker.get('http://127.0.0.1:8776/v2/volumes/detail',
|
||||
json={'volumes': {}})
|
||||
|
||||
self.shell('list')
|
||||
self.assertTrue(v1_request.called)
|
||||
|
||||
self.shell('--os-volume-api-version 2 list')
|
||||
self.assertTrue(v2_request.called)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_cinder_version_discovery_only_v1(self, mocker):
|
||||
"""Verify when v1 is only enabled, the client discovers it."""
|
||||
token = keystone_client_fixture.V2Token()
|
||||
|
||||
volume_service = token.add_service('volume', 'Cinder')
|
||||
volume_service.add_endpoint(public='http://127.0.0.1:8776',
|
||||
region='RegionOne')
|
||||
|
||||
mocker.post(keystone_client.BASE_HOST + '/v2.0/tokens',
|
||||
json=token)
|
||||
mocker.get(
|
||||
'http://127.0.0.1:8776/',
|
||||
json=fixture_base.generate_version_output(v1=True, v2=True)
|
||||
)
|
||||
volume_request = mocker.get('http://127.0.0.1:8776/v1/volumes/detail',
|
||||
json={'volumes': {}})
|
||||
|
||||
self.shell('list')
|
||||
self.assertTrue(volume_request.called)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_cinder_version_discovery_only_v2(self, mocker):
|
||||
"""Verify when v2 is enabled, the client discovers it."""
|
||||
token = keystone_client_fixture.V2Token()
|
||||
|
||||
volumev2_service = token.add_service('volume', 'Cinder')
|
||||
volumev2_service.add_endpoint(public='http://127.0.0.1:8776',
|
||||
region='RegionOne')
|
||||
|
||||
mocker.post(keystone_client.BASE_HOST + '/v2.0/tokens',
|
||||
json=token)
|
||||
|
||||
mocker.get(
|
||||
'http://127.0.0.1:8776/',
|
||||
json=fixture_base.generate_version_output(v1=False, v2=True)
|
||||
)
|
||||
volume_request = mocker.get('http://127.0.0.1:8776/v2/volumes/detail',
|
||||
json={'volumes': {}})
|
||||
|
||||
self.shell('list')
|
||||
self.assertTrue(volume_request.called)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_cinder_version_discovery_fallback(self, mocker):
|
||||
"""Client defaults to v1, but v2 is only available, fallback to v2."""
|
||||
token = keystone_client_fixture.V2Token()
|
||||
|
||||
volumev2_service = token.add_service('volumev2', 'Cinder v2')
|
||||
volumev2_service.add_endpoint(public='http://127.0.0.1:8776',
|
||||
region='RegionOne')
|
||||
|
||||
mocker.post(keystone_client.BASE_HOST + '/v2.0/tokens',
|
||||
json=token)
|
||||
|
||||
mocker.get(
|
||||
'http://127.0.0.1:8776/',
|
||||
json=fixture_base.generate_version_output(v1=False, v2=True)
|
||||
)
|
||||
volume_request = mocker.get('http://127.0.0.1:8776/v2/volumes/detail',
|
||||
json={'volumes': {}})
|
||||
|
||||
self.shell('list')
|
||||
self.assertTrue(volume_request.called)
|
||||
|
||||
@requests_mock.Mocker()
|
||||
def test_cinder_version_discovery_unsupported_version(self, mocker):
|
||||
"""Try a version from the client that's not enabled in Cinder."""
|
||||
token = keystone_client_fixture.V2Token()
|
||||
|
||||
volume_service = token.add_service('volume', 'Cinder')
|
||||
volume_service.add_endpoint(public='http://127.0.0.1:8776',
|
||||
region='RegionOne')
|
||||
|
||||
mocker.post(keystone_client.BASE_HOST + '/v2.0/tokens',
|
||||
json=token)
|
||||
|
||||
mocker.get(
|
||||
'http://127.0.0.1:8776/',
|
||||
json=fixture_base.generate_version_output(v1=False, v2=True)
|
||||
)
|
||||
|
||||
self.assertRaises(exceptions.InvalidAPIVersion,
|
||||
self.shell, '--os-volume-api-version 1 list')
|
||||
|
||||
@mock.patch('sys.stdin', side_effect=mock.MagicMock)
|
||||
@mock.patch('getpass.getpass', return_value='password')
|
||||
def test_password_prompted(self, mock_getpass, mock_stdin):
|
||||
|
||||
@@ -330,9 +330,9 @@ class FakeHTTPClient(base_client.HTTPClient):
|
||||
assert len(list(body)) == 1
|
||||
action = list(body)[0]
|
||||
if action == 'os-attach':
|
||||
assert sorted(list(body[action])) == ['instance_uuid',
|
||||
'mode',
|
||||
'mountpoint']
|
||||
keys = sorted(list(body[action]))
|
||||
assert (keys == ['instance_uuid', 'mode', 'mountpoint'] or
|
||||
keys == ['host_name', 'mode', 'mountpoint'])
|
||||
elif action == 'os-detach':
|
||||
assert body[action] is None
|
||||
elif action == 'os-reserve':
|
||||
|
||||
@@ -16,17 +16,15 @@
|
||||
# under the License.
|
||||
|
||||
import fixtures
|
||||
from keystoneclient import fixture as keystone_client_fixture
|
||||
from requests_mock.contrib import fixture as requests_mock_fixture
|
||||
|
||||
from cinderclient import client
|
||||
from cinderclient import exceptions
|
||||
from cinderclient import shell
|
||||
from cinderclient.tests.unit.fixture_data import base as fixture_base
|
||||
from cinderclient.tests.unit.fixture_data import keystone_client
|
||||
from cinderclient.tests.unit import utils
|
||||
from cinderclient.tests.unit.v1 import fakes
|
||||
from cinderclient.v1 import shell as shell_v1
|
||||
from cinderclient.tests.unit.v1 import fakes
|
||||
from cinderclient.tests.unit import utils
|
||||
from cinderclient.tests.unit.fixture_data import keystone_client
|
||||
|
||||
|
||||
class ShellTest(utils.TestCase):
|
||||
@@ -56,18 +54,7 @@ class ShellTest(utils.TestCase):
|
||||
self.requests = self.useFixture(requests_mock_fixture.Fixture())
|
||||
self.requests.register_uri(
|
||||
'GET', keystone_client.BASE_URL,
|
||||
text=keystone_client.keystone_request_callback
|
||||
)
|
||||
token = keystone_client_fixture.V2Token()
|
||||
s = token.add_service('volume', 'cinder')
|
||||
s.add_endpoint(public='http://127.0.0.1:8776')
|
||||
|
||||
self.requests.post(keystone_client.BASE_URL + 'v2.0/tokens',
|
||||
json=token)
|
||||
self.requests.get(
|
||||
'http://127.0.0.1:8776',
|
||||
json=fixture_base.generate_version_output()
|
||||
)
|
||||
text=keystone_client.keystone_request_callback)
|
||||
|
||||
def tearDown(self):
|
||||
# For some method like test_image_meta_bad_action we are
|
||||
|
||||
@@ -38,6 +38,11 @@ class VolumesTest(utils.TestCase):
|
||||
cs.volumes.attach(v, 1, '/dev/vdc', mode='rw')
|
||||
cs.assert_called('POST', '/volumes/1234/action')
|
||||
|
||||
def test_attach_to_host(self):
|
||||
v = cs.volumes.get('1234')
|
||||
cs.volumes.attach(v, None, None, host_name='test', mode='rw')
|
||||
cs.assert_called('POST', '/volumes/1234/action')
|
||||
|
||||
def test_detach(self):
|
||||
v = cs.volumes.get('1234')
|
||||
cs.volumes.detach(v)
|
||||
|
||||
@@ -40,6 +40,7 @@ def _stub_volume(**kwargs):
|
||||
"snapshot_id": None,
|
||||
"status": "available",
|
||||
"volume_type": "None",
|
||||
"multiattach": "false",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://localhost/v2/fake/volumes/1234",
|
||||
@@ -411,11 +412,11 @@ class FakeHTTPClient(base_client.HTTPClient):
|
||||
assert len(list(body)) == 1
|
||||
action = list(body)[0]
|
||||
if action == 'os-attach':
|
||||
assert sorted(list(body[action])) == ['instance_uuid',
|
||||
'mode',
|
||||
'mountpoint']
|
||||
keys = sorted(list(body[action]))
|
||||
assert (keys == ['instance_uuid', 'mode', 'mountpoint'] or
|
||||
keys == ['host_name', 'mode', 'mountpoint'])
|
||||
elif action == 'os-detach':
|
||||
assert body[action] is None
|
||||
assert list(body[action]) == ['attachment_id']
|
||||
elif action == 'os-reserve':
|
||||
assert body[action] is None
|
||||
elif action == 'os-unreserve':
|
||||
@@ -538,7 +539,8 @@ class FakeHTTPClient(base_client.HTTPClient):
|
||||
'gigabytes': 1,
|
||||
'backups': 1,
|
||||
'backup_gigabytes': 1,
|
||||
'consistencygroups': 1}})
|
||||
'consistencygroups': 1,
|
||||
'per_volume_gigabytes': 1, }})
|
||||
|
||||
def get_os_quota_sets_test_defaults(self):
|
||||
return (200, {}, {'quota_set': {
|
||||
@@ -549,7 +551,8 @@ class FakeHTTPClient(base_client.HTTPClient):
|
||||
'gigabytes': 1,
|
||||
'backups': 1,
|
||||
'backup_gigabytes': 1,
|
||||
'consistencygroups': 1}})
|
||||
'consistencygroups': 1,
|
||||
'per_volume_gigabytes': 1, }})
|
||||
|
||||
def put_os_quota_sets_test(self, body, **kw):
|
||||
assert list(body) == ['quota_set']
|
||||
@@ -563,7 +566,8 @@ class FakeHTTPClient(base_client.HTTPClient):
|
||||
'gigabytes': 1,
|
||||
'backups': 1,
|
||||
'backup_gigabytes': 1,
|
||||
'consistencygroups': 2}})
|
||||
'consistencygroups': 2,
|
||||
'per_volume_gigabytes': 1, }})
|
||||
|
||||
def delete_os_quota_sets_1234(self, **kw):
|
||||
return (200, {}, {})
|
||||
@@ -584,7 +588,8 @@ class FakeHTTPClient(base_client.HTTPClient):
|
||||
'gigabytes': 1,
|
||||
'backups': 1,
|
||||
'backup_gigabytes': 1,
|
||||
'consistencygroups': 1}})
|
||||
'consistencygroups': 1,
|
||||
'per_volume_gigabytes': 1, }})
|
||||
|
||||
def put_os_quota_class_sets_test(self, body, **kw):
|
||||
assert list(body) == ['quota_class_set']
|
||||
@@ -598,7 +603,8 @@ class FakeHTTPClient(base_client.HTTPClient):
|
||||
'gigabytes': 1,
|
||||
'backups': 1,
|
||||
'backup_gigabytes': 1,
|
||||
'consistencygroups': 2}})
|
||||
'consistencygroups': 2,
|
||||
'per_volume_gigabytes': 1}})
|
||||
|
||||
#
|
||||
# VolumeTypes
|
||||
@@ -692,8 +698,12 @@ class FakeHTTPClient(base_client.HTTPClient):
|
||||
def post_types_2_encryption(self, body, **kw):
|
||||
return (200, {}, {'encryption': body})
|
||||
|
||||
def put_types_1_encryption_1(self, body, **kw):
|
||||
return (200, {}, {})
|
||||
def put_types_1_encryption_provider(self, body, **kw):
|
||||
get_body = self.get_types_1_encryption()[2]
|
||||
for k, v in body.items():
|
||||
if k in get_body.keys():
|
||||
get_body.update([(k, v)])
|
||||
return (200, {}, get_body)
|
||||
|
||||
def delete_types_1_encryption_provider(self, **kw):
|
||||
return (202, {}, None)
|
||||
|
||||
@@ -31,7 +31,7 @@ class QuotaClassSetsTest(utils.TestCase):
|
||||
q = cs.quota_classes.get('test')
|
||||
q.update(volumes=2, snapshots=2, gigabytes=2000,
|
||||
backups=2, backup_gigabytes=2000,
|
||||
consistencygroups=2)
|
||||
consistencygroups=2, per_volume_gigabytes=100)
|
||||
cs.assert_called('PUT', '/os-quota-class-sets/test')
|
||||
|
||||
def test_refresh_quota(self):
|
||||
@@ -43,6 +43,7 @@ class QuotaClassSetsTest(utils.TestCase):
|
||||
self.assertEqual(q.backups, q2.backups)
|
||||
self.assertEqual(q.backup_gigabytes, q2.backup_gigabytes)
|
||||
self.assertEqual(q.consistencygroups, q2.consistencygroups)
|
||||
self.assertEqual(q.per_volume_gigabytes, q2.per_volume_gigabytes)
|
||||
q2.volumes = 0
|
||||
self.assertNotEqual(q.volumes, q2.volumes)
|
||||
q2.snapshots = 0
|
||||
@@ -55,6 +56,8 @@ class QuotaClassSetsTest(utils.TestCase):
|
||||
self.assertNotEqual(q.backup_gigabytes, q2.backup_gigabytes)
|
||||
q2.consistencygroups = 0
|
||||
self.assertNotEqual(q.consistencygroups, q2.consistencygroups)
|
||||
q2.per_volume_gigabytes = 0
|
||||
self.assertNotEqual(q.per_volume_gigabytes, q2.per_volume_gigabytes)
|
||||
q2.get()
|
||||
self.assertEqual(q.volumes, q2.volumes)
|
||||
self.assertEqual(q.snapshots, q2.snapshots)
|
||||
@@ -62,3 +65,4 @@ class QuotaClassSetsTest(utils.TestCase):
|
||||
self.assertEqual(q.backups, q2.backups)
|
||||
self.assertEqual(q.backup_gigabytes, q2.backup_gigabytes)
|
||||
self.assertEqual(q.consistencygroups, q2.consistencygroups)
|
||||
self.assertEqual(q.per_volume_gigabytes, q2.per_volume_gigabytes)
|
||||
|
||||
@@ -40,6 +40,7 @@ class QuotaSetsTest(utils.TestCase):
|
||||
q.update(backups=2)
|
||||
q.update(backup_gigabytes=2000)
|
||||
q.update(consistencygroups=2)
|
||||
q.update(per_volume_gigabytes=100)
|
||||
cs.assert_called('PUT', '/os-quota-sets/test')
|
||||
|
||||
def test_refresh_quota(self):
|
||||
@@ -51,6 +52,7 @@ class QuotaSetsTest(utils.TestCase):
|
||||
self.assertEqual(q.backups, q2.backups)
|
||||
self.assertEqual(q.backup_gigabytes, q2.backup_gigabytes)
|
||||
self.assertEqual(q.consistencygroups, q2.consistencygroups)
|
||||
self.assertEqual(q.per_volume_gigabytes, q2.per_volume_gigabytes)
|
||||
q2.volumes = 0
|
||||
self.assertNotEqual(q.volumes, q2.volumes)
|
||||
q2.snapshots = 0
|
||||
@@ -63,6 +65,8 @@ class QuotaSetsTest(utils.TestCase):
|
||||
self.assertNotEqual(q.backup_gigabytes, q2.backup_gigabytes)
|
||||
q2.consistencygroups = 0
|
||||
self.assertNotEqual(q.consistencygroups, q2.consistencygroups)
|
||||
q2.per_volume_gigabytes = 0
|
||||
self.assertNotEqual(q.per_volume_gigabytes, q2.per_volume_gigabytes)
|
||||
q2.get()
|
||||
self.assertEqual(q.volumes, q2.volumes)
|
||||
self.assertEqual(q.snapshots, q2.snapshots)
|
||||
@@ -70,6 +74,7 @@ class QuotaSetsTest(utils.TestCase):
|
||||
self.assertEqual(q.backups, q2.backups)
|
||||
self.assertEqual(q.backup_gigabytes, q2.backup_gigabytes)
|
||||
self.assertEqual(q.consistencygroups, q2.consistencygroups)
|
||||
self.assertEqual(q.per_volume_gigabytes, q2.per_volume_gigabytes)
|
||||
|
||||
def test_delete_quota(self):
|
||||
tenant_id = 'test'
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
# under the License.
|
||||
|
||||
import fixtures
|
||||
from keystoneclient import fixture as keystone_client_fixture
|
||||
import mock
|
||||
from requests_mock.contrib import fixture as requests_mock_fixture
|
||||
from six.moves.urllib import parse
|
||||
@@ -22,10 +21,9 @@ from six.moves.urllib import parse
|
||||
from cinderclient import client
|
||||
from cinderclient import exceptions
|
||||
from cinderclient import shell
|
||||
from cinderclient.tests.unit.fixture_data import base as fixture_base
|
||||
from cinderclient.tests.unit.fixture_data import keystone_client
|
||||
from cinderclient.tests.unit import utils
|
||||
from cinderclient.tests.unit.v2 import fakes
|
||||
from cinderclient.tests.unit.fixture_data import keystone_client
|
||||
|
||||
|
||||
class ShellTest(utils.TestCase):
|
||||
@@ -55,18 +53,7 @@ class ShellTest(utils.TestCase):
|
||||
self.requests = self.useFixture(requests_mock_fixture.Fixture())
|
||||
self.requests.register_uri(
|
||||
'GET', keystone_client.BASE_URL,
|
||||
text=keystone_client.keystone_request_callback
|
||||
)
|
||||
token = keystone_client_fixture.V2Token()
|
||||
s = token.add_service('volume', 'cinder')
|
||||
s.add_endpoint(public='http://127.0.0.1:8776')
|
||||
|
||||
self.requests.post(keystone_client.BASE_URL + 'v2.0/tokens',
|
||||
json=token)
|
||||
self.requests.get(
|
||||
'http://127.0.0.1:8776',
|
||||
json=fixture_base.generate_version_output()
|
||||
)
|
||||
text=keystone_client.keystone_request_callback)
|
||||
|
||||
def tearDown(self):
|
||||
# For some methods like test_image_meta_bad_action we are
|
||||
@@ -125,7 +112,8 @@ class ShellTest(utils.TestCase):
|
||||
'snapshot_id': None,
|
||||
'metadata': {'key1': '"--test1"'},
|
||||
'volume_type': None,
|
||||
'description': None}}
|
||||
'description': None,
|
||||
'multiattach': False}}
|
||||
self.assert_called_anytime('POST', '/volumes', expected)
|
||||
|
||||
def test_metadata_args_limiter_display_name(self):
|
||||
@@ -145,7 +133,8 @@ class ShellTest(utils.TestCase):
|
||||
'snapshot_id': None,
|
||||
'metadata': {'key1': '"--t1"'},
|
||||
'volume_type': None,
|
||||
'description': None}}
|
||||
'description': None,
|
||||
'multiattach': False}}
|
||||
self.assert_called_anytime('POST', '/volumes', expected)
|
||||
|
||||
def test_delimit_metadata_args(self):
|
||||
@@ -165,7 +154,8 @@ class ShellTest(utils.TestCase):
|
||||
'metadata': {'key1': '"test1"',
|
||||
'key2': '"test2"'},
|
||||
'volume_type': None,
|
||||
'description': None}}
|
||||
'description': None,
|
||||
'multiattach': False}}
|
||||
self.assert_called_anytime('POST', '/volumes', expected)
|
||||
|
||||
def test_delimit_metadata_args_display_name(self):
|
||||
@@ -185,7 +175,8 @@ class ShellTest(utils.TestCase):
|
||||
'snapshot_id': None,
|
||||
'metadata': {'key1': '"t1"'},
|
||||
'volume_type': None,
|
||||
'description': None}}
|
||||
'description': None,
|
||||
'multiattach': False}}
|
||||
self.assert_called_anytime('POST', '/volumes', expected)
|
||||
|
||||
def test_list_filter_status(self):
|
||||
@@ -334,6 +325,13 @@ class ShellTest(utils.TestCase):
|
||||
def test_create_size_required_if_not_snapshot_or_clone(self):
|
||||
self.assertRaises(SystemExit, self.run_command, 'create')
|
||||
|
||||
def test_create_size_zero_if_not_snapshot_or_clone(self):
|
||||
expected = {'volume': {'status': 'creating',
|
||||
'size': 0}}
|
||||
self.run_command('create 0')
|
||||
self.assert_called_anytime('POST', '/volumes', partial_body=expected)
|
||||
self.assert_called('GET', '/volumes/1234')
|
||||
|
||||
def test_show(self):
|
||||
self.run_command('show 1234')
|
||||
self.assert_called('GET', '/volumes/1234')
|
||||
@@ -496,10 +494,6 @@ class ShellTest(utils.TestCase):
|
||||
|
||||
def test_type_list(self):
|
||||
self.run_command('type-list')
|
||||
self.assert_called_anytime('GET', '/types')
|
||||
|
||||
def test_type_list_all(self):
|
||||
self.run_command('type-list --all')
|
||||
self.assert_called_anytime('GET', '/types?is_public=None')
|
||||
|
||||
def test_type_create(self):
|
||||
@@ -535,6 +529,16 @@ class ShellTest(utils.TestCase):
|
||||
self.assert_called('POST', '/types/3/action',
|
||||
body=expected)
|
||||
|
||||
def test_type_access_add_project_by_name(self):
|
||||
expected = {'addProjectAccess': {'project': '101'}}
|
||||
with mock.patch('cinderclient.utils.find_resource') as mock_find:
|
||||
mock_find.return_value = '3'
|
||||
self.run_command('type-access-add --volume-type type_name \
|
||||
--project-id 101')
|
||||
mock_find.assert_called_once_with(mock.ANY, 'type_name')
|
||||
self.assert_called('POST', '/types/3/action',
|
||||
body=expected)
|
||||
|
||||
def test_type_access_remove_project(self):
|
||||
expected = {'removeProjectAccess': {'project': '101'}}
|
||||
self.run_command('type-access-remove '
|
||||
@@ -552,7 +556,7 @@ class ShellTest(utils.TestCase):
|
||||
- one per volume type to retrieve the encryption type information
|
||||
"""
|
||||
self.run_command('encryption-type-list')
|
||||
self.assert_called_anytime('GET', '/types')
|
||||
self.assert_called_anytime('GET', '/types?is_public=None')
|
||||
self.assert_called_anytime('GET', '/types/1/encryption')
|
||||
self.assert_called_anytime('GET', '/types/2/encryption')
|
||||
|
||||
@@ -592,8 +596,67 @@ class ShellTest(utils.TestCase):
|
||||
- one GET request to retrieve the relevant volume type information
|
||||
- one GET request to retrieve the relevant encryption type information
|
||||
- one PUT request to update the encryption type information
|
||||
Verify that the PUT request correctly parses encryption-type-update
|
||||
parameters from sys.argv
|
||||
"""
|
||||
self.skipTest("Not implemented")
|
||||
parameters = {'--provider': 'EncryptionProvider', '--cipher': 'des',
|
||||
'--key-size': 1024, '--control-location': 'back-end'}
|
||||
|
||||
# Construct the argument string for the update call and the
|
||||
# expected encryption-type body that should be produced by it
|
||||
args = ' '.join(['%s %s' % (k, v) for k, v in parameters.items()])
|
||||
expected = {'encryption': {'provider': 'EncryptionProvider',
|
||||
'cipher': 'des',
|
||||
'key_size': 1024,
|
||||
'control_location': 'back-end'}}
|
||||
|
||||
self.run_command('encryption-type-update 1 %s' % args)
|
||||
self.assert_called('GET', '/types/1/encryption')
|
||||
self.assert_called_anytime('GET', '/types/1')
|
||||
self.assert_called_anytime('PUT', '/types/1/encryption/provider',
|
||||
body=expected)
|
||||
|
||||
def test_encryption_type_update_no_attributes(self):
|
||||
"""
|
||||
Test encryption-type-update shell command.
|
||||
|
||||
Verify two GETs/one PUT requests are made per command invocation:
|
||||
- one GET request to retrieve the relevant volume type information
|
||||
- one GET request to retrieve the relevant encryption type information
|
||||
- one PUT request to update the encryption type information
|
||||
"""
|
||||
expected = {'encryption': {}}
|
||||
self.run_command('encryption-type-update 1')
|
||||
self.assert_called('GET', '/types/1/encryption')
|
||||
self.assert_called_anytime('GET', '/types/1')
|
||||
self.assert_called_anytime('PUT', '/types/1/encryption/provider',
|
||||
body=expected)
|
||||
|
||||
def test_encryption_type_update_default_attributes(self):
|
||||
"""
|
||||
Test encryption-type-update shell command.
|
||||
|
||||
Verify two GETs/one PUT requests are made per command invocation:
|
||||
- one GET request to retrieve the relevant volume type information
|
||||
- one GET request to retrieve the relevant encryption type information
|
||||
- one PUT request to update the encryption type information
|
||||
Verify that the encryption-type body produced contains default None
|
||||
values for all specified parameters.
|
||||
"""
|
||||
parameters = ['--cipher', '--key-size']
|
||||
|
||||
# Construct the argument string for the update call and the
|
||||
# expected encryption-type body that should be produced by it
|
||||
args = ' '.join(['%s' % (p) for p in parameters])
|
||||
expected_pairs = [(k.strip('-').replace('-', '_'), None) for k in
|
||||
parameters]
|
||||
expected = {'encryption': dict(expected_pairs)}
|
||||
|
||||
self.run_command('encryption-type-update 1 %s' % args)
|
||||
self.assert_called('GET', '/types/1/encryption')
|
||||
self.assert_called_anytime('GET', '/types/1')
|
||||
self.assert_called_anytime('PUT', '/types/1/encryption/provider',
|
||||
body=expected)
|
||||
|
||||
def test_encryption_type_delete(self):
|
||||
"""
|
||||
|
||||
@@ -25,7 +25,7 @@ class TypesTest(utils.TestCase):
|
||||
|
||||
def test_list_types(self):
|
||||
tl = cs.volume_types.list()
|
||||
cs.assert_called('GET', '/types')
|
||||
cs.assert_called('GET', '/types?is_public=None')
|
||||
for t in tl:
|
||||
self.assertIsInstance(t, volume_types.VolumeType)
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ class VolumeEncryptionTypesTest(utils.TestCase):
|
||||
Verify that all returned information is :class: VolumeEncryptionType
|
||||
"""
|
||||
encryption_types = cs.volume_encryption_types.list()
|
||||
cs.assert_called_anytime('GET', '/types')
|
||||
cs.assert_called_anytime('GET', '/types?is_public=None')
|
||||
cs.assert_called_anytime('GET', '/types/2/encryption')
|
||||
cs.assert_called_anytime('GET', '/types/1/encryption')
|
||||
for encryption_type in encryption_types:
|
||||
@@ -84,8 +84,18 @@ class VolumeEncryptionTypesTest(utils.TestCase):
|
||||
def test_update(self):
|
||||
"""
|
||||
Unit test for VolumeEncryptionTypesManager.update
|
||||
|
||||
Verify that one PUT request is made for encryption type update
|
||||
Verify that an empty encryption-type update returns the original
|
||||
encryption-type information.
|
||||
"""
|
||||
self.skipTest("Not implemented")
|
||||
expected = {'id': 1, 'volume_type_id': 1, 'provider': 'test',
|
||||
'cipher': 'test', 'key_size': 1,
|
||||
'control_location': 'front-end'}
|
||||
result = cs.volume_encryption_types.update(1, {})
|
||||
cs.assert_called('PUT', '/types/1/encryption/provider')
|
||||
self.assertEqual(expected, result,
|
||||
"empty update must yield original data")
|
||||
|
||||
def test_delete(self):
|
||||
"""
|
||||
|
||||
@@ -95,7 +95,8 @@ class VolumesTest(utils.TestCase):
|
||||
'project_id': None,
|
||||
'metadata': {},
|
||||
'source_replica': None,
|
||||
'consistencygroup_id': None},
|
||||
'consistencygroup_id': None,
|
||||
'multiattach': False},
|
||||
'OS-SCH-HNT:scheduler_hints': 'uuid'}
|
||||
cs.assert_called('POST', '/volumes', body=expected)
|
||||
|
||||
@@ -104,9 +105,14 @@ class VolumesTest(utils.TestCase):
|
||||
cs.volumes.attach(v, 1, '/dev/vdc', mode='ro')
|
||||
cs.assert_called('POST', '/volumes/1234/action')
|
||||
|
||||
def test_attach_to_host(self):
|
||||
v = cs.volumes.get('1234')
|
||||
cs.volumes.attach(v, None, None, host_name='test', mode='rw')
|
||||
cs.assert_called('POST', '/volumes/1234/action')
|
||||
|
||||
def test_detach(self):
|
||||
v = cs.volumes.get('1234')
|
||||
cs.volumes.detach(v)
|
||||
cs.volumes.detach(v, 'abc123')
|
||||
cs.assert_called('POST', '/volumes/1234/action')
|
||||
|
||||
def test_reserve(self):
|
||||
|
||||
@@ -43,7 +43,7 @@ class QoSSpecsManager(base.ManagerWithFind):
|
||||
"""
|
||||
resource_class = QoSSpecs
|
||||
|
||||
def list(self):
|
||||
def list(self, search_opts=None):
|
||||
"""Get a list of all qos specs.
|
||||
|
||||
:rtype: list of :class:`QoSSpecs`.
|
||||
|
||||
@@ -26,7 +26,7 @@ class QuotaClassSet(base.Resource):
|
||||
return self.class_name
|
||||
|
||||
def update(self, *args, **kwargs):
|
||||
self.manager.update(self.class_name, *args, **kwargs)
|
||||
return self.manager.update(self.class_name, *args, **kwargs)
|
||||
|
||||
|
||||
class QuotaClassSetManager(base.Manager):
|
||||
@@ -42,4 +42,6 @@ class QuotaClassSetManager(base.Manager):
|
||||
for update in updates:
|
||||
body['quota_class_set'][update] = updates[update]
|
||||
|
||||
self._update('/os-quota-class-sets/%s' % (class_name), body)
|
||||
result = self._update('/os-quota-class-sets/%s' % (class_name), body)
|
||||
return self.resource_class(self,
|
||||
result['quota_class_set'], loaded=True)
|
||||
|
||||
@@ -40,14 +40,17 @@ class Volume(base.Resource):
|
||||
"""Update the display_name or display_description for this volume."""
|
||||
self.manager.update(self, **kwargs)
|
||||
|
||||
def attach(self, instance_uuid, mountpoint, mode='rw'):
|
||||
def attach(self, instance_uuid, mountpoint, mode='rw',
|
||||
host_name=None):
|
||||
"""Set attachment metadata.
|
||||
|
||||
:param instance_uuid: uuid of the attaching instance.
|
||||
:param mountpoint: mountpoint on the attaching instance.
|
||||
:param mountpoint: mountpoint on the attaching instance or host.
|
||||
:param mode: the access mode
|
||||
:param host_name: name of the attaching host.
|
||||
"""
|
||||
return self.manager.attach(self, instance_uuid, mountpoint, mode)
|
||||
return self.manager.attach(self, instance_uuid, mountpoint, mode,
|
||||
host_name)
|
||||
|
||||
def detach(self):
|
||||
"""Clear attachment metadata."""
|
||||
@@ -255,21 +258,24 @@ class VolumeManager(base.ManagerWithFind):
|
||||
url = '/volumes/%s/action' % base.getid(volume)
|
||||
return self.api.client.post(url, body=body)
|
||||
|
||||
def attach(self, volume, instance_uuid, mountpoint, mode='rw'):
|
||||
def attach(self, volume, instance_uuid, mountpoint, mode='rw',
|
||||
host_name=None):
|
||||
"""
|
||||
Set attachment metadata.
|
||||
|
||||
:param volume: The :class:`Volume` (or its ID)
|
||||
you would like to attach.
|
||||
:param instance_uuid: uuid of the attaching instance.
|
||||
:param instance_uuid: uuid of the attaching instance or host.
|
||||
:param mountpoint: mountpoint on the attaching instance.
|
||||
:param mode: the access mode.
|
||||
:param host_name: name of the attaching host.
|
||||
"""
|
||||
return self._action('os-attach',
|
||||
volume,
|
||||
{'instance_uuid': instance_uuid,
|
||||
'mountpoint': mountpoint,
|
||||
'mode': mode})
|
||||
body = {'mountpoint': mountpoint, 'mode': mode}
|
||||
if instance_uuid is not None:
|
||||
body.update({'instance_uuid': instance_uuid})
|
||||
if host_name is not None:
|
||||
body.update({'host_name': host_name})
|
||||
return self._action('os-attach', volume, body)
|
||||
|
||||
def detach(self, volume):
|
||||
"""
|
||||
|
||||
@@ -43,7 +43,7 @@ class QoSSpecsManager(base.ManagerWithFind):
|
||||
"""
|
||||
resource_class = QoSSpecs
|
||||
|
||||
def list(self):
|
||||
def list(self, search_opts=None):
|
||||
"""Get a list of all qos specs.
|
||||
|
||||
:rtype: list of :class:`QoSSpecs`.
|
||||
|
||||
@@ -24,7 +24,7 @@ class QuotaClassSet(base.Resource):
|
||||
return self.class_name
|
||||
|
||||
def update(self, *args, **kwargs):
|
||||
self.manager.update(self.class_name, *args, **kwargs)
|
||||
return self.manager.update(self.class_name, *args, **kwargs)
|
||||
|
||||
|
||||
class QuotaClassSetManager(base.Manager):
|
||||
@@ -40,4 +40,6 @@ class QuotaClassSetManager(base.Manager):
|
||||
for update in updates:
|
||||
body['quota_class_set'][update] = updates[update]
|
||||
|
||||
self._update('/os-quota-class-sets/%s' % (class_name), body)
|
||||
result = self._update('/os-quota-class-sets/%s' % (class_name), body)
|
||||
return self.resource_class(self,
|
||||
result['quota_class_set'], loaded=True)
|
||||
|
||||
@@ -234,10 +234,12 @@ def do_list(cs, args):
|
||||
|
||||
if all_tenants:
|
||||
key_list = ['ID', 'Tenant ID', 'Status', 'Name',
|
||||
'Size', 'Volume Type', 'Bootable', 'Attached to']
|
||||
'Size', 'Volume Type', 'Bootable', 'Multiattach',
|
||||
'Attached to']
|
||||
else:
|
||||
key_list = ['ID', 'Status', 'Name',
|
||||
'Size', 'Volume Type', 'Bootable', 'Attached to']
|
||||
'Size', 'Volume Type', 'Bootable',
|
||||
'Multiattach', 'Attached to']
|
||||
if args.sort_key or args.sort_dir or args.sort:
|
||||
sortby_index = None
|
||||
else:
|
||||
@@ -261,8 +263,8 @@ def do_show(cs, args):
|
||||
|
||||
class CheckSizeArgForCreate(argparse.Action):
|
||||
def __call__(self, parser, args, values, option_string=None):
|
||||
if (values or args.snapshot_id or args.source_volid
|
||||
or args.source_replica) is None:
|
||||
if ((args.snapshot_id or args.source_volid or args.source_replica)
|
||||
is None and values is None):
|
||||
parser.error('Size is a required parameter if snapshot '
|
||||
'or source volume is not specified.')
|
||||
setattr(args, self.dest, values)
|
||||
@@ -348,6 +350,12 @@ class CheckSizeArgForCreate(argparse.Action):
|
||||
action='append',
|
||||
default=[],
|
||||
help='Scheduler hint, like in nova.')
|
||||
@utils.arg('--allow-multiattach',
|
||||
dest='multiattach',
|
||||
action="store_true",
|
||||
help=('Allow volume to be attached more than once.'
|
||||
' Default=False'),
|
||||
default=False)
|
||||
@utils.service_type('volumev2')
|
||||
def do_create(cs, args):
|
||||
"""Creates a volume."""
|
||||
@@ -391,7 +399,8 @@ def do_create(cs, args):
|
||||
imageRef=image_ref,
|
||||
metadata=volume_metadata,
|
||||
scheduler_hints=hints,
|
||||
source_replica=args.source_replica)
|
||||
source_replica=args.source_replica,
|
||||
multiattach=args.multiattach)
|
||||
|
||||
info = dict()
|
||||
volume = cs.volumes.get(volume.id)
|
||||
@@ -738,17 +747,9 @@ def _print_volume_type_list(vtypes):
|
||||
|
||||
|
||||
@utils.service_type('volumev2')
|
||||
@utils.arg('--all',
|
||||
dest='all',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Display all volume types (Admin only).')
|
||||
def do_type_list(cs, args):
|
||||
"""Lists available 'volume types'."""
|
||||
if args.all:
|
||||
vtypes = cs.volume_types.list(is_public=None)
|
||||
else:
|
||||
vtypes = cs.volume_types.list()
|
||||
"""Lists available 'volume types'. (Admin only will see private types)"""
|
||||
vtypes = cs.volume_types.list()
|
||||
_print_volume_type_list(vtypes)
|
||||
|
||||
|
||||
@@ -891,7 +892,7 @@ def do_credentials(cs, args):
|
||||
|
||||
_quota_resources = ['volumes', 'snapshots', 'gigabytes',
|
||||
'backups', 'backup_gigabytes',
|
||||
'consistencygroups']
|
||||
'consistencygroups', 'per_volume_gigabytes']
|
||||
_quota_infos = ['Type', 'In_use', 'Reserved', 'Limit']
|
||||
|
||||
|
||||
@@ -997,6 +998,10 @@ def do_quota_defaults(cs, args):
|
||||
metavar='<volume_type_name>',
|
||||
default=None,
|
||||
help='Volume type. Default=None.')
|
||||
@utils.arg('--per-volume-gigabytes',
|
||||
metavar='<per_volume_gigabytes>',
|
||||
type=int, default=None,
|
||||
help='Set max volume size limit. Default=None.')
|
||||
@utils.service_type('volumev2')
|
||||
def do_quota_update(cs, args):
|
||||
"""Updates quotas for a tenant."""
|
||||
@@ -1023,8 +1028,8 @@ def do_quota_class_show(cs, args):
|
||||
_quota_show(cs.quota_classes.get(args.class_name))
|
||||
|
||||
|
||||
@utils.arg('class-name',
|
||||
metavar='<class-name>',
|
||||
@utils.arg('class_name',
|
||||
metavar='<class_name>',
|
||||
help='Name of quota class for which to set quotas.')
|
||||
@utils.arg('--volumes',
|
||||
metavar='<volumes>',
|
||||
@@ -1544,6 +1549,63 @@ def do_encryption_type_create(cs, args):
|
||||
_print_volume_encryption_type_list([result])
|
||||
|
||||
|
||||
@utils.arg('volume_type',
|
||||
metavar='<volume-type>',
|
||||
type=str,
|
||||
help="Name or ID of the volume type")
|
||||
@utils.arg('--provider',
|
||||
metavar='<provider>',
|
||||
type=str,
|
||||
required=False,
|
||||
default=argparse.SUPPRESS,
|
||||
help="Class providing encryption support (e.g. LuksEncryptor) "
|
||||
"(Optional)")
|
||||
@utils.arg('--cipher',
|
||||
metavar='<cipher>',
|
||||
type=str,
|
||||
nargs='?',
|
||||
required=False,
|
||||
default=argparse.SUPPRESS,
|
||||
const=None,
|
||||
help="Encryption algorithm/mode to use (e.g., aes-xts-plain64). "
|
||||
"Provide parameter without value to set to provider default. "
|
||||
"(Optional)")
|
||||
@utils.arg('--key-size',
|
||||
dest='key_size',
|
||||
metavar='<key-size>',
|
||||
type=int,
|
||||
nargs='?',
|
||||
required=False,
|
||||
default=argparse.SUPPRESS,
|
||||
const=None,
|
||||
help="Size of the encryption key, in bits (e.g., 128, 256). "
|
||||
"Provide parameter without value to set to provider default. "
|
||||
"(Optional)")
|
||||
@utils.arg('--control-location',
|
||||
dest='control_location',
|
||||
metavar='<control-location>',
|
||||
choices=['front-end', 'back-end'],
|
||||
type=str,
|
||||
required=False,
|
||||
default=argparse.SUPPRESS,
|
||||
help="Notional service where encryption is performed (e.g., "
|
||||
"front-end=Nova). Values: 'front-end', 'back-end' (Optional)")
|
||||
@utils.service_type('volumev2')
|
||||
def do_encryption_type_update(cs, args):
|
||||
"""Update encryption type information for a volume type (Admin Only)."""
|
||||
volume_type = _find_volume_type(cs, args.volume_type)
|
||||
|
||||
# An argument should only be pulled if the user specified the parameter.
|
||||
body = {}
|
||||
for attr in ['provider', 'cipher', 'key_size', 'control_location']:
|
||||
if hasattr(args, attr):
|
||||
body[attr] = getattr(args, attr)
|
||||
|
||||
cs.volume_encryption_types.update(volume_type, body)
|
||||
result = cs.volume_encryption_types.get(volume_type)
|
||||
_print_volume_encryption_type_list([result])
|
||||
|
||||
|
||||
@utils.arg('volume_type',
|
||||
metavar='<volume_type>',
|
||||
type=str,
|
||||
@@ -1841,11 +1903,9 @@ def do_manage(cs, args):
|
||||
# dictionary so that it is consistent with what the user specified on the
|
||||
# CLI.
|
||||
|
||||
if hasattr(args, 'source_name') and \
|
||||
args.source_name is not None:
|
||||
if hasattr(args, 'source_name') and args.source_name is not None:
|
||||
ref_dict['source-name'] = args.source_name
|
||||
if hasattr(args, 'source_id') and \
|
||||
args.source_id is not None:
|
||||
if hasattr(args, 'source_id') and args.source_id is not None:
|
||||
ref_dict['source-id'] = args.source_id
|
||||
|
||||
volume = cs.volumes.manage(host=args.host,
|
||||
|
||||
@@ -94,7 +94,7 @@ class VolumeBackupManager(base.ManagerWithFind):
|
||||
"""Export volume backup metadata record.
|
||||
|
||||
:param backup_service: Backup service to use for importing the backup
|
||||
:param backup_urlBackup URL for importing the backup metadata
|
||||
:param backup_url: Backup URL for importing the backup metadata
|
||||
:rtype: :class:`VolumeBackup`
|
||||
"""
|
||||
body = {'backup-record': {'backup_service': backup_service,
|
||||
|
||||
@@ -84,7 +84,9 @@ class VolumeEncryptionTypeManager(base.ManagerWithFind):
|
||||
:param specs: the encryption type specifications to update
|
||||
:return: an instance of :class: VolumeEncryptionType
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
body = {'encryption': specs}
|
||||
return self._update("/types/%s/encryption/provider" %
|
||||
base.getid(volume_type), body)
|
||||
|
||||
def delete(self, volume_type):
|
||||
"""
|
||||
|
||||
@@ -77,7 +77,7 @@ class VolumeTypeManager(base.ManagerWithFind):
|
||||
"""Manage :class:`VolumeType` resources."""
|
||||
resource_class = VolumeType
|
||||
|
||||
def list(self, search_opts=None, is_public=True):
|
||||
def list(self, search_opts=None, is_public=None):
|
||||
"""Lists all volume types.
|
||||
|
||||
:rtype: list of :class:`VolumeType`.
|
||||
|
||||
@@ -45,14 +45,16 @@ class Volume(base.Resource):
|
||||
"""Update the name or description for this volume."""
|
||||
self.manager.update(self, **kwargs)
|
||||
|
||||
def attach(self, instance_uuid, mountpoint, mode='rw'):
|
||||
def attach(self, instance_uuid, mountpoint, mode='rw', host_name=None):
|
||||
"""Set attachment metadata.
|
||||
|
||||
:param instance_uuid: uuid of the attaching instance.
|
||||
:param mountpoint: mountpoint on the attaching instance.
|
||||
:param mountpoint: mountpoint on the attaching instance or host.
|
||||
:param mode: the access mode.
|
||||
:param host_name: name of the attaching host.
|
||||
"""
|
||||
return self.manager.attach(self, instance_uuid, mountpoint, mode)
|
||||
return self.manager.attach(self, instance_uuid, mountpoint, mode,
|
||||
host_name)
|
||||
|
||||
def detach(self):
|
||||
"""Clear attachment metadata."""
|
||||
@@ -177,8 +179,8 @@ class VolumeManager(base.ManagerWithFind):
|
||||
volume_type=None, user_id=None,
|
||||
project_id=None, availability_zone=None,
|
||||
metadata=None, imageRef=None, scheduler_hints=None,
|
||||
source_replica=None):
|
||||
"""Creates a volume.
|
||||
source_replica=None, multiattach=False):
|
||||
"""Create a volume.
|
||||
|
||||
:param size: Size of volume in GB
|
||||
:param consistencygroup_id: ID of the consistencygroup
|
||||
@@ -195,6 +197,8 @@ class VolumeManager(base.ManagerWithFind):
|
||||
:param source_replica: ID of source volume to clone replica
|
||||
:param scheduler_hints: (optional extension) arbitrary key-value pairs
|
||||
specified by the client to help boot an instance
|
||||
:param multiattach: Allow the volume to be attached to more than
|
||||
one instance
|
||||
:rtype: :class:`Volume`
|
||||
"""
|
||||
if metadata is None:
|
||||
@@ -217,6 +221,7 @@ class VolumeManager(base.ManagerWithFind):
|
||||
'imageRef': imageRef,
|
||||
'source_volid': source_volid,
|
||||
'source_replica': source_replica,
|
||||
'multiattach': multiattach,
|
||||
}}
|
||||
|
||||
if scheduler_hints:
|
||||
@@ -373,28 +378,33 @@ class VolumeManager(base.ManagerWithFind):
|
||||
url = '/volumes/%s/action' % base.getid(volume)
|
||||
return self.api.client.post(url, body=body)
|
||||
|
||||
def attach(self, volume, instance_uuid, mountpoint, mode='rw'):
|
||||
def attach(self, volume, instance_uuid, mountpoint, mode='rw',
|
||||
host_name=None):
|
||||
"""Set attachment metadata.
|
||||
|
||||
:param volume: The :class:`Volume` (or its ID)
|
||||
you would like to attach.
|
||||
:param instance_uuid: uuid of the attaching instance.
|
||||
:param mountpoint: mountpoint on the attaching instance.
|
||||
:param mountpoint: mountpoint on the attaching instance or host.
|
||||
:param mode: the access mode.
|
||||
:param host_name: name of the attaching host.
|
||||
"""
|
||||
return self._action('os-attach',
|
||||
volume,
|
||||
{'instance_uuid': instance_uuid,
|
||||
'mountpoint': mountpoint,
|
||||
'mode': mode})
|
||||
body = {'mountpoint': mountpoint, 'mode': mode}
|
||||
if instance_uuid is not None:
|
||||
body.update({'instance_uuid': instance_uuid})
|
||||
if host_name is not None:
|
||||
body.update({'host_name': host_name})
|
||||
return self._action('os-attach', volume, body)
|
||||
|
||||
def detach(self, volume):
|
||||
def detach(self, volume, attachment_uuid=None):
|
||||
"""Clear attachment metadata.
|
||||
|
||||
:param volume: The :class:`Volume` (or its ID)
|
||||
you would like to detach.
|
||||
:param attachment_uuid: The uuid of the volume attachment.
|
||||
"""
|
||||
return self._action('os-detach', volume)
|
||||
return self._action('os-detach', volume,
|
||||
{'attachment_id': attachment_uuid})
|
||||
|
||||
def reserve(self, volume):
|
||||
"""Reserve this volume.
|
||||
|
||||
@@ -33,6 +33,23 @@ Release Notes
|
||||
MASTER
|
||||
-----
|
||||
|
||||
1.3.0
|
||||
-----
|
||||
|
||||
* Revert version discovery support due to this breaking deployments using
|
||||
proxies. We will revisit this once the Kilo config option 'public_endpoint'
|
||||
has been available longer to allow these deployments to work again with
|
||||
version discovery available from the Cinder client.
|
||||
* Add volume multi-attach support.
|
||||
* Add encryption-type-update to update volume encryption types.
|
||||
|
||||
.. _1454276 http://bugs.launchpad.net/python-cinderclient/+bug/1454276
|
||||
.. _1462104 http://bugs.launchpad.net/python-cinderclient/+bug/1462104
|
||||
.. _1418580 http://bugs.launchpad.net/python-cinderclient/+bug/1418580
|
||||
.. _1464160 http://bugs.launchpad.net/python-cinderclient/+bug/1464160
|
||||
|
||||
|
||||
|
||||
1.2.2
|
||||
-----
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# The order of packages is significant, because pip processes them in the order
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
pbr>=0.11,<2.0
|
||||
pbr<2.0,>=0.11
|
||||
argparse
|
||||
PrettyTable>=0.7,<0.8
|
||||
PrettyTable<0.8,>=0.7
|
||||
python-keystoneclient>=1.6.0
|
||||
requests>=2.5.2
|
||||
simplejson>=2.2.0
|
||||
|
||||
1
setup.py
1
setup.py
@@ -1,4 +1,3 @@
|
||||
#!/usr/bin/env python
|
||||
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
# of appearance. Changing the order has an impact on the overall integration
|
||||
# process, which may cause wedges in the gate later.
|
||||
# Hacking already pins down pep8, pyflakes and flake8
|
||||
hacking>=0.10.0,<0.11
|
||||
hacking<0.11,>=0.10.0
|
||||
coverage>=3.6
|
||||
discover
|
||||
fixtures>=0.3.14
|
||||
fixtures>=1.3.1
|
||||
mock>=1.0
|
||||
oslosphinx>=2.5.0 # Apache-2.0
|
||||
oslosphinx>=2.5.0 # Apache-2.0
|
||||
python-subunit>=0.0.18
|
||||
requests-mock>=0.6.0 # Apache-2.0
|
||||
sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3
|
||||
tempest-lib>=0.5.0
|
||||
testtools>=0.9.36,!=1.2.0
|
||||
requests-mock>=0.6.0 # Apache-2.0
|
||||
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2
|
||||
tempest-lib>=0.6.1
|
||||
testtools>=1.4.0
|
||||
testrepository>=0.0.18
|
||||
|
||||
3
tox.ini
3
tox.ini
@@ -1,6 +1,6 @@
|
||||
[tox]
|
||||
distribute = False
|
||||
envlist = py26,py27,py33,pypy,pep8
|
||||
envlist = py26,py27,py33,py34,pypy,pep8
|
||||
minversion = 1.6
|
||||
skipsdist = True
|
||||
|
||||
@@ -29,6 +29,7 @@ commands=
|
||||
[testenv:functional]
|
||||
setenv =
|
||||
OS_TEST_PATH = ./cinderclient/tests/functional
|
||||
OS_VOLUME_API_VERSION = 2
|
||||
|
||||
[tox:jenkins]
|
||||
downloadcache = ~/cache/pip
|
||||
|
||||
Reference in New Issue
Block a user