merged cinder-to-manila

This commit is contained in:
Yulia Portnova 2013-09-17 16:41:56 +03:00
commit 95c64adea3
101 changed files with 1172 additions and 6511 deletions

6
.gitignore vendored
View File

@ -1 +1,7 @@
*.pyc
.idea
python_manilaclient.egg-info
setuptools_git*.egg
setuptools_git*.egg
AUTHORS
ChangeLog

8
.testr.conf Normal file
View File

@ -0,0 +1,8 @@
[DEFAULT]
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \
${PYTHON:-python} -m subunit.run discover -t ./ ./tests $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list

14
HACKING
View File

@ -1,4 +1,4 @@
Cinder Style Commandments
Manila Style Commandments
=========================
Step 1: Read http://www.python.org/dev/peps/pep-0008/
@ -16,7 +16,7 @@ Imports
# vim: tabstop=4 shiftwidth=4 softtabstop=4
{{stdlib imports in human alphabetical order}}
\n
{{cinder imports in human alphabetical order}}
{{manila imports in human alphabetical order}}
\n
\n
{{begin your code}}
@ -42,11 +42,11 @@ Human Alphabetical Order Examples
import time
import unittest
from cinder import flags
from cinder import test
from cinder.auth import users
from cinder.endpoint import api
from cinder.endpoint import cloud
from manila import flags
from manila import test
from manila.auth import users
from manila.endpoint import api
from manila.endpoint import cloud
Docstrings
----------

View File

@ -178,7 +178,7 @@ All rights reserved.
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
--- License for python-cinderclient versions prior to 2.1 ---
--- License for python-manilaclient versions prior to 2.1 ---
All rights reserved.

View File

@ -1,11 +1,11 @@
Python bindings to the OpenStack Cinder API
Python bindings to the OpenStack Manila API
===========================================
This is a client for the OpenStack Cinder API. There's a Python API (the
``cinderclient`` module), and a command-line script (``cinder``). Each
implements 100% of the OpenStack Cinder API.
This is a client for the OpenStack Manila API. There's a Python API (the
``manilaclient`` module), and a command-line script (``manila``). Each
implements 100% of the OpenStack Manila API.
See the `OpenStack CLI guide`_ for information on how to use the ``cinder``
See the `OpenStack CLI guide`_ for information on how to use the ``manila``
command-line tool. You may also want to look at the
`OpenStack API documentation`_.
@ -16,13 +16,13 @@ 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
pull requests.
.. _Github: https://github.com/openstack/python-cinderclient
.. _Launchpad: https://launchpad.net/python-cinderclient
.. _Github: https://github.com/openstack/python-manilaclient
.. _Launchpad: https://launchpad.net/python-manilaclient
.. _Gerrit: http://wiki.openstack.org/GerritWorkflow
This code 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.
python-manilaclient is licensed under the Apache License like the rest of OpenStack.
__ http://github.com/jacobian/python-cloudservers
@ -32,7 +32,7 @@ __ http://github.com/jacobian/python-cloudservers
Command-line API
----------------
Installing this package gets you a shell command, ``cinder``, that you
Installing this package gets you a shell command, ``manila``, that you
can use to interact with any Rackspace compatible API (including OpenStack).
You'll need to provide your OpenStack username and password. You can do this
@ -50,7 +50,7 @@ variables as well::
export OS_AUTH_URL=http://example.com:8774/v1.1/
export OS_VOLUME_API_VERSION=1
If you are using Keystone, you need to set the CINDER_URL to the keystone
If you are using Keystone, you need to set the MANILA_URL to the keystone
endpoint::
export OS_AUTH_URL=http://example.com:5000/v2.0/
@ -60,9 +60,9 @@ can specify the one you want with ``--os-region-name`` (or
``export OS_REGION_NAME``). It defaults to the first in the list returned.
You'll find complete documentation on the shell by running
``cinder help``::
``manila help``::
usage: cinder [--debug] [--os-username <auth-user-name>]
usage: manila [--debug] [--os-username <auth-user-name>]
[--os-password <auth-password>]
[--os-tenant-name <auth-tenant-name>] [--os-auth-url <auth-url>]
[--os-region-name <region-name>] [--service-type <service-type>]
@ -73,7 +73,7 @@ You'll find complete documentation on the shell by running
[--os-cacert <ca-certificate>] [--retries <retries>]
<subcommand> ...
Command-line interface to the OpenStack Cinder API.
Command-line interface to the OpenStack Manila API.
Positional arguments:
<subcommand>
@ -124,11 +124,11 @@ You'll find complete documentation on the shell by running
--service-type <service-type>
Defaults to compute for most actions
--service-name <service-name>
Defaults to env[CINDER_SERVICE_NAME]
Defaults to env[MANILA_SERVICE_NAME]
--volume-service-name <volume-service-name>
Defaults to env[CINDER_VOLUME_SERVICE_NAME]
Defaults to env[MANILA_VOLUME_SERVICE_NAME]
--endpoint-type <endpoint-type>
Defaults to env[CINDER_ENDPOINT_TYPE] or publicURL.
Defaults to env[MANILA_ENDPOINT_TYPE] or publicURL.
--os-volume-api-version <compute-api-ver>
Accepts 1,defaults to env[OS_VOLUME_API_VERSION].
--os-cacert <ca-certificate>
@ -144,7 +144,7 @@ There's also a complete Python API, but it has not yet been documented.
Quick-start using keystone::
# use v2.0 auth with http://example.com:5000/v2.0/")
>>> from cinderclient.v1 import client
>>> from manilaclient.v1 import client
>>> nt = client.Client(USER, PASS, TENANT, AUTH_URL, service_type="volume")
>>> nt.volumes.list()
[...]

1
__init__.py Normal file
View File

@ -0,0 +1 @@
__author__ = 'vkostenko'

View File

@ -1,17 +0,0 @@
# Copyright (c) 2012 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 cinderclient.v1.client import Client

View File

@ -1,89 +0,0 @@
from cinderclient import client
from cinderclient.v1 import limits
from cinderclient.v1 import quota_classes
from cinderclient.v1 import quotas
from cinderclient.v1 import shares
from cinderclient.v1 import share_snapshots
from cinderclient.v1 import volumes
from cinderclient.v1 import volume_snapshots
from cinderclient.v1 import volume_types
from cinderclient.v1 import volume_backups
from cinderclient.v1 import volume_backups_restore
class Client(object):
"""
Top-level object to access the OpenStack Volume API.
Create an instance with your creds::
>>> client = Client(USERNAME, PASSWORD, PROJECT_ID, AUTH_URL)
Then call methods on its managers::
>>> client.volumes.list()
...
"""
def __init__(self, username, api_key, project_id=None, auth_url='',
insecure=False, timeout=None, tenant_id=None,
proxy_tenant_id=None, proxy_token=None, region_name=None,
endpoint_type='publicURL', extensions=None,
service_type='volume', service_name=None,
volume_service_name=None, retries=None,
http_log_debug=False,
cacert=None):
# FIXME(comstud): Rename the api_key argument above when we
# know it's not being used as keyword argument
password = api_key
self.limits = limits.LimitsManager(self)
# extensions
self.volumes = volumes.VolumeManager(self)
self.volume_snapshots = volume_snapshots.SnapshotManager(self)
self.volume_types = volume_types.VolumeTypeManager(self)
self.quota_classes = quota_classes.QuotaClassSetManager(self)
self.quotas = quotas.QuotaSetManager(self)
self.backups = volume_backups.VolumeBackupManager(self)
self.restores = volume_backups_restore.VolumeBackupRestoreManager(self)
self.shares = shares.ShareManager(self)
self.share_snapshots = share_snapshots.ShareSnapshotManager(self)
# Add in any extensions...
if extensions:
for extension in extensions:
if extension.manager_class:
setattr(self, extension.name,
extension.manager_class(self))
self.client = client.HTTPClient(
username,
password,
project_id,
auth_url,
insecure=insecure,
timeout=timeout,
tenant_id=tenant_id,
proxy_token=proxy_token,
proxy_tenant_id=proxy_tenant_id,
region_name=region_name,
endpoint_type=endpoint_type,
service_type=service_type,
service_name=service_name,
volume_service_name=volume_service_name,
retries=retries,
http_log_debug=http_log_debug,
cacert=cacert)
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.
"""
self.client.authenticate()

View File

@ -1,47 +0,0 @@
# Copyright 2011 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 cinderclient import base
from cinderclient import utils
class ListExtResource(base.Resource):
@property
def summary(self):
descr = self.description.strip()
if not descr:
return '??'
lines = descr.split("\n")
if len(lines) == 1:
return lines[0]
else:
return lines[0] + "..."
class ListExtManager(base.Manager):
resource_class = ListExtResource
def show_all(self):
return self._list("/extensions", 'extensions')
@utils.service_type('volume')
def do_list_extensions(client, _args):
"""
List all the os-api extensions that are available.
"""
extensions = client.list_extensions.show_all()
fields = ["Name", "Summary", "Alias", "Updated"]
utils.print_list(extensions, fields)

View File

@ -1,79 +0,0 @@
# Copyright 2011 OpenStack LLC.
from cinderclient import base
class Limits(base.Resource):
"""A collection of RateLimit and AbsoluteLimit objects"""
def __repr__(self):
return "<Limits>"
@property
def absolute(self):
for (name, value) in 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

@ -1,52 +0,0 @@
# Copyright 2012 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 cinderclient import base
class QuotaClassSet(base.Resource):
@property
def id(self):
"""QuotaClassSet does not have a 'id' attribute but base.Resource
needs it to self-refresh and QuotaSet is indexed by class_name"""
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,
volumes=None,
gigabytes=None):
body = {'quota_class_set': {
'class_name': class_name,
'volumes': volumes,
'gigabytes': gigabytes}}
for key in body['quota_class_set'].keys():
if body['quota_class_set'][key] is None:
body['quota_class_set'].pop(key)
self._update('/os-quota-class-sets/%s' % (class_name), body)

View File

@ -1,55 +0,0 @@
# Copyright 2011 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 cinderclient import base
class QuotaSet(base.Resource):
@property
def id(self):
"""QuotaSet does not have a 'id' attribute but base.Resource needs it
to self-refresh and QuotaSet is indexed by tenant_id"""
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):
if hasattr(tenant_id, 'tenant_id'):
tenant_id = tenant_id.tenant_id
return self._get("/os-quota-sets/%s" % (tenant_id), "quota_set")
def update(self, tenant_id, volumes=None, snapshots=None, gigabytes=None):
body = {'quota_set': {
'tenant_id': tenant_id,
'volumes': volumes,
'snapshots': snapshots,
'gigabytes': gigabytes}}
for key in body['quota_set'].keys():
if body['quota_set'][key] is None:
body['quota_set'].pop(key)
self._update('/os-quota-sets/%s' % (tenant_id), body)
def defaults(self, tenant_id):
return self._get('/os-quota-sets/%s/defaults' % tenant_id,
'quota_set')

View File

@ -1,974 +0,0 @@
# Copyright 2010 Jacob Kaplan-Moss
# Copyright 2011 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 argparse
import os
import sys
import time
from cinderclient import exceptions
from cinderclient import utils
def _poll_for_status(poll_fn, obj_id, action, final_ok_states,
poll_period=5, show_progress=True):
"""Block while an action is being performed, periodically printing
progress.
"""
def print_progress(progress):
if show_progress:
msg = ('\rInstance %(action)s... %(progress)s%% complete'
% dict(action=action, progress=progress))
else:
msg = '\rInstance %(action)s...' % dict(action=action)
sys.stdout.write(msg)
sys.stdout.flush()
print
while True:
obj = poll_fn(obj_id)
status = obj.status.lower()
progress = getattr(obj, 'progress', None) or 0
if status in final_ok_states:
print_progress(100)
print "\nFinished"
break
elif status == "error":
print "\nError %(action)s instance" % locals()
break
else:
print_progress(progress)
time.sleep(poll_period)
def _find_volume(cs, volume):
"""Get a volume by ID."""
return utils.find_resource(cs.volumes, volume)
def _find_volume_snapshot(cs, snapshot):
"""Get a volume snapshot by ID."""
return utils.find_resource(cs.volume_snapshots, snapshot)
def _find_backup(cs, backup):
"""Get a backup by ID."""
return utils.find_resource(cs.backups, backup)
def _print_volume(volume):
utils.print_dict(volume._info)
def _print_volume_snapshot(snapshot):
utils.print_dict(snapshot._info)
def _find_share(cs, share):
"""Get a share by ID."""
return utils.find_resource(cs.shares, share)
def _print_share(cs, share):
info = share._info.copy()
info.pop('links', None)
utils.print_dict(info)
def _find_share_snapshot(cs, snapshot):
"""Get a snapshot by ID."""
return utils.find_resource(cs.share_snapshots, snapshot)
def _print_share_snapshot(cs, snapshot):
info = snapshot._info.copy()
info.pop('links', None)
utils.print_dict(info)
def _translate_keys(collection, convert):
for item in collection:
keys = item.__dict__.keys()
for from_key, to_key in convert:
if from_key in keys and to_key not in keys:
setattr(item, to_key, item._info[from_key])
def _translate_volume_keys(collection):
convert = [('displayName', 'display_name'), ('volumeType', 'volume_type')]
_translate_keys(collection, convert)
def _translate_volume_snapshot_keys(collection):
convert = [('displayName', 'display_name'), ('volumeId', 'volume_id')]
_translate_keys(collection, convert)
def _extract_metadata(args):
metadata = {}
for metadatum in args.metadata:
# unset doesn't require a val, so we have the if/else
if '=' in metadatum:
(key, value) = metadatum.split('=', 1)
else:
key = metadatum
value = None
metadata[key] = value
return metadata
@utils.arg(
'--all-tenants',
dest='all_tenants',
metavar='<0|1>',
nargs='?',
type=int,
const=1,
default=0,
help='Display information from all tenants (Admin only).')
@utils.arg(
'--all_tenants',
nargs='?',
type=int,
const=1,
help=argparse.SUPPRESS)
@utils.arg(
'--display-name',
metavar='<display-name>',
default=None,
help='Filter results by display-name')
@utils.arg(
'--status',
metavar='<status>',
default=None,
help='Filter results by status')
@utils.service_type('volume')
def do_list(cs, args):
"""List all the volumes."""
all_tenants = int(os.environ.get("ALL_TENANTS", args.all_tenants))
search_opts = {
'all_tenants': all_tenants,
'display_name': args.display_name,
'status': args.status,
}
volumes = cs.volumes.list(search_opts=search_opts)
_translate_volume_keys(volumes)
# Create a list of servers to which the volume is attached
for vol in volumes:
servers = [s.get('server_id') for s in vol.attachments]
setattr(vol, 'attached_to', ','.join(map(str, servers)))
utils.print_list(volumes, ['ID', 'Status', 'Display Name',
'Size', 'Volume Type', 'Bootable', 'Attached to'])
@utils.arg('volume', metavar='<volume>', help='ID of the volume.')
@utils.service_type('volume')
def do_show(cs, args):
"""Show details about a volume."""
volume = _find_volume(cs, args.volume)
_print_volume(volume)
@utils.arg('size',
metavar='<size>',
type=int,
help='Size of volume in GB')
@utils.arg(
'--snapshot-id',
metavar='<snapshot-id>',
default=None,
help='Create volume from snapshot id (Optional, Default=None)')
@utils.arg(
'--snapshot_id',
help=argparse.SUPPRESS)
@utils.arg(
'--source-volid',
metavar='<source-volid>',
default=None,
help='Create volume from volume id (Optional, Default=None)')
@utils.arg(
'--source_volid',
help=argparse.SUPPRESS)
@utils.arg(
'--image-id',
metavar='<image-id>',
default=None,
help='Create volume from image id (Optional, Default=None)')
@utils.arg(
'--image_id',
help=argparse.SUPPRESS)
@utils.arg(
'--display-name',
metavar='<display-name>',
default=None,
help='Volume name (Optional, Default=None)')
@utils.arg(
'--display_name',
help=argparse.SUPPRESS)
@utils.arg(
'--display-description',
metavar='<display-description>',
default=None,
help='Volume description (Optional, Default=None)')
@utils.arg(
'--display_description',
help=argparse.SUPPRESS)
@utils.arg(
'--volume-type',
metavar='<volume-type>',
default=None,
help='Volume type (Optional, Default=None)')
@utils.arg(
'--volume_type',
help=argparse.SUPPRESS)
@utils.arg(
'--availability-zone',
metavar='<availability-zone>',
default=None,
help='Availability zone for volume (Optional, Default=None)')
@utils.arg(
'--availability_zone',
help=argparse.SUPPRESS)
@utils.arg('--metadata',
type=str,
nargs='*',
metavar='<key=value>',
help='Metadata key=value pairs (Optional, Default=None)',
default=None)
@utils.service_type('volume')
def do_create(cs, args):
"""Add a new volume."""
volume_metadata = None
if args.metadata is not None:
volume_metadata = _extract_metadata(args)
volume = cs.volumes.create(args.size,
args.snapshot_id,
args.source_volid,
args.display_name,
args.display_description,
args.volume_type,
availability_zone=args.availability_zone,
imageRef=args.image_id,
metadata=volume_metadata)
_print_volume(volume)
@utils.arg('volume', metavar='<volume>', help='ID of the volume to delete.')
@utils.service_type('volume')
def do_delete(cs, args):
"""Remove a volume."""
volume = _find_volume(cs, args.volume)
volume.delete()
@utils.arg('volume', metavar='<volume>', help='ID of the volume to delete.')
@utils.service_type('volume')
def do_force_delete(cs, args):
"""Attempt forced removal of a volume, regardless of its state."""
volume = _find_volume(cs, args.volume)
volume.force_delete()
@utils.arg('volume', metavar='<volume>', help='ID of the volume to rename.')
@utils.arg('display_name', nargs='?', metavar='<display-name>',
help='New display-name for the volume.')
@utils.arg('--display-description', metavar='<display-description>',
help='Optional volume description. (Default=None)',
default=None)
@utils.service_type('volume')
def do_rename(cs, args):
"""Rename a volume."""
kwargs = {}
if args.display_name is not None:
kwargs['display_name'] = args.display_name
if args.display_description is not None:
kwargs['display_description'] = args.display_description
_find_volume(cs, args.volume).update(**kwargs)
@utils.arg('volume',
metavar='<volume>',
help='ID of the volume to update metadata on.')
@utils.arg('action',
metavar='<action>',
choices=['set', 'unset'],
help="Actions: 'set' or 'unset'")
@utils.arg('metadata',
metavar='<key=value>',
nargs='+',
default=[],
help='Metadata to set/unset (only key is necessary on unset)')
@utils.service_type('volume')
def do_metadata(cs, args):
"""Set or Delete metadata on a volume."""
volume = _find_volume(cs, args.volume)
metadata = _extract_metadata(args)
if args.action == 'set':
cs.volumes.set_metadata(volume, metadata)
elif args.action == 'unset':
cs.volumes.delete_metadata(volume, metadata.keys())
@utils.arg(
'--all-tenants',
dest='all_tenants',
metavar='<0|1>',
nargs='?',
type=int,
const=1,
default=0,
help='Display information from all tenants (Admin only).')
@utils.arg(
'--all_tenants',
nargs='?',
type=int,
const=1,
help=argparse.SUPPRESS)
@utils.arg(
'--display-name',
metavar='<display-name>',
default=None,
help='Filter results by display-name')
@utils.arg(
'--status',
metavar='<status>',
default=None,
help='Filter results by status')
@utils.arg(
'--volume-id',
metavar='<volume-id>',
default=None,
help='Filter results by volume-id')
@utils.service_type('volume')
def do_snapshot_list(cs, args):
"""List all the snapshots."""
all_tenants = int(os.environ.get("ALL_TENANTS", args.all_tenants))
search_opts = {
'all_tenants': all_tenants,
'display_name': args.display_name,
'status': args.status,
'volume_id': args.volume_id,
}
snapshots = cs.volume_snapshots.list(search_opts=search_opts)
_translate_volume_snapshot_keys(snapshots)
utils.print_list(snapshots,
['ID', 'Volume ID', 'Status', 'Display Name', 'Size',
'Source Type'])
@utils.arg('snapshot', metavar='<snapshot>', help='ID of the snapshot.')
@utils.service_type('volume')
def do_snapshot_show(cs, args):
"""Show details about a snapshot."""
snapshot = _find_volume_snapshot(cs, args.snapshot)
_print_volume_snapshot(snapshot)
@utils.arg('volume_id',
metavar='<volume-id>',
help='ID of the volume to snapshot')
@utils.arg('--force',
metavar='<True|False>',
help='Optional flag to indicate whether '
'to snapshot a volume even if it\'s '
'attached to an instance. (Default=False)',
default=False)
@utils.arg(
'--display-name',
metavar='<display-name>',
default=None,
help='Optional snapshot name. (Default=None)')
@utils.arg(
'--display_name',
help=argparse.SUPPRESS)
@utils.arg(
'--display-description',
metavar='<display-description>',
default=None,
help='Optional snapshot description. (Default=None)')
@utils.arg(
'--display_description',
help=argparse.SUPPRESS)
@utils.service_type('volume')
def do_snapshot_create(cs, args):
"""Add a new snapshot."""
snapshot = cs.volume_snapshots.create(args.volume_id,
args.force,
args.display_name,
args.display_description)
_print_volume_snapshot(snapshot)
@utils.arg('snapshot_id',
metavar='<snapshot-id>',
help='ID of the snapshot to delete.')
@utils.service_type('volume')
def do_snapshot_delete(cs, args):
"""Remove a snapshot."""
snapshot = _find_volume_snapshot(cs, args.snapshot_id)
snapshot.delete()
@utils.arg('snapshot', metavar='<snapshot>', help='ID of the snapshot.')
@utils.arg('display_name', nargs='?', metavar='<display-name>',
help='New display-name for the snapshot.')
@utils.arg('--display-description', metavar='<display-description>',
help='Optional snapshot description. (Default=None)',
default=None)
@utils.service_type('volume')
def do_snapshot_rename(cs, args):
"""Rename a snapshot."""
kwargs = {}
if args.display_name is not None:
kwargs['display_name'] = args.display_name
if args.display_description is not None:
kwargs['display_description'] = args.display_description
_find_volume_snapshot(cs, args.snapshot).update(**kwargs)
def _print_volume_type_list(vtypes):
utils.print_list(vtypes, ['ID', 'Name'])
def _print_type_and_extra_specs_list(vtypes):
formatters = {'extra_specs': _print_type_extra_specs}
utils.print_list(vtypes, ['ID', 'Name', 'extra_specs'], formatters)
@utils.service_type('volume')
def do_type_list(cs, args):
"""Print a list of available 'volume types'."""
vtypes = cs.volume_types.list()
_print_volume_type_list(vtypes)
@utils.service_type('volume')
def do_extra_specs_list(cs, args):
"""Print a list of current 'volume types and extra specs' (Admin Only)."""
vtypes = cs.volume_types.list()
_print_type_and_extra_specs_list(vtypes)
@utils.arg('name',
metavar='<name>',
help="Name of the new volume type")
@utils.service_type('volume')
def do_type_create(cs, args):
"""Create a new volume type."""
vtype = cs.volume_types.create(args.name)
_print_volume_type_list([vtype])
@utils.arg('id',
metavar='<id>',
help="Unique ID of the volume type to delete")
@utils.service_type('volume')
def do_type_delete(cs, args):
"""Delete a specific volume type"""
cs.volume_types.delete(args.id)
@utils.arg('vtype',
metavar='<vtype>',
help="Name or ID of the volume type")
@utils.arg('action',
metavar='<action>',
choices=['set', 'unset'],
help="Actions: 'set' or 'unset'")
@utils.arg('metadata',
metavar='<key=value>',
nargs='*',
default=None,
help='Extra_specs to set/unset (only key is necessary on unset)')
@utils.service_type('volume')
def do_type_key(cs, args):
"Set or unset extra_spec for a volume type."""
vtype = _find_volume_type(cs, args.vtype)
if args.metadata is not None:
keypair = _extract_metadata(args)
if args.action == 'set':
vtype.set_keys(keypair)
elif args.action == 'unset':
vtype.unset_keys(keypair.keys())
def do_endpoints(cs, args):
"""Discover endpoints that get returned from the authenticate services"""
catalog = cs.client.service_catalog.catalog
for e in catalog['access']['serviceCatalog']:
utils.print_dict(e['endpoints'][0], e['name'])
def do_credentials(cs, args):
"""Show user credentials returned from auth"""
catalog = cs.client.service_catalog.catalog
utils.print_dict(catalog['access']['user'], "User Credentials")
utils.print_dict(catalog['access']['token'], "Token")
_quota_resources = ['volumes', 'snapshots', 'gigabytes']
def _quota_show(quotas):
quota_dict = {}
for resource in _quota_resources:
quota_dict[resource] = getattr(quotas, resource, None)
utils.print_dict(quota_dict)
def _quota_update(manager, identifier, args):
updates = {}
for resource in _quota_resources:
val = getattr(args, resource, None)
if val is not None:
updates[resource] = val
if updates:
manager.update(identifier, **updates)
@utils.arg('tenant', metavar='<tenant_id>',
help='UUID of tenant to list the quotas for.')
@utils.service_type('volume')
def do_quota_show(cs, args):
"""List the quotas for a tenant."""
_quota_show(cs.quotas.get(args.tenant))
@utils.arg('tenant', metavar='<tenant_id>',
help='UUID of tenant to list the default quotas for.')
@utils.service_type('volume')
def do_quota_defaults(cs, args):
"""List the default quotas for a tenant."""
_quota_show(cs.quotas.defaults(args.tenant))
@utils.arg('tenant', metavar='<tenant_id>',
help='UUID of tenant to set the quotas for.')
@utils.arg('--volumes',
metavar='<volumes>',
type=int, default=None,
help='New value for the "volumes" quota.')
@utils.arg('--snapshots',
metavar='<snapshots>',
type=int, default=None,
help='New value for the "snapshots" quota.')
@utils.arg('--gigabytes',
metavar='<gigabytes>',
type=int, default=None,
help='New value for the "gigabytes" quota.')
@utils.service_type('volume')
def do_quota_update(cs, args):
"""Update the quotas for a tenant."""
_quota_update(cs.quotas, args.tenant, args)
@utils.arg('class_name', metavar='<class>',
help='Name of quota class to list the quotas for.')
@utils.service_type('volume')
def do_quota_class_show(cs, args):
"""List the quotas for a quota class."""
_quota_show(cs.quota_classes.get(args.class_name))
@utils.arg('class_name', metavar='<class>',
help='Name of quota class to set the quotas for.')
@utils.arg('--volumes',
metavar='<volumes>',
type=int, default=None,
help='New value for the "volumes" quota.')
@utils.arg('--snapshots',
metavar='<snapshots>',
type=int, default=None,
help='New value for the "snapshots" quota.')
@utils.arg('--gigabytes',
metavar='<gigabytes>',
type=int, default=None,
help='New value for the "gigabytes" quota.')
@utils.service_type('volume')
def do_quota_class_update(cs, args):
"""Update the quotas for a quota class."""
_quota_update(cs.quota_classes, args.class_name, args)
@utils.service_type('volume')
def do_absolute_limits(cs, args):
"""Print a list of absolute limits for a user"""
limits = cs.limits.get().absolute
columns = ['Name', 'Value']
utils.print_list(limits, columns)
@utils.service_type('volume')
def do_rate_limits(cs, args):
"""Print a list of rate limits for a user"""
limits = cs.limits.get().rate
columns = ['Verb', 'URI', 'Value', 'Remain', 'Unit', 'Next_Available']
utils.print_list(limits, columns)
def _print_type_extra_specs(vol_type):
try:
return vol_type.get_keys()
except exceptions.NotFound:
return "N/A"
def _find_volume_type(cs, vtype):
"""Get a volume type by name or ID."""
return utils.find_resource(cs.volume_types, vtype)
@utils.arg('volume_id',
metavar='<volume-id>',
help='ID of the volume to upload to an image')
@utils.arg('--force',
metavar='<True|False>',
help='Optional flag to indicate whether '
'to upload a volume even if it\'s '
'attached to an instance. (Default=False)',
default=False)
@utils.arg('--container-format',
metavar='<container-format>',
help='Optional type for container format '
'(Default=bare)',
default='bare')
@utils.arg('--disk-format',
metavar='<disk-format>',
help='Optional type for disk format '
'(Default=raw)',
default='raw')
@utils.arg('image_name',
metavar='<image-name>',
help='Name for created image')
@utils.service_type('volume')
def do_upload_to_image(cs, args):
"""Upload volume to image service as image."""
volume = _find_volume(cs, args.volume_id)
volume.upload_to_image(args.force,
args.image_name,
args.container_format,
args.disk_format)
@utils.arg('volume', metavar='<volume>',
help='ID of the volume to backup.')
@utils.arg('--container', metavar='<container>',
help='Optional Backup container name. (Default=None)',
default=None)
@utils.arg('--display-name', metavar='<display-name>',
help='Optional backup name. (Default=None)',
default=None)
@utils.arg('--display-description', metavar='<display-description>',
help='Optional backup description. (Default=None)',
default=None)
@utils.service_type('volume')
def do_backup_create(cs, args):
"""Creates a backup."""
cs.backups.create(args.volume,
args.container,
args.display_name,
args.display_description)
@utils.arg('backup', metavar='<backup>', help='ID of the backup.')
@utils.service_type('volume')
def do_backup_show(cs, args):
"""Show details about a backup."""
backup = _find_backup(cs, args.backup)
info = dict()
info.update(backup._info)
if 'links' in info:
info.pop('links')
utils.print_dict(info)
@utils.service_type('volume')
def do_backup_list(cs, args):
"""List all the backups."""
backups = cs.backups.list()
columns = ['ID', 'Volume ID', 'Status', 'Name', 'Size', 'Object Count',
'Container']
utils.print_list(backups, columns)
@utils.arg('backup', metavar='<backup>',
help='ID of the backup to delete.')
@utils.service_type('volume')
def do_backup_delete(cs, args):
"""Remove a backup."""
backup = _find_backup(cs, args.backup)
backup.delete()
@utils.arg('backup', metavar='<backup>',
help='ID of the backup to restore.')
@utils.arg('--volume-id', metavar='<volume-id>',
help='Optional ID of the volume to restore to.',
default=None)
@utils.service_type('volume')
def do_backup_restore(cs, args):
"""Restore a backup."""
cs.restores.restore(args.backup,
args.volume_id)
@utils.arg(
'share_protocol',
metavar='<share_protocol>',
type=str,
help='Share type (NFS or CIFS)')
@utils.arg(
'size',
metavar='<size>',
type=int,
help='Share size in GB')
@utils.arg(
'--snapshot-id',
metavar='<snapshot-id>',
help='Optional snapshot id to create the share from. (Default=None)',
default=None)
@utils.arg(
'--display-name',
metavar='<display-name>',
help='Optional share name. (Default=None)',
default=None)
@utils.arg(
'--display_name',
help=argparse.SUPPRESS)
@utils.arg(
'--display-description',
metavar='<display-description>',
help='Optional share description. (Default=None)',
default=None)
@utils.arg(
'--display_description',
help=argparse.SUPPRESS)
@utils.service_type('volume')
def do_share_create(cs, args):
"""Creates new NAS storage (NFS or CIFS)."""
share = cs.shares.create(args.share_protocol, args.size, args.snapshot_id,
args.display_name, args.display_description)
_print_share(cs, share)
@utils.arg(
'share',
metavar='<share>',
help='ID of the NAS to delete.')
@utils.service_type('volume')
def do_share_delete(cs, args):
"""Deletes NAS storage."""
cs.shares.delete(args.share)
@utils.arg(
'share',
metavar='<share>',
help='ID of the NAS share.')
@utils.service_type('volume')
def do_share_show(cs, args):
"""Show details about a NAS share."""
share = _find_share(cs, args.share)
_print_share(cs, share)
@utils.arg(
'share',
metavar='<share>',
help='ID of the NAS share to modify.')
@utils.arg(
'access_type',
metavar='<access_type>',
help='access rule type (only "ip" is supported).')
@utils.arg(
'access_to',
metavar='<access_to>',
help='Value that defines access')
@utils.service_type('volume')
def do_share_allow(cs, args):
"""Allow access to the share."""
share = _find_share(cs, args.share)
share.allow(args.access_type, args.access_to)
@utils.arg(
'share',
metavar='<share>',
help='ID of the NAS share to modify.')
@utils.arg(
'id',
metavar='<id>',
help='id of the access rule to be deleted.')
@utils.service_type('volume')
def do_share_deny(cs, args):
"""Deny access to a share."""
share = _find_share(cs, args.share)
share.deny(args.id)
@utils.arg(
'share',
metavar='<share>',
help='ID of the share.')
@utils.service_type('volume')
def do_share_access_list(cs, args):
"""Show access list for share."""
share = _find_share(cs, args.share)
access_list = share.access_list()
utils.print_list(access_list, ['id', 'access type', 'access to', 'state'])
@utils.arg(
'--all-tenants',
dest='all_tenants',
metavar='<0|1>',
nargs='?',
type=int,
const=1,
default=0,
help='Display information from all tenants (Admin only).')
@utils.arg(
'--display-name',
metavar='<display-name>',
default=None,
help='Filter results by name')
@utils.arg(
'--status',
metavar='<status>',
default=None,
help='Filter results by status')
@utils.service_type('volume')
def do_share_list(cs, args):
"""List all NAS shares."""
all_tenants = int(os.environ.get("ALL_TENANTS", args.all_tenants))
search_opts = {
'all_tenants': all_tenants,
'display_name': args.display_name,
'status': args.status,
}
shares = cs.shares.list(search_opts=search_opts)
utils.print_list(shares,
['ID', 'Display Name', 'Size', 'Share Proto', 'Status',
'Export location'])
@utils.arg(
'--all-tenants',
dest='all_tenants',
metavar='<0|1>',
nargs='?',
type=int,
const=1,
default=0,
help='Display information from all tenants (Admin only).')
@utils.arg(
'--display-name',
metavar='<display-name>',
default=None,
help='Filter results by name')
@utils.arg(
'--status',
metavar='<status>',
default=None,
help='Filter results by status')
@utils.arg(
'--share-id',
metavar='<share-id>',
default=None,
help='Filter results by share-id')
@utils.service_type('volume')
def do_share_snapshot_list(cs, args):
"""List all the snapshots."""
all_tenants = int(os.environ.get("ALL_TENANTS", args.all_tenants))
search_opts = {
'all_tenants': all_tenants,
'display_name': args.display_name,
'status': args.status,
'share_id': args.share_id,
}
snapshots = cs.share_snapshots.list(search_opts=search_opts)
utils.print_list(snapshots,
['ID', 'Share ID', 'Status', 'Display Name',
'Share Size'])
@utils.arg(
'snapshot',
metavar='<snapshot>',
help='ID of the snapshot.')
@utils.service_type('volume')
def do_share_snapshot_show(cs, args):
"""Show details about a snapshot."""
snapshot = _find_share_snapshot(cs, args.snapshot)
_print_share_snapshot(cs, snapshot)
@utils.arg(
'share_id',
metavar='<share-id>',
help='ID of the share to snapshot')
@utils.arg(
'--force',
metavar='<True|False>',
help='Optional flag to indicate whether '
'to snapshot a share even if it\'s busy.'
' (Default=False)',
default=False)
@utils.arg(
'--display-name',
metavar='<display-name>',
default=None,
help='Optional snapshot name. (Default=None)')
@utils.arg(
'--display-description',
metavar='<display-description>',
default=None,
help='Optional snapshot description. (Default=None)')
@utils.service_type('volume')
def do_share_snapshot_create(cs, args):
"""Add a new snapshot."""
snapshot = cs.share_snapshots.create(args.share_id,
args.force,
args.display_name,
args.display_description)
_print_share_snapshot(cs, snapshot)
@utils.arg(
'snapshot_id',
metavar='<snapshot-id>',
help='ID of the snapshot to delete.')
@utils.service_type('volume')
def do_share_snapshot_delete(cs, args):
"""Remove a snapshot."""
snapshot = _find_share_snapshot(cs, args.snapshot_id)
snapshot.delete()

View File

@ -1,76 +0,0 @@
# Copyright (C) 2013 Hewlett-Packard Development Company, L.P.
# 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.
"""
Volume Backups interface (1.1 extension).
"""
from cinderclient import base
class VolumeBackup(base.Resource):
"""A volume backup is a block level backup of a volume."""
def __repr__(self):
return "<VolumeBackup: %s>" % self.id
def delete(self):
"""Delete this volume backup."""
return self.manager.delete(self)
class VolumeBackupManager(base.ManagerWithFind):
"""Manage :class:`VolumeBackup` resources."""
resource_class = VolumeBackup
def create(self, volume_id, container=None,
name=None, description=None):
"""Create a volume backup.
:param volume_id: The ID of the volume to backup.
:param container: The name of the backup service container.
:param name: The name of the backup.
:param description: The description of the backup.
:rtype: :class:`VolumeBackup`
"""
body = {'backup': {'volume_id': volume_id,
'container': container,
'name': name,
'description': description}}
return self._create('/backups', body, 'backup')
def get(self, backup_id):
"""Show details of a volume backup.
:param backup_id: The ID of the backup to display.
:rtype: :class:`VolumeBackup`
"""
return self._get("/backups/%s" % backup_id, "backup")
def list(self, detailed=True):
"""Get a list of all volume backups.
:rtype: list of :class:`VolumeBackup`
"""
if detailed is True:
return self._list("/backups/detail", "backups")
else:
return self._list("/backups", "backups")
def delete(self, backup):
"""Delete a volume backup.
:param backup: The :class:`VolumeBackup` to delete.
"""
self._delete("/backups/%s" % base.getid(backup))

View File

@ -1,43 +0,0 @@
# Copyright (C) 2013 Hewlett-Packard Development Company, L.P.
# 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.
"""Volume Backups Restore interface (1.1 extension).
This is part of the Volume Backups interface.
"""
from cinderclient import base
class VolumeBackupsRestore(base.Resource):
"""A Volume Backups Restore represents a restore operation."""
def __repr__(self):
return "<VolumeBackupsRestore: %s>" % self.id
class VolumeBackupRestoreManager(base.ManagerWithFind):
"""Manage :class:`VolumeBackupsRestore` resources."""
resource_class = VolumeBackupsRestore
def restore(self, backup_id, volume_id=None):
"""Restore a backup to a volume.
:param backup_id: The ID of the backup to restore.
:param volume_id: The ID of the volume to restore the backup to.
:rtype: :class:`Restore`
"""
body = {'restore': {'volume_id': volume_id}}
return self._create("/backups/%s/restore" % backup_id,
body, "restore")

View File

@ -1,130 +0,0 @@
# Copyright 2011 Denali Systems, Inc.
# 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.
"""
Volume snapshot interface (1.1 extension).
"""
import urllib
from cinderclient import base
class Snapshot(base.Resource):
"""
A Snapshot is a point-in-time snapshot of an openstack volume.
"""
def __repr__(self):
return "<Snapshot: %s>" % self.id
def delete(self):
"""
Delete this snapshot.
"""
self.manager.delete(self)
def update(self, **kwargs):
"""
Update the display_name or display_description for this snapshot.
"""
self.manager.update(self, **kwargs)
@property
def progress(self):
return self._info.get('os-extended-snapshot-attributes:progress')
@property
def project_id(self):
return self._info.get('os-extended-snapshot-attributes:project_id')
class SnapshotManager(base.ManagerWithFind):
"""
Manage :class:`Snapshot` resources.
"""
resource_class = Snapshot
def create(self, volume_id, force=False,
display_name=None, display_description=None):
"""
Create a snapshot of the given volume.
:param volume_id: The ID of the volume to snapshot.
:param force: If force is True, create a snapshot even if the volume is
attached to an instance. Default is False.
:param display_name: Name of the snapshot
:param display_description: Description of the snapshot
:rtype: :class:`Snapshot`
"""
body = {'snapshot': {'volume_id': volume_id,
'force': force,
'display_name': display_name,
'display_description': display_description}}
return self._create('/snapshots', body, 'snapshot')
def get(self, snapshot_id):
"""
Get a snapshot.
:param snapshot_id: The ID of the snapshot to get.
:rtype: :class:`Snapshot`
"""
return self._get("/snapshots/%s" % snapshot_id, "snapshot")
def list(self, detailed=True, search_opts=None):
"""
Get a list of all snapshots.
:rtype: list of :class:`Snapshot`
"""
if search_opts is None:
search_opts = {}
qparams = {}
for opt, val in search_opts.iteritems():
if val:
qparams[opt] = val
query_string = "?%s" % urllib.urlencode(qparams) if qparams else ""
detail = ""
if detailed:
detail = "/detail"
return self._list("/snapshots%s%s" % (detail, query_string),
"snapshots")
def delete(self, snapshot):
"""
Delete a snapshot.
:param snapshot: The :class:`Snapshot` to delete.
"""
self._delete("/snapshots/%s" % base.getid(snapshot))
def update(self, snapshot, **kwargs):
"""
Update the display_name or display_description for a snapshot.
:param snapshot: The :class:`Snapshot` to delete.
"""
if not kwargs:
return
body = {"snapshot": kwargs}
self._update("/snapshots/%s" % base.getid(snapshot), body)

View File

@ -1,122 +0,0 @@
# 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.
"""
Volume Type interface.
"""
from cinderclient import base
class VolumeType(base.Resource):
"""
A Volume Type is the type of volume to be created
"""
def __repr__(self):
return "<VolumeType: %s>" % self.name
def get_keys(self):
"""
Get extra specs from a volume type.
:param vol_type: The :class:`VolumeType` to get extra specs from
"""
_resp, body = self.manager.api.client.get(
"/types/%s/extra_specs" %
base.getid(self))
return body["extra_specs"]
def set_keys(self, metadata):
"""
Set extra specs on a volume type.
:param type : The :class:`VolumeType` 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" % base.getid(self),
body,
"extra_specs",
return_raw=True)
def unset_keys(self, keys):
"""
Unset extra specs on a volue type.
:param type_id: The :class:`VolumeType` 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" % (
base.getid(self), k))
if resp is not None:
return resp
class VolumeTypeManager(base.ManagerWithFind):
"""
Manage :class:`VolumeType` resources.
"""
resource_class = VolumeType
def list(self):
"""
Get a list of all volume types.
:rtype: list of :class:`VolumeType`.
"""
return self._list("/types", "volume_types")
def get(self, volume_type):
"""
Get a specific volume type.
:param volume_type: The ID of the :class:`VolumeType` to get.
:rtype: :class:`VolumeType`
"""
return self._get("/types/%s" % base.getid(volume_type), "volume_type")
def delete(self, volume_type):
"""
Delete a specific volume_type.
:param volume_type: The ID of the :class:`VolumeType` to get.
"""
self._delete("/types/%s" % base.getid(volume_type))
def create(self, name):
"""
Create a volume type.
:param name: Descriptive name of the volume type
:rtype: :class:`VolumeType`
"""
body = {
"volume_type": {
"name": name,
}
}
return self._create("/types", body, "volume_type")

View File

@ -1,328 +0,0 @@
# Copyright 2011 Denali Systems, Inc.
# 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.
"""
Volume interface (1.1 extension).
"""
import urllib
from cinderclient import base
class Volume(base.Resource):
"""A volume is an extra block level storage to the OpenStack instances."""
def __repr__(self):
return "<Volume: %s>" % self.id
def delete(self):
"""Delete this volume."""
self.manager.delete(self)
def update(self, **kwargs):
"""Update the display_name or display_description for this volume."""
self.manager.update(self, **kwargs)
def attach(self, instance_uuid, mountpoint):
"""Set attachment metadata.
:param instance_uuid: uuid of the attaching instance.
:param mountpoint: mountpoint on the attaching instance.
"""
return self.manager.attach(self, instance_uuid, mountpoint)
def detach(self):
"""Clear attachment metadata."""
return self.manager.detach(self)
def reserve(self, volume):
"""Reserve this volume."""
return self.manager.reserve(self)
def unreserve(self, volume):
"""Unreserve this volume."""
return self.manager.unreserve(self)
def begin_detaching(self, volume):
"""Begin detaching volume."""
return self.manager.begin_detaching(self)
def roll_detaching(self, volume):
"""Roll detaching volume."""
return self.manager.roll_detaching(self)
def initialize_connection(self, volume, connector):
"""Initialize a volume connection.
:param connector: connector dict from nova.
"""
return self.manager.initialize_connection(self, connector)
def terminate_connection(self, volume, connector):
"""Terminate a volume connection.
:param connector: connector dict from nova.
"""
return self.manager.terminate_connection(self, connector)
def set_metadata(self, volume, metadata):
"""Set or Append metadata to a volume.
:param type : The :class: `Volume` to set metadata on
:param metadata: A dict of key/value pairs to set
"""
return self.manager.set_metadata(self, metadata)
def upload_to_image(self, force, image_name, container_format,
disk_format):
"""Upload a volume to image service as an image."""
self.manager.upload_to_image(self, force, image_name, container_format,
disk_format)
def force_delete(self):
"""Delete the specified volume ignoring its current state.
:param volume: The UUID of the volume to force-delete.
"""
self.manager.force_delete(self)
class VolumeManager(base.ManagerWithFind):
"""
Manage :class:`Volume` resources.
"""
resource_class = Volume
def create(self, size, snapshot_id=None, source_volid=None,
display_name=None, display_description=None,
volume_type=None, user_id=None,
project_id=None, availability_zone=None,
metadata=None, imageRef=None):
"""
Create a volume.
:param size: Size of volume in GB
:param snapshot_id: ID of the snapshot
:param display_name: Name of the volume
:param display_description: Description of the volume
:param volume_type: Type of volume
:rtype: :class:`Volume`
:param user_id: User id derived from context
:param project_id: Project id derived from context
:param availability_zone: Availability Zone to use
:param metadata: Optional metadata to set on volume creation
:param imageRef: reference to an image stored in glance
:param source_volid: ID of source volume to clone from
"""
if metadata is None:
volume_metadata = {}
else:
volume_metadata = metadata
body = {'volume': {'size': size,
'snapshot_id': snapshot_id,
'display_name': display_name,
'display_description': display_description,
'volume_type': volume_type,
'user_id': user_id,
'project_id': project_id,
'availability_zone': availability_zone,
'status': "creating",
'attach_status': "detached",
'metadata': volume_metadata,
'imageRef': imageRef,
'source_volid': source_volid,
}}
return self._create('/volumes', body, 'volume')
def get(self, volume_id):
"""
Get a volume.
:param volume_id: The ID of the volume to delete.
:rtype: :class:`Volume`
"""
return self._get("/volumes/%s" % volume_id, "volume")
def list(self, detailed=True, search_opts=None):
"""
Get a list of all volumes.
:rtype: list of :class:`Volume`
"""
if search_opts is None:
search_opts = {}
qparams = {}
for opt, val in search_opts.iteritems():
if val:
qparams[opt] = val
query_string = "?%s" % urllib.urlencode(qparams) if qparams else ""
detail = ""
if detailed:
detail = "/detail"
return self._list("/volumes%s%s" % (detail, query_string),
"volumes")
def delete(self, volume):
"""
Delete a volume.
:param volume: The :class:`Volume` to delete.
"""
self._delete("/volumes/%s" % base.getid(volume))
def update(self, volume, **kwargs):
"""
Update the display_name or display_description for a volume.
:param volume: The :class:`Volume` to delete.
"""
if not kwargs:
return
body = {"volume": kwargs}
self._update("/volumes/%s" % base.getid(volume), body)
def _action(self, action, volume, info=None, **kwargs):
"""
Perform a volume "action."
"""
body = {action: info}
self.run_hooks('modify_body_for_action', body, **kwargs)
url = '/volumes/%s/action' % base.getid(volume)
return self.api.client.post(url, body=body)
def attach(self, volume, instance_uuid, mountpoint):
"""
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.
"""
return self._action('os-attach',
volume,
{'instance_uuid': instance_uuid,
'mountpoint': mountpoint})
def detach(self, volume):
"""
Clear attachment metadata.
:param volume: The :class:`Volume` (or its ID)
you would like to detach.
"""
return self._action('os-detach', volume)
def reserve(self, volume):
"""
Reserve this volume.
:param volume: The :class:`Volume` (or its ID)
you would like to reserve.
"""
return self._action('os-reserve', volume)
def unreserve(self, volume):
"""
Unreserve this volume.
:param volume: The :class:`Volume` (or its ID)
you would like to unreserve.
"""
return self._action('os-unreserve', volume)
def begin_detaching(self, volume):
"""
Begin detaching this volume.
:param volume: The :class:`Volume` (or its ID)
you would like to detach.
"""
return self._action('os-begin_detaching', volume)
def roll_detaching(self, volume):
"""
Roll detaching this volume.
:param volume: The :class:`Volume` (or its ID)
you would like to roll detaching.
"""
return self._action('os-roll_detaching', volume)
def initialize_connection(self, volume, connector):
"""
Initialize a volume connection.
:param volume: The :class:`Volume` (or its ID).
:param connector: connector dict from nova.
"""
return self._action('os-initialize_connection', volume,
{'connector': connector})[1]['connection_info']
def terminate_connection(self, volume, connector):
"""
Terminate a volume connection.
:param volume: The :class:`Volume` (or its ID).
:param connector: connector dict from nova.
"""
self._action('os-terminate_connection', volume,
{'connector': connector})
def set_metadata(self, volume, metadata):
"""
Update/Set a volumes metadata.
:param volume: The :class:`Volume`.
:param metadata: A list of keys to be set.
"""
body = {'metadata': metadata}
return self._create("/volumes/%s/metadata" % base.getid(volume),
body, "metadata")
def delete_metadata(self, volume, keys):
"""
Delete specified keys from volumes metadata.
:param volume: The :class:`Volume`.
:param metadata: A list of keys to be removed.
"""
for k in keys:
self._delete("/volumes/%s/metadata/%s" % (base.getid(volume), k))
def upload_to_image(self, volume, force, image_name, container_format,
disk_format):
"""
Upload volume to image service as image.
:param volume: The :class:`Volume` to upload.
"""
return self._action('os-volume_upload_image',
volume,
{'force': force,
'image_name': image_name,
'container_format': container_format,
'disk_format': disk_format})
def force_delete(self, volume):
return self._action('os-force_delete', base.getid(volume))

View File

@ -1,91 +0,0 @@
# 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 extention."""
import os
import urllib
from cinderclient import base
from cinderclient import utils
class ShareSnapshot(base.Resource):
"""Represent a snapshot of a share."""
def __repr__(self):
return "<ShareSnapshot: %s>" % self.id
def delete(self):
"""Delete this snapshot."""
self.manager.delete(self)
class ShareSnapshotManager(base.ManagerWithFind):
"""Manage :class:`ShareSnapshot` resources.
"""
resource_class = ShareSnapshot
def create(self, share_id, 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 = {'share-snapshot': {'share_id': share_id,
'force': force,
'name': name,
'description': description}}
return self._create('/share-snapshots', body, 'share-snapshot')
def get(self, snapshot_id):
"""Get a snapshot.
:param snapshot_id: The ID of the snapshot to get.
:rtype: :class:`ShareSnapshot`
"""
return self._get('/share-snapshots/%s' % snapshot_id, 'share-snapshot')
def list(self, detailed=True, search_opts=None):
"""Get a list of all snapshots of shares.
:rtype: list of :class:`ShareSnapshot`
"""
if search_opts:
query_string = urllib.urlencode([(key, value)
for (key, value)
in search_opts.items()
if value])
if query_string:
query_string = "?%s" % (query_string,)
else:
query_string = ''
if detailed:
path = "/share-snapshots/detail%s" % (query_string,)
else:
path = "/share-snapshots%s" % (query_string,)
return self._list(path, 'share-snapshots')
def delete(self, snapshot):
"""Delete a snapshot of a share.
:param share: The :class:`ShareSnapshot` to delete.
"""
self._delete("/share-snapshots/%s" % base.getid(snapshot))

View File

@ -1,182 +0,0 @@
# 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 extention."""
import collections
import os
import re
import urllib
from cinderclient import base
from cinderclient import exceptions
from cinderclient import utils
class Share(base.Resource):
"""A share is an extra block level storage to the OpenStack instances."""
def __repr__(self):
return "<Share: %s>" % self.id
def delete(self):
"""Delete this share."""
self.manager.delete(self)
def allow(self, access_type, access):
"""Allow access to a share."""
self._validate_access(access_type, access)
return self.manager.allow(self, access_type, access)
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 == 'passwd':
self._validate_username(access)
else:
raise exceptions.CommandError(
'Only ip and passwd type are supported')
@staticmethod
def _validate_username(access):
valid_useraname_re = '\w{4,32}'
username = access
if not re.match(valid_useraname_re, username):
exc_str = _('Invalid user name. Must be alphanum 4-32 chars long')
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.*, 10.0.0.0/24')
if len(ip_range) > 2:
raise exceptions.CommandError(exc_str)
allow_asterisk = (len(ip_range) == 1)
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:
if not (allow_asterisk and item == '*'):
raise exceptions.CommandError(exc_str)
class ShareManager(base.ManagerWithFind):
"""Manage :class:`Share` resources."""
resource_class = Share
def create(self, share_proto, size, snapshot_id=None, name=None,
description=None):
"""Create NAS.
:param size: Size of NAS in GB
:param snapshot_id: ID of the snapshot
:param name: Name of the NAS
:param description: Short description of a share
:param share_proto: Type of NAS (NFS or CIFS)
:rtype: :class:`Share`
"""
body = {'share': {'size': size,
'snapshot_id': snapshot_id,
'name': name,
'description': description,
'share_proto': share_proto}}
return self._create('/shares', body, 'share')
def get(self, share_id):
"""Get a share.
:param share_id: The ID of the share to delete.
:rtype: :class:`Share`
"""
return self._get("/shares/%s" % share_id, "share")
def list(self, detailed=True, search_opts=None):
"""Get a list of all shares.
:rtype: list of :class:`Share`
"""
if search_opts:
query_string = urllib.urlencode([(key, value)
for (key, value)
in search_opts.items()
if value])
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):
"""Delete a share.
:param share: The :class:`Share` to delete.
"""
self._delete("/shares/%s" % base.getid(share))
def allow(self, share, access_type, access):
"""Allow access from IP to a shares.
:param share: The :class:`Share` to delete.
:param access_type: string that represents access type ('ip','domain')
:param access: string that represents access ('127.0.0.1')
"""
return self._action('os-allow_access', share,
{'access_type': access_type,
'access_to': access})
def deny(self, share, id):
"""Deny access from IP to a shares.
:param share: The :class:`Share` to delete.
:param ip: string that represents ip address
"""
return self._action('os-deny_access', share, {'access_id': id})
def access_list(self, share):
"""Get access list to the share."""
access_list = self._action("os-access_list", share)[1]["access_list"]
if access_list:
t = collections.namedtuple('Access', access_list[0].keys())
return [t(*value.values()) for value in access_list]
else:
return []
def _action(self, action, share, info=None, **kwargs):
"""Perform a share 'action'."""
body = {action: info}
self.run_hooks('modify_body_for_action', body, **kwargs)
url = '/shares/%s/action' % base.getid(share)
return self.api.client.post(url, body=body)
#########################

File diff suppressed because it is too large Load Diff

View File

@ -1,76 +0,0 @@
# Copyright (C) 2013 Hewlett-Packard Development Company, L.P.
# 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.
"""
Volume Backups interface (1.1 extension).
"""
from cinderclient import base
class VolumeBackup(base.Resource):
"""A volume backup is a block level backup of a volume."""
def __repr__(self):
return "<VolumeBackup: %s>" % self.id
def delete(self):
"""Delete this volume backup."""
return self.manager.delete(self)
class VolumeBackupManager(base.ManagerWithFind):
"""Manage :class:`VolumeBackup` resources."""
resource_class = VolumeBackup
def create(self, volume_id, container=None,
name=None, description=None):
"""Create a volume backup.
:param volume_id: The ID of the volume to backup.
:param container: The name of the backup service container.
:param name: The name of the backup.
:param description: The description of the backup.
:rtype: :class:`VolumeBackup`
"""
body = {'backup': {'volume_id': volume_id,
'container': container,
'name': name,
'description': description}}
return self._create('/backups', body, 'backup')
def get(self, backup_id):
"""Show details of a volume backup.
:param backup_id: The ID of the backup to display.
:rtype: :class:`VolumeBackup`
"""
return self._get("/backups/%s" % backup_id, "backup")
def list(self, detailed=True):
"""Get a list of all volume backups.
:rtype: list of :class:`VolumeBackup`
"""
if detailed is True:
return self._list("/backups/detail", "backups")
else:
return self._list("/backups", "backups")
def delete(self, backup):
"""Delete a volume backup.
:param backup: The :class:`VolumeBackup` to delete.
"""
self._delete("/backups/%s" % base.getid(backup))

View File

@ -1,43 +0,0 @@
# Copyright (C) 2013 Hewlett-Packard Development Company, L.P.
# 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.
"""Volume Backups Restore interface (1.1 extension).
This is part of the Volume Backups interface.
"""
from cinderclient import base
class VolumeBackupsRestore(base.Resource):
"""A Volume Backups Restore represents a restore operation."""
def __repr__(self):
return "<VolumeBackupsRestore: %s>" % self.id
class VolumeBackupRestoreManager(base.ManagerWithFind):
"""Manage :class:`VolumeBackupsRestore` resources."""
resource_class = VolumeBackupsRestore
def restore(self, backup_id, volume_id=None):
"""Restore a backup to a volume.
:param backup_id: The ID of the backup to restore.
:param volume_id: The ID of the volume to restore the backup to.
:rtype: :class:`Restore`
"""
body = {'restore': {'volume_id': volume_id}}
return self._create("/backups/%s/restore" % backup_id,
body, "restore")

View File

@ -1,116 +0,0 @@
# 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.
"""Volume snapshot interface (1.1 extension)."""
import urllib
from cinderclient import base
class Snapshot(base.Resource):
"""A Snapshot is a point-in-time snapshot of an openstack volume."""
def __repr__(self):
return "<Snapshot: %s>" % self.id
def delete(self):
"""Delete this snapshot."""
self.manager.delete(self)
def update(self, **kwargs):
"""Update the name or description for this snapshot."""
self.manager.update(self, **kwargs)
@property
def progress(self):
return self._info.get('os-extended-snapshot-attributes:progress')
@property
def project_id(self):
return self._info.get('os-extended-snapshot-attributes:project_id')
class SnapshotManager(base.ManagerWithFind):
"""Manage :class:`Snapshot` resources."""
resource_class = Snapshot
def create(self, volume_id, force=False,
name=None, description=None):
"""Create a snapshot of the given volume.
:param volume_id: The ID of the volume to snapshot.
:param force: If force is True, create a snapshot even if the volume is
attached to an instance. Default is False.
:param name: Name of the snapshot
:param description: Description of the snapshot
:rtype: :class:`Snapshot`
"""
body = {'snapshot': {'volume_id': volume_id,
'force': force,
'name': name,
'description': description}}
return self._create('/snapshots', body, 'snapshot')
def get(self, snapshot_id):
"""Get a snapshot.
:param snapshot_id: The ID of the snapshot to get.
:rtype: :class:`Snapshot`
"""
return self._get("/snapshots/%s" % snapshot_id, "snapshot")
def list(self, detailed=True, search_opts=None):
"""Get a list of all snapshots.
:rtype: list of :class:`Snapshot`
"""
if search_opts is None:
search_opts = {}
qparams = {}
for opt, val in search_opts.iteritems():
if val:
qparams[opt] = val
query_string = "?%s" % urllib.urlencode(qparams) if qparams else ""
detail = ""
if detailed:
detail = "/detail"
return self._list("/snapshots%s%s" % (detail, query_string),
"snapshots")
def delete(self, snapshot):
"""Delete a snapshot.
:param snapshot: The :class:`Snapshot` to delete.
"""
self._delete("/snapshots/%s" % base.getid(snapshot))
def update(self, snapshot, **kwargs):
"""Update the name or description for a snapshot.
:param snapshot: The :class:`Snapshot` to delete.
"""
if not kwargs:
return
body = {"snapshot": kwargs}
self._update("/snapshots/%s" % base.getid(snapshot), body)

View File

@ -1,108 +0,0 @@
# Copyright 2013 OpenStack LLC.
#
# 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.
"""Volume Type interface."""
from cinderclient import base
class VolumeType(base.Resource):
"""A Volume Type is the type of volume to be created."""
def __repr__(self):
return "<VolumeType: %s>" % self.name
def get_keys(self):
"""Get extra specs from a volume type.
:param vol_type: The :class:`VolumeType` to get extra specs from
"""
_resp, body = self.manager.api.client.get(
"/types/%s/extra_specs" %
base.getid(self))
return body["extra_specs"]
def set_keys(self, metadata):
"""Set extra specs on a volume type.
:param type : The :class:`VolumeType` 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" % base.getid(self),
body,
"extra_specs",
return_raw=True)
def unset_keys(self, keys):
"""Unset extra specs on a volue type.
:param type_id: The :class:`VolumeType` 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
for k in keys:
resp = self.manager._delete(
"/types/%s/extra_specs/%s" % (
base.getid(self), k))
if resp is not None:
return resp
class VolumeTypeManager(base.ManagerWithFind):
"""Manage :class:`VolumeType` resources."""
resource_class = VolumeType
def list(self):
"""Get a list of all volume types.
:rtype: list of :class:`VolumeType`.
"""
return self._list("/types", "volume_types")
def get(self, volume_type):
"""Get a specific volume type.
:param volume_type: The ID of the :class:`VolumeType` to get.
:rtype: :class:`VolumeType`
"""
return self._get("/types/%s" % base.getid(volume_type), "volume_type")
def delete(self, volume_type):
"""Delete a specific volume_type.
:param volume_type: The ID of the :class:`VolumeType` to get.
"""
self._delete("/types/%s" % base.getid(volume_type))
def create(self, name):
"""Create a volume type.
:param name: Descriptive name of the volume type
:rtype: :class:`VolumeType`
"""
body = {
"volume_type": {
"name": name,
}
}
return self._create("/types", body, "volume_type")

View File

@ -1,308 +0,0 @@
# 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.
"""Volume interface (v2 extension)."""
import urllib
from cinderclient import base
class Volume(base.Resource):
"""A volume is an extra block level storage to the OpenStack instances."""
def __repr__(self):
return "<Volume: %s>" % self.id
def delete(self):
"""Delete this volume."""
self.manager.delete(self)
def update(self, **kwargs):
"""Update the name or description for this volume."""
self.manager.update(self, **kwargs)
def attach(self, instance_uuid, mountpoint):
"""Set attachment metadata.
:param instance_uuid: uuid of the attaching instance.
:param mountpoint: mountpoint on the attaching instance.
"""
return self.manager.attach(self, instance_uuid, mountpoint)
def detach(self):
"""Clear attachment metadata."""
return self.manager.detach(self)
def reserve(self, volume):
"""Reserve this volume."""
return self.manager.reserve(self)
def unreserve(self, volume):
"""Unreserve this volume."""
return self.manager.unreserve(self)
def begin_detaching(self, volume):
"""Begin detaching volume."""
return self.manager.begin_detaching(self)
def roll_detaching(self, volume):
"""Roll detaching volume."""
return self.manager.roll_detaching(self)
def initialize_connection(self, volume, connector):
"""Initialize a volume connection.
:param connector: connector dict from nova.
"""
return self.manager.initialize_connection(self, connector)
def terminate_connection(self, volume, connector):
"""Terminate a volume connection.
:param connector: connector dict from nova.
"""
return self.manager.terminate_connection(self, connector)
def set_metadata(self, volume, metadata):
"""Set or Append metadata to a volume.
:param type : The :class: `Volume` to set metadata on
:param metadata: A dict of key/value pairs to set
"""
return self.manager.set_metadata(self, metadata)
def upload_to_image(self, force, image_name, container_format,
disk_format):
"""Upload a volume to image service as an image."""
self.manager.upload_to_image(self, force, image_name, container_format,
disk_format)
def force_delete(self):
"""Delete the specified volume ignoring its current state.
:param volume: The UUID of the volume to force-delete.
"""
self.manager.force_delete(self)
class VolumeManager(base.ManagerWithFind):
"""Manage :class:`Volume` resources."""
resource_class = Volume
def create(self, size, snapshot_id=None, source_volid=None,
name=None, description=None,
volume_type=None, user_id=None,
project_id=None, availability_zone=None,
metadata=None, imageRef=None):
"""Create a volume.
:param size: Size of volume in GB
:param snapshot_id: ID of the snapshot
:param name: Name of the volume
:param description: Description of the volume
:param volume_type: Type of volume
:rtype: :class:`Volume`
:param user_id: User id derived from context
:param project_id: Project id derived from context
:param availability_zone: Availability Zone to use
:param metadata: Optional metadata to set on volume creation
:param imageRef: reference to an image stored in glance
:param source_volid: ID of source volume to clone from
"""
if metadata is None:
volume_metadata = {}
else:
volume_metadata = metadata
body = {'volume': {'size': size,
'snapshot_id': snapshot_id,
'name': name,
'description': description,
'volume_type': volume_type,
'user_id': user_id,
'project_id': project_id,
'availability_zone': availability_zone,
'status': "creating",
'attach_status': "detached",
'metadata': volume_metadata,
'imageRef': imageRef,
'source_volid': source_volid,
}}
return self._create('/volumes', body, 'volume')
def get(self, volume_id):
"""Get a volume.
:param volume_id: The ID of the volume to delete.
:rtype: :class:`Volume`
"""
return self._get("/volumes/%s" % volume_id, "volume")
def list(self, detailed=True, search_opts=None):
"""Get a list of all volumes.
:rtype: list of :class:`Volume`
"""
if search_opts is None:
search_opts = {}
qparams = {}
for opt, val in search_opts.iteritems():
if val:
qparams[opt] = val
query_string = "?%s" % urllib.urlencode(qparams) if qparams else ""
detail = ""
if detailed:
detail = "/detail"
return self._list("/volumes%s%s" % (detail, query_string),
"volumes")
def delete(self, volume):
"""Delete a volume.
:param volume: The :class:`Volume` to delete.
"""
self._delete("/volumes/%s" % base.getid(volume))
def update(self, volume, **kwargs):
"""Update the name or description for a volume.
:param volume: The :class:`Volume` to delete.
"""
if not kwargs:
return
body = {"volume": kwargs}
self._update("/volumes/%s" % base.getid(volume), body)
def _action(self, action, volume, info=None, **kwargs):
"""Perform a volume "action."
"""
body = {action: info}
self.run_hooks('modify_body_for_action', body, **kwargs)
url = '/volumes/%s/action' % base.getid(volume)
return self.api.client.post(url, body=body)
def attach(self, volume, instance_uuid, mountpoint):
"""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.
"""
return self._action('os-attach',
volume,
{'instance_uuid': instance_uuid,
'mountpoint': mountpoint})
def detach(self, volume):
"""Clear attachment metadata.
:param volume: The :class:`Volume` (or its ID)
you would like to detach.
"""
return self._action('os-detach', volume)
def reserve(self, volume):
"""Reserve this volume.
:param volume: The :class:`Volume` (or its ID)
you would like to reserve.
"""
return self._action('os-reserve', volume)
def unreserve(self, volume):
"""Unreserve this volume.
:param volume: The :class:`Volume` (or its ID)
you would like to unreserve.
"""
return self._action('os-unreserve', volume)
def begin_detaching(self, volume):
"""Begin detaching this volume.
:param volume: The :class:`Volume` (or its ID)
you would like to detach.
"""
return self._action('os-begin_detaching', volume)
def roll_detaching(self, volume):
"""Roll detaching this volume.
:param volume: The :class:`Volume` (or its ID)
you would like to roll detaching.
"""
return self._action('os-roll_detaching', volume)
def initialize_connection(self, volume, connector):
"""Initialize a volume connection.
:param volume: The :class:`Volume` (or its ID).
:param connector: connector dict from nova.
"""
return self._action('os-initialize_connection', volume,
{'connector': connector})[1]['connection_info']
def terminate_connection(self, volume, connector):
"""Terminate a volume connection.
:param volume: The :class:`Volume` (or its ID).
:param connector: connector dict from nova.
"""
self._action('os-terminate_connection', volume,
{'connector': connector})
def set_metadata(self, volume, metadata):
"""Update/Set a volumes metadata.
:param volume: The :class:`Volume`.
:param metadata: A list of keys to be set.
"""
body = {'metadata': metadata}
return self._create("/volumes/%s/metadata" % base.getid(volume),
body, "metadata")
def delete_metadata(self, volume, keys):
"""Delete specified keys from volumes metadata.
:param volume: The :class:`Volume`.
:param metadata: A list of keys to be removed.
"""
for k in keys:
self._delete("/volumes/%s/metadata/%s" % (base.getid(volume), k))
def upload_to_image(self, volume, force, image_name, container_format,
disk_format):
"""Upload volume to image service as image.
:param volume: The :class:`Volume` to upload.
"""
return self._action('os-volume_upload_image',
volume,
{'force': force,
'image_name': image_name,
'container_format': container_format,
'disk_format': disk_format})
def force_delete(self, volume):
return self._action('os-force_delete', base.getid(volume))

View File

@ -62,9 +62,9 @@ qthelp:
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/python-cinderclient.qhcp"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/python-manilaclient.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-cinderclient.qhc"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-manilaclient.qhc"
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# python-cinderclient documentation build configuration file, created by
# python-manilaclient documentation build configuration file, created by
# sphinx-quickstart on Sun Dec 6 14:19:25 2009.
#
# This file is execfile()d with current directory set to its containing dir.
@ -42,7 +42,7 @@ source_suffix = '.rst'
master_doc = 'index'
# General information about the project.
project = u'python-cinderclient'
project = u'python-manilaclient'
copyright = u'Rackspace, based on work by Jacob Kaplan-Moss'
# The version info for the project you're documenting, acts as replacement for
@ -164,7 +164,7 @@ html_static_path = ['_static']
#html_file_suffix = ''
# Output file base name for HTML help builder.
htmlhelp_basename = 'python-cinderclientdoc'
htmlhelp_basename = 'python-manilaclientdoc'
# -- Options for LaTeX output -------------------------------------------------
@ -179,7 +179,7 @@ htmlhelp_basename = 'python-cinderclientdoc'
# (source start file, target name, title, author, documentclass [howto/manual])
# .
latex_documents = [
('index', 'python-cinderclient.tex', u'python-cinderclient Documentation',
('index', 'python-manilaclient.tex', u'python-manilaclient Documentation',
u'Rackspace - based on work by Jacob Kaplan-Moss', 'manual'),
]

View File

@ -2,14 +2,14 @@ Python API
==========
In order to use the python api directly, you must first obtain an auth token and identify which endpoint you wish to speak to. Once you have done so, you can use the API like so::
>>> from cinderclient import client
>>> cinder = client.Client('1', $OS_USER_NAME, $OS_PASSWORD, $OS_TENANT_NAME, $OS_AUTH_URL)
>>> cinder.volumes.list()
>>> from manilaclient import client
>>> manila = client.Client('1', $OS_USER_NAME, $OS_PASSWORD, $OS_TENANT_NAME, $OS_AUTH_URL)
>>> manila.volumes.list()
[]
>>> myvol = cinder.volumes.create(display_name="test-vol", size=1)
>>> myvol = manila.volumes.create(display_name="test-vol", size=1)
>>> myvol.id
ce06d0a8-5c1b-4e2c-81d2-39eca6bbfb70
>>> cinder.volumes.list()
>>> manila.volumes.list()
[<Volume: ce06d0a8-5c1b-4e2c-81d2-39eca6bbfb70>]
>>>myvol.delete
@ -22,7 +22,7 @@ In order to use the CLI, you must provide your OpenStack username, password, ten
export OS_TENANT_ID=b363706f891f48019483f8bd6503c54b
export OS_AUTH_URL=http://auth.example.com:5000/v2.0
Once you've configured your authentication parameters, you can run ``cinder help`` to see a complete listing of available commands.
Once you've configured your authentication parameters, you can run ``manila help`` to see a complete listing of available commands.
Release Notes
@ -30,31 +30,31 @@ Release Notes
1.0.4
-----
* Added suport for backup-service commands
.. _1163546: http://bugs.launchpad.net/python-cinderclient/+bug/1163546
.. _1161857: http://bugs.launchpad.net/python-cinderclient/+bug/1161857
.. _1160898: http://bugs.launchpad.net/python-cinderclient/+bug/1160898
.. _1161857: http://bugs.launchpad.net/python-cinderclient/+bug/1161857
.. _1156994: http://bugs.launchpad.net/python-cinderclient/+bug/1156994
.. _1163546: http://bugs.launchpad.net/python-manilaclient/+bug/1163546
.. _1161857: http://bugs.launchpad.net/python-manilaclient/+bug/1161857
.. _1160898: http://bugs.launchpad.net/python-manilaclient/+bug/1160898
.. _1161857: http://bugs.launchpad.net/python-manilaclient/+bug/1161857
.. _1156994: http://bugs.launchpad.net/python-manilaclient/+bug/1156994
1.0.3
-----
* Added support for V2 Cinder API
* Added support for V2 Manila API
* Corected upload-volume-to-image help messaging
* Align handling of metadata args for all methods
* Update OSLO version
* Correct parsing of volume metadata
* Enable force delete of volumes and snapshots in error state
* Implement clone volume API call
* Add list-extensions call to cinderclient
* Add list-extensions call to manilaclient
* Add bootable column to list output
* Add retries to cinderclient operations
* Add retries to manilaclient operations
* Add Type/Extra-Specs support
* Add volume and snapshot rename commands
.. _1155655: http://bugs.launchpad.net/python-cinderclient/+bug/1155655
.. _1130730: http://bugs.launchpad.net/python-cinderclient/+bug/1130730
.. _1068521: http://bugs.launchpad.net/python-cinderclient/+bug/1068521
.. _1052161: http://bugs.launchpad.net/python-cinderclient/+bug/1052161
.. _1071003: http://bugs.launchpad.net/python-cinderclient/+bug/1071003
.. _1065275: http://bugs.launchpad.net/python-cinderclient/+bug/1065275
.. _1053432: http://bugs.launchpad.net/python-cinderclient/+bug/1053432
.. _1155655: http://bugs.launchpad.net/python-manilaclient/+bug/1155655
.. _1130730: http://bugs.launchpad.net/python-manilaclient/+bug/1130730
.. _1068521: http://bugs.launchpad.net/python-manilaclient/+bug/1068521
.. _1052161: http://bugs.launchpad.net/python-manilaclient/+bug/1052161
.. _1071003: http://bugs.launchpad.net/python-manilaclient/+bug/1071003
.. _1065275: http://bugs.launchpad.net/python-manilaclient/+bug/1065275
.. _1053432: http://bugs.launchpad.net/python-manilaclient/+bug/1053432

View File

@ -1,30 +1,30 @@
The :program:`cinder` shell utility
The :program:`manila` shell utility
=========================================
.. program:: cinder
.. program:: manila
.. highlight:: bash
The :program:`cinder` shell utility interacts with the OpenStack Cinder API
from the command line. It supports the entirety of the OpenStack Cinder API.
The :program:`manila` shell utility interacts with the OpenStack Manila API
from the command line. It supports the entirety of the OpenStack Manila API.
You'll need to provide :program:`cinder` with your OpenStack username and
You'll need to provide :program:`manila` with your OpenStack username and
API key. You can do this with the :option:`--os-username`, :option:`--os-password`
and :option:`--os-tenant-name` options, but it's easier to just set them as
environment variables by setting two environment variables:
.. envvar:: OS_USERNAME or CINDER_USERNAME
.. envvar:: OS_USERNAME or MANILA_USERNAME
Your OpenStack Cinder username.
Your OpenStack Manila username.
.. envvar:: OS_PASSWORD or CINDER_PASSWORD
.. envvar:: OS_PASSWORD or MANILA_PASSWORD
Your password.
.. envvar:: OS_TENANT_NAME or CINDER_PROJECT_ID
.. envvar:: OS_TENANT_NAME or MANILA_PROJECT_ID
Project for work.
.. envvar:: OS_AUTH_URL or CINDER_URL
.. envvar:: OS_AUTH_URL or MANILA_URL
The OpenStack API server URL.
@ -42,8 +42,8 @@ For example, in Bash you'd use::
From there, all shell commands take the form::
cinder <command> [arguments...]
manila <command> [arguments...]
Run :program:`cinder help` to get a full list of all possible commands,
and run :program:`cinder help <command>` to get detailed help for that
Run :program:`manila help` to get a full list of all possible commands,
and run :program:`manila help <command>` to get detailed help for that
command.

View File

@ -14,9 +14,9 @@
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.openstack.common import version
from manilaclient.openstack.common import version
version_info = version.VersionInfo('python-cinderclient')
version_info = version.VersionInfo('python-manilaclient')
# We have a circular import problem when we first run python setup.py sdist
# It's harmless, so deflect it.
try:

View File

@ -22,8 +22,8 @@ Base utilities to build API operation managers and objects on top of.
import contextlib
import hashlib
import os
from cinderclient import exceptions
from cinderclient import utils
from manilaclient import exceptions
from manilaclient import utils
# Python 2.4 compat
@ -92,13 +92,13 @@ class Manager(utils.HookableMixin):
Delete is not handled because listings are assumed to be performed
often enough to keep the cache reasonably up-to-date.
"""
base_dir = utils.env('CINDERCLIENT_UUID_CACHE_DIR',
default="~/.cinderclient")
base_dir = utils.env('manilaclient_UUID_CACHE_DIR',
default="~/.manilaclient")
# NOTE(sirp): Keep separate UUID caches for each username + endpoint
# pair
username = utils.env('OS_USERNAME', 'CINDER_USERNAME')
url = utils.env('OS_URL', 'CINDER_URL')
username = utils.env('OS_USERNAME', 'MANILA_USERNAME')
url = utils.env('OS_URL', 'MANILA_URL')
uniqifier = hashlib.md5(username + url).hexdigest()
cache_dir = os.path.expanduser(os.path.join(base_dir, uniqifier))

View File

@ -27,21 +27,33 @@ if not hasattr(urlparse, 'parse_qsl'):
import requests
from cinderclient import exceptions
from cinderclient import service_catalog
from cinderclient import utils
from manilaclient import exceptions
from manilaclient import service_catalog
from manilaclient import utils
class HTTPClient(object):
USER_AGENT = 'python-cinderclient'
USER_AGENT = 'python-manilaclient'
def __init__(self, user, password, projectid, auth_url, insecure=False,
timeout=None, tenant_id=None, proxy_tenant_id=None,
proxy_token=None, region_name=None,
endpoint_type='publicURL', service_type=None,
service_name=None, volume_service_name=None, retries=None,
http_log_debug=False, cacert=None):
def __init__(self,
user,
password,
projectid,
auth_url,
insecure=False,
timeout=None,
tenant_id=None,
proxy_tenant_id=None,
proxy_token=None,
region_name=None,
endpoint_type='publicURL',
service_type=None,
service_name=None,
share_service_name=None,
retries=None,
http_log_debug=False,
cacert=None):
self.user = user
self.password = password
self.projectid = projectid
@ -52,7 +64,7 @@ class HTTPClient(object):
self.endpoint_type = endpoint_type
self.service_type = service_type
self.service_name = service_name
self.volume_service_name = volume_service_name
self.share_service_name = share_service_name
self.retries = int(retries or 0)
self.http_log_debug = http_log_debug
@ -212,7 +224,7 @@ class HTTPClient(object):
endpoint_type=self.endpoint_type,
service_type=self.service_type,
service_name=self.service_name,
volume_service_name=self.volume_service_name)
share_service_name=self.share_service_name)
self.management_url = management_url.rstrip('/')
return None
except exceptions.AmbiguousEndpoints:
@ -272,7 +284,7 @@ class HTTPClient(object):
auth_url = self.auth_url
if self.version == "v2.0":
while auth_url:
if "CINDER_RAX_AUTH" in os.environ:
if "MANILA_RAX_AUTH" in os.environ:
auth_url = self._rax_auth(auth_url)
else:
auth_url = self._v2_auth(auth_url)
@ -290,7 +302,7 @@ class HTTPClient(object):
try:
while auth_url:
auth_url = self._v1_auth(auth_url)
# In some configurations cinder makes redirection to
# In some configurations manila makes redirection to
# v2.0 keystone endpoint. Also, new location does not contain
# real endpoint, only hostname and port.
except exceptions.AuthorizationFailure:
@ -360,8 +372,8 @@ class HTTPClient(object):
def get_client_class(version):
version_map = {
'1': 'cinderclient.v1.client.Client',
'2': 'cinderclient.v2.client.Client',
'1': 'manilaclient.v1.client.Client',
'2': 'manilaclient.v2.client.Client',
}
try:
client_path = version_map[str(version)]

View File

@ -13,8 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient import base
from cinderclient import utils
from manilaclient import base
from manilaclient import utils
class Extension(utils.HookableMixin):

View File

@ -322,7 +322,7 @@ def _get_version_from_git(pre_version):
else:
return _run_shell_command(
"git --git-dir=" + git_dir + " describe --always").replace(
'-', '.')
'-', '.')
return None
@ -342,6 +342,26 @@ def _get_version_from_pkg_info(package_name):
return pkg_info.get('Version', None)
def _get_defined_version():
MANILA_CLIENT_VERSION = ['2013', '3', 0]
FINAL = False # This becomes true at Release Candidate time
def canonical_version_string():
return '.'.join(filter(None, MANILA_CLIENT_VERSION))
def version_string():
if FINAL:
return canonical_version_string()
else:
return '%s-dev' % (canonical_version_string(),)
return version_string()
def version_string_with_vcs():
return '%s-%s' % (canonical_version_string(), vcs_version_string())
def get_version(package_name, pre_version=None):
"""Get the version of the project. First, try getting it from PKG-INFO, if
it exists. If it does, that means we're in a distribution tarball or that
@ -360,6 +380,11 @@ def get_version(package_name, pre_version=None):
version = _get_version_from_pkg_info(package_name)
if version:
return version
version = _get_defined_version()
if version:
return version
version = _get_version_from_git(pre_version)
if version:
return version

View File

@ -52,7 +52,7 @@ class VersionInfo(object):
# The most likely cause for this is running tests in a tree
# produced from a tarball where the package itself has not been
# installed into anything. Revert to setup-time logic.
from cinderclient.openstack.common import setup
from manilaclient.openstack.common import setup
return setup.get_version(self.package)
def release_string(self):

View File

@ -16,7 +16,7 @@
# limitations under the License.
import cinderclient.exceptions
import manilaclient.exceptions
class ServiceCatalog(object):
@ -30,7 +30,7 @@ class ServiceCatalog(object):
def url_for(self, attr=None, filter_value=None,
service_type=None, endpoint_type='publicURL',
service_name=None, volume_service_name=None):
service_name=None, share_service_name=None):
"""Fetch the public URL from the Compute service for
a particular endpoint attribute. If none given, return
the first. See tests for sample service catalog."""
@ -41,7 +41,7 @@ class ServiceCatalog(object):
if not filter_value or endpoint[attr] == filter_value:
matching_endpoints.append(endpoint)
if not matching_endpoints:
raise cinderclient.exceptions.EndpointNotFound()
raise manilaclient.exceptions.EndpointNotFound()
# We don't always get a service catalog back ...
if not 'serviceCatalog' in self.catalog['access']:
@ -58,8 +58,8 @@ class ServiceCatalog(object):
service.get('name') != service_name):
continue
if (volume_service_name and service_type == 'volume' and
service.get('name') != volume_service_name):
if (share_service_name and service_type == 'share' and
service.get('name') != share_service_name):
continue
endpoints = service['endpoints']
@ -69,9 +69,9 @@ class ServiceCatalog(object):
matching_endpoints.append(endpoint)
if not matching_endpoints:
raise cinderclient.exceptions.EndpointNotFound()
raise manilaclient.exceptions.EndpointNotFound()
elif len(matching_endpoints) > 1:
raise cinderclient.exceptions.AmbiguousEndpoints(
raise manilaclient.exceptions.AmbiguousEndpoints(
endpoints=matching_endpoints)
else:
return matching_endpoints[0][endpoint_type]

View File

@ -15,7 +15,7 @@
# under the License.
"""
Command-line interface to the OpenStack Cinder API.
Command-line interface to the OpenStack Manila API.
"""
import argparse
@ -27,13 +27,13 @@ import pkgutil
import sys
import logging
from cinderclient import client
from cinderclient import exceptions as exc
import cinderclient.extension
from cinderclient.openstack.common import strutils
from cinderclient import utils
from cinderclient.v1 import shell as shell_v1
from cinderclient.v2 import shell as shell_v2
from manilaclient import client
from manilaclient import exceptions as exc
import manilaclient.extension
from manilaclient.openstack.common import strutils
from manilaclient import utils
from manilaclient.v1 import shell as shell_v1
# from manilaclient.v2 import shell as shell_v2
DEFAULT_OS_SHARE_API_VERSION = "1"
DEFAULT_MANILA_ENDPOINT_TYPE = 'publicURL'
@ -42,10 +42,10 @@ DEFAULT_MANILA_SERVICE_TYPE = 'share'
logger = logging.getLogger(__name__)
class CinderClientArgumentParser(argparse.ArgumentParser):
class ManilaClientArgumentParser(argparse.ArgumentParser):
def __init__(self, *args, **kwargs):
super(CinderClientArgumentParser, self).__init__(*args, **kwargs)
super(ManilaClientArgumentParser, self).__init__(*args, **kwargs)
def error(self, message):
"""error(message: string)
@ -64,13 +64,13 @@ class CinderClientArgumentParser(argparse.ArgumentParser):
'subp': progparts[2]})
class OpenStackCinderShell(object):
class OpenStackManilaShell(object):
def get_base_parser(self):
parser = CinderClientArgumentParser(
prog='cinder',
parser = ManilaClientArgumentParser(
prog='manila',
description=__doc__.strip(),
epilog='See "cinder help COMMAND" '
epilog='See "manila help COMMAND" '
'for help on a specific command.',
add_help=False,
formatter_class=OpenStackHelpFormatter,
@ -83,18 +83,18 @@ class OpenStackCinderShell(object):
parser.add_argument('--version',
action='version',
version=cinderclient.__version__)
version=manilaclient.__version__)
parser.add_argument('--debug',
action='store_true',
default=utils.env('CINDERCLIENT_DEBUG',
default=utils.env('manilaclient_DEBUG',
default=False),
help="Print debugging output")
parser.add_argument('--os-username',
metavar='<auth-user-name>',
default=utils.env('OS_USERNAME',
'CINDER_USERNAME'),
'MANILA_USERNAME'),
help='Defaults to env[OS_USERNAME].')
parser.add_argument('--os_username',
help=argparse.SUPPRESS)
@ -102,7 +102,7 @@ class OpenStackCinderShell(object):
parser.add_argument('--os-password',
metavar='<auth-password>',
default=utils.env('OS_PASSWORD',
'CINDER_PASSWORD'),
'MANILA_PASSWORD'),
help='Defaults to env[OS_PASSWORD].')
parser.add_argument('--os_password',
help=argparse.SUPPRESS)
@ -110,7 +110,7 @@ class OpenStackCinderShell(object):
parser.add_argument('--os-tenant-name',
metavar='<auth-tenant-name>',
default=utils.env('OS_TENANT_NAME',
'CINDER_PROJECT_ID'),
'MANILA_PROJECT_ID'),
help='Defaults to env[OS_TENANT_NAME].')
parser.add_argument('--os_tenant_name',
help=argparse.SUPPRESS)
@ -118,7 +118,7 @@ class OpenStackCinderShell(object):
parser.add_argument('--os-tenant-id',
metavar='<auth-tenant-id>',
default=utils.env('OS_TENANT_ID',
'CINDER_TENANT_ID'),
'MANILA_TENANT_ID'),
help='Defaults to env[OS_TENANT_ID].')
parser.add_argument('--os_tenant_id',
help=argparse.SUPPRESS)
@ -126,7 +126,7 @@ class OpenStackCinderShell(object):
parser.add_argument('--os-auth-url',
metavar='<auth-url>',
default=utils.env('OS_AUTH_URL',
'CINDER_URL'),
'MANILA_URL'),
help='Defaults to env[OS_AUTH_URL].')
parser.add_argument('--os_auth_url',
help=argparse.SUPPRESS)
@ -134,7 +134,7 @@ class OpenStackCinderShell(object):
parser.add_argument('--os-region-name',
metavar='<region-name>',
default=utils.env('OS_REGION_NAME',
'CINDER_REGION_NAME'),
'MANILA_REGION_NAME'),
help='Defaults to env[OS_REGION_NAME].')
parser.add_argument('--os_region_name',
help=argparse.SUPPRESS)
@ -147,34 +147,36 @@ class OpenStackCinderShell(object):
parser.add_argument('--service-name',
metavar='<service-name>',
default=utils.env('CINDER_SERVICE_NAME'),
help='Defaults to env[CINDER_SERVICE_NAME]')
default=utils.env('MANILA_SERVICE_NAME'),
help='Defaults to env[MANILA_SERVICE_NAME]')
parser.add_argument('--service_name',
help=argparse.SUPPRESS)
parser.add_argument('--volume-service-name',
metavar='<volume-service-name>',
default=utils.env('CINDER_VOLUME_SERVICE_NAME'),
help='Defaults to env[CINDER_VOLUME_SERVICE_NAME]')
parser.add_argument('--volume_service_name',
parser.add_argument('--share-service-name',
metavar='<share-service-name>',
default=utils.env('MANILA_share_service_name'),
help='Defaults to env[MANILA_share_service_name]')
parser.add_argument('--share_service_name',
help=argparse.SUPPRESS)
parser.add_argument('--endpoint-type',
metavar='<endpoint-type>',
default=utils.env('CINDER_ENDPOINT_TYPE',
default=DEFAULT_MANILA_ENDPOINT_TYPE),
help='Defaults to env[CINDER_ENDPOINT_TYPE] or '
default=utils.env(
'MANILA_ENDPOINT_TYPE',
default=DEFAULT_MANILA_ENDPOINT_TYPE),
help='Defaults to env[MANILA_ENDPOINT_TYPE] or '
+ DEFAULT_MANILA_ENDPOINT_TYPE + '.')
parser.add_argument('--endpoint_type',
help=argparse.SUPPRESS)
parser.add_argument('--os-volume-api-version',
parser.add_argument('--os-share-api-version',
metavar='<compute-api-ver>',
default=utils.env('OS_VOLUME_API_VERSION',
default=DEFAULT_OS_SHARE_API_VERSION),
default=utils.env(
'OS_SHARE_API_VERSION',
default=DEFAULT_OS_SHARE_API_VERSION),
help='Accepts 1 or 2,defaults '
'to env[OS_VOLUME_API_VERSION].')
parser.add_argument('--os_volume_api_version',
'to env[OS_SHARE_API_VERSION].')
parser.add_argument('--os_share_api_version',
help=argparse.SUPPRESS)
parser.add_argument('--os-cacert',
@ -185,7 +187,7 @@ class OpenStackCinderShell(object):
'Defaults to env[OS_CACERT]')
parser.add_argument('--insecure',
default=utils.env('CINDERCLIENT_INSECURE',
default=utils.env('manilaclient_INSECURE',
default=False),
action='store_true',
help=argparse.SUPPRESS)
@ -209,17 +211,17 @@ class OpenStackCinderShell(object):
# alias for --os-password, left in for backwards compatibility
parser.add_argument('--apikey', '--password', dest='apikey',
default=utils.env('CINDER_API_KEY'),
default=utils.env('MANILA_API_KEY'),
help=argparse.SUPPRESS)
# alias for --os-tenant-name, left in for backward compatibility
parser.add_argument('--projectid', '--tenant_name', dest='projectid',
default=utils.env('CINDER_PROJECT_ID'),
default=utils.env('MANILA_PROJECT_ID'),
help=argparse.SUPPRESS)
# alias for --os-auth-url, left in for backward compatibility
parser.add_argument('--url', '--auth_url', dest='url',
default=utils.env('CINDER_URL'),
default=utils.env('MANILA_URL'),
help=argparse.SUPPRESS)
return parser
@ -233,7 +235,6 @@ class OpenStackCinderShell(object):
try:
actions_module = {
'1.1': shell_v1,
'2': shell_v2,
}[version]
except KeyError:
actions_module = shell_v1
@ -254,14 +255,14 @@ class OpenStackCinderShell(object):
self._discover_via_python_path(version),
self._discover_via_contrib_path(version)):
extension = cinderclient.extension.Extension(name, module)
extension = manilaclient.extension.Extension(name, module)
extensions.append(extension)
return extensions
def _discover_via_python_path(self, version):
for (module_loader, name, ispkg) in pkgutil.iter_modules():
if name.endswith('python_cinderclient_ext'):
if name.endswith('python_manilaclient_ext'):
if not hasattr(module_loader, 'load_module'):
# Python 2.6 compat: actually get an ImpImporter obj
module_loader = module_loader.find_module(name)
@ -336,11 +337,11 @@ class OpenStackCinderShell(object):
# build available subcommands based on version
self.extensions = self._discover_extensions(
options.os_volume_api_version)
options.os_share_api_version)
self._run_extension_hooks('__pre_parse_args__')
subcommand_parser = self.get_subcommand_parser(
options.os_volume_api_version)
options.os_share_api_version)
self.parser = subcommand_parser
if options.help or not argv:
@ -360,16 +361,14 @@ class OpenStackCinderShell(object):
(os_username, os_password, os_tenant_name, os_auth_url,
os_region_name, os_tenant_id, endpoint_type, insecure,
service_type, service_name, volume_service_name,
service_type, service_name, share_service_name,
username, apikey, projectid, url, region_name, cacert) = (
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.volume_service_name, args.username,
args.apikey, args.projectid,
args.url, args.region_name, args.os_cacert)
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.username,
args.apikey, args.projectid, args.url, args.region_name,
args.os_cacert)
if not endpoint_type:
endpoint_type = DEFAULT_MANILA_ENDPOINT_TYPE
@ -427,7 +426,7 @@ class OpenStackCinderShell(object):
"You must provide an auth url "
"via either --os-auth-url or env[OS_AUTH_URL]")
self.cs = client.Client(options.os_volume_api_version, os_username,
self.cs = client.Client(options.os_share_api_version, os_username,
os_password, os_tenant_name, os_auth_url,
insecure, region_name=os_region_name,
tenant_id=os_tenant_id,
@ -435,7 +434,7 @@ class OpenStackCinderShell(object):
extensions=self.extensions,
service_type=service_type,
service_name=service_name,
volume_service_name=volume_service_name,
share_service_name=share_service_name,
retries=options.retries,
http_log_debug=args.debug,
cacert=cacert)
@ -444,7 +443,7 @@ class OpenStackCinderShell(object):
if not utils.isunauthenticated(args.func):
self.cs.authenticate()
except exc.Unauthorized:
raise exc.CommandError("Invalid OpenStack Cinder credentials.")
raise exc.CommandError("Invalid OpenStack Manila credentials.")
except exc.AuthorizationFailure:
raise exc.CommandError("Unable to authorize user")
@ -459,7 +458,7 @@ class OpenStackCinderShell(object):
"""Print arguments for bash_completion.
Prints all of the commands and options to stdout so that the
cinder.bash_completion script doesn't have to hard code them.
manila.bash_completion script doesn't have to hard code them.
"""
commands = set()
options = set()
@ -498,9 +497,9 @@ class OpenStackHelpFormatter(argparse.HelpFormatter):
def main():
try:
OpenStackCinderShell().main(map(strutils.safe_decode, sys.argv[1:]))
OpenStackManilaShell().main(map(strutils.safe_decode, sys.argv[1:]))
except KeyboardInterrupt:
print >> sys.stderr, "... terminating cinder client"
print >> sys.stderr, "... terminating manila client"
sys.exit(130)
except Exception, e:
logger.debug(e, exc_info=1)

View File

@ -5,8 +5,8 @@ import uuid
import prettytable
from cinderclient import exceptions
from cinderclient.openstack.common import strutils
from manilaclient import exceptions
from manilaclient.openstack.common import strutils
def arg(*args, **kwargs):
@ -103,7 +103,7 @@ def service_type(stype):
"""
Adds 'service_type' attribute to decorated function.
Usage:
@service_type('volume')
@service_type('share')
def mymethod(f):
...
"""
@ -143,8 +143,7 @@ def print_list(objs, fields, formatters={}):
row.append(data)
pt.add_row(row)
if len(objs) > 0:
print strutils.safe_encode(pt.get_string(sortby=fields[0]))
print strutils.safe_encode(pt.get_string(sortby=fields[0]))
def print_dict(d, property="Property"):

View File

@ -14,4 +14,4 @@
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient.v2.client import Client
from manilaclient.v1.client import Client

View File

@ -1,14 +1,9 @@
from cinderclient import client
from cinderclient.v2 import limits
from cinderclient.v2 import quota_classes
from cinderclient.v2 import quotas
from cinderclient.v2 import shares
from cinderclient.v2 import share_snapshots
from cinderclient.v2 import volumes
from cinderclient.v2 import volume_snapshots
from cinderclient.v2 import volume_types
from cinderclient.v2 import volume_backups
from cinderclient.v2 import volume_backups_restore
from manilaclient import client
from manilaclient.v1 import limits
from manilaclient.v1 import quota_classes
from manilaclient.v1 import quotas
from manilaclient.v1 import shares
from manilaclient.v1 import share_snapshots
class Client(object):
@ -20,7 +15,7 @@ class Client(object):
Then call methods on its managers::
>>> client.volumes.list()
>>> client.share.list()
...
"""
@ -28,8 +23,8 @@ class Client(object):
insecure=False, timeout=None, tenant_id=None,
proxy_tenant_id=None, proxy_token=None, region_name=None,
endpoint_type='publicURL', extensions=None,
service_type='volume', service_name=None,
volume_service_name=None, retries=None,
service_type='share', service_name=None,
share_service_name=None, retries=None,
http_log_debug=False,
cacert=None):
# FIXME(comstud): Rename the api_key argument above when we
@ -37,14 +32,9 @@ class Client(object):
password = api_key
self.limits = limits.LimitsManager(self)
# extensions
self.volumes = volumes.VolumeManager(self)
self.volume_snapshots = volume_snapshots.SnapshotManager(self)
self.volume_types = volume_types.VolumeTypeManager(self)
self.quota_classes = quota_classes.QuotaClassSetManager(self)
self.quotas = quotas.QuotaSetManager(self)
self.backups = volume_backups.VolumeBackupManager(self)
self.restores = volume_backups_restore.VolumeBackupRestoreManager(self)
self.shares = shares.ShareManager(self)
self.share_snapshots = share_snapshots.ShareSnapshotManager(self)
@ -69,7 +59,7 @@ class Client(object):
endpoint_type=endpoint_type,
service_type=service_type,
service_name=service_name,
volume_service_name=volume_service_name,
share_service_name=share_service_name,
retries=retries,
http_log_debug=http_log_debug,
cacert=cacert)

View File

@ -13,8 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient import base
from cinderclient import utils
from manilaclient import base
from manilaclient import utils
class ListExtResource(base.Resource):
@ -37,7 +37,7 @@ class ListExtManager(base.Manager):
return self._list("/extensions", 'extensions')
@utils.service_type('volume')
@utils.service_type('share')
def do_list_extensions(client, _args):
"""
List all the os-api extensions that are available.

View File

@ -1,6 +1,6 @@
# Copyright 2013 OpenStack LLC.
from cinderclient import base
from manilaclient import base
class Limits(base.Resource):

View File

@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient import base
from manilaclient import base
class QuotaClassSet(base.Resource):
@ -36,12 +36,12 @@ class QuotaClassSetManager(base.ManagerWithFind):
def update(self,
class_name,
volumes=None,
shares=None,
gigabytes=None):
body = {'quota_class_set': {
'class_name': class_name,
'volumes': volumes,
'shares': shares,
'gigabytes': gigabytes}}
for key in body['quota_class_set'].keys():

View File

@ -13,7 +13,7 @@
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient import base
from manilaclient import base
class QuotaSet(base.Resource):
@ -35,11 +35,11 @@ class QuotaSetManager(base.ManagerWithFind):
tenant_id = tenant_id.tenant_id
return self._get("/os-quota-sets/%s" % (tenant_id), "quota_set")
def update(self, tenant_id, volumes=None, snapshots=None, gigabytes=None):
def update(self, tenant_id, shares=None, snapshots=None, gigabytes=None):
body = {'quota_set': {
'tenant_id': tenant_id,
'volumes': volumes,
'shares': shares,
'snapshots': snapshots,
'gigabytes': gigabytes}}

View File

@ -17,8 +17,8 @@
import os
import urllib
from cinderclient import base
from cinderclient import utils
from manilaclient import base
from manilaclient import utils
class ShareSnapshot(base.Resource):
@ -47,11 +47,11 @@ class ShareSnapshotManager(base.ManagerWithFind):
:param description: Description of the snapshot
:rtype: :class:`ShareSnapshot`
"""
body = {'share-snapshot': {'share_id': share_id,
'force': force,
'name': name,
'description': description}}
return self._create('/share-snapshots', body, 'share-snapshot')
body = {'snapshot': {'share_id': share_id,
'force': force,
'name': name,
'description': description}}
return self._create('/snapshots', body, 'snapshot')
def get(self, snapshot_id):
"""Get a snapshot.
@ -59,7 +59,7 @@ class ShareSnapshotManager(base.ManagerWithFind):
:param snapshot_id: The ID of the snapshot to get.
:rtype: :class:`ShareSnapshot`
"""
return self._get('/share-snapshots/%s' % snapshot_id, 'share-snapshot')
return self._get('/snapshots/%s' % snapshot_id, 'snapshot')
def list(self, detailed=True, search_opts=None):
"""Get a list of all snapshots of shares.
@ -77,15 +77,15 @@ class ShareSnapshotManager(base.ManagerWithFind):
query_string = ''
if detailed:
path = "/share-snapshots/detail%s" % (query_string,)
path = "/snapshots/detail%s" % (query_string,)
else:
path = "/share-snapshots%s" % (query_string,)
path = "/snapshots%s" % (query_string,)
return self._list(path, 'share-snapshots')
return self._list(path, 'snapshots')
def delete(self, snapshot):
"""Delete a snapshot of a share.
:param share: The :class:`ShareSnapshot` to delete.
"""
self._delete("/share-snapshots/%s" % base.getid(snapshot))
self._delete("/snapshots/%s" % base.getid(snapshot))

View File

@ -19,9 +19,9 @@ import os
import re
import urllib
from cinderclient import base
from cinderclient import exceptions
from cinderclient import utils
from manilaclient import base
from manilaclient import exceptions
from manilaclient import utils
class Share(base.Resource):
@ -150,9 +150,10 @@ class ShareManager(base.ManagerWithFind):
:param access_type: string that represents access type ('ip','domain')
:param access: string that represents access ('127.0.0.1')
"""
return self._action('os-allow_access', share,
{'access_type': access_type,
'access_to': access})
access = self._action('os-allow_access', share,
{'access_type': access_type,
'access_to': access})[1]["access"]
return access
def deny(self, share, id):
"""Deny access from IP to a shares.
@ -177,6 +178,3 @@ class ShareManager(base.ManagerWithFind):
self.run_hooks('modify_body_for_action', body, **kwargs)
url = '/shares/%s/action' % base.getid(share)
return self.api.client.post(url, body=body)
#########################

474
manilaclient/v1/shell.py Normal file
View File

@ -0,0 +1,474 @@
# 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.
import argparse
import os
import sys
import time
from manilaclient import exceptions
from manilaclient import utils
def _poll_for_status(poll_fn, obj_id, action, final_ok_states,
poll_period=5, show_progress=True):
"""Block while action is performed, periodically printing progress."""
def print_progress(progress):
if show_progress:
msg = ('\rInstance %(action)s... %(progress)s%% complete'
% dict(action=action, progress=progress))
else:
msg = '\rInstance %(action)s...' % dict(action=action)
sys.stdout.write(msg)
sys.stdout.flush()
print
while True:
obj = poll_fn(obj_id)
status = obj.status.lower()
progress = getattr(obj, 'progress', None) or 0
if status in final_ok_states:
print_progress(100)
print "\nFinished"
break
elif status == "error":
print "\nError %(action)s instance" % locals()
break
else:
print_progress(progress)
time.sleep(poll_period)
def _find_share(cs, share):
"""Get a share by ID."""
return utils.find_resource(cs.shares, share)
def _print_share(cs, share):
info = share._info.copy()
utils.print_dict(info)
def _find_share_snapshot(cs, snapshot):
"""Get a snapshot by ID."""
return utils.find_resource(cs.share_snapshots, snapshot)
def _print_share_snapshot(cs, snapshot):
info = snapshot._info.copy()
info.pop('links')
utils.print_dict(info)
def _translate_keys(collection, convert):
for item in collection:
keys = item.__dict__.keys()
for from_key, to_key in convert:
if from_key in keys and to_key not in keys:
setattr(item, to_key, item._info[from_key])
def _extract_metadata(args):
metadata = {}
for metadatum in args.metadata[0]:
# unset doesn't require a val, so we have the if/else
if '=' in metadatum:
(key, value) = metadatum.split('=', 1)
else:
key = metadatum
value = None
metadata[key] = value
return metadata
def do_endpoints(cs, args):
"""Discover endpoints that get returned from the authenticate services"""
catalog = cs.client.service_catalog.catalog
for e in catalog['access']['serviceCatalog']:
utils.print_dict(e['endpoints'][0], e['name'])
def do_credentials(cs, args):
"""Show user credentials returned from auth"""
catalog = cs.client.service_catalog.catalog
utils.print_dict(catalog['access']['user'], "User Credentials")
utils.print_dict(catalog['access']['token'], "Token")
_quota_resources = ['shares', 'snapshots', 'gigabytes']
def _quota_show(quotas):
quota_dict = {}
for resource in _quota_resources:
quota_dict[resource] = getattr(quotas, resource, None)
utils.print_dict(quota_dict)
def _quota_update(manager, identifier, args):
updates = {}
for resource in _quota_resources:
val = getattr(args, resource, None)
if val is not None:
updates[resource] = val
if updates:
manager.update(identifier, **updates)
@utils.arg('tenant',
metavar='<tenant_id>',
help='UUID of tenant to list the quotas for.')
@utils.service_type('share')
def do_quota_show(cs, args):
"""List the quotas for a tenant."""
_quota_show(cs.quotas.get(args.tenant))
@utils.arg('tenant',
metavar='<tenant_id>',
help='UUID of tenant to list the default quotas for.')
@utils.service_type('share')
def do_quota_defaults(cs, args):
"""List the default quotas for a tenant."""
_quota_show(cs.quotas.defaults(args.tenant))
@utils.arg('tenant',
metavar='<tenant_id>',
help='UUID of tenant to set the quotas for.')
@utils.arg('--shares',
metavar='<shares>',
type=int, default=None,
help='New value for the "shares" quota.')
@utils.arg('--snapshots',
metavar='<snapshots>',
type=int, default=None,
help='New value for the "snapshots" quota.')
@utils.arg('--gigabytes',
metavar='<gigabytes>',
type=int, default=None,
help='New value for the "gigabytes" quota.')
@utils.service_type('share')
def do_quota_update(cs, args):
"""Update the quotas for a tenant."""
_quota_update(cs.quotas, args.tenant, args)
@utils.arg('class_name',
metavar='<class>',
help='Name of quota class to list the quotas for.')
@utils.service_type('share')
def do_quota_class_show(cs, args):
"""List the quotas for a quota class."""
_quota_show(cs.quota_classes.get(args.class_name))
@utils.arg('class-name',
metavar='<class-name>',
help='Name of quota class to set the quotas for.')
@utils.arg('--shares',
metavar='<shares>',
type=int, default=None,
help='New value for the "shares" quota.')
@utils.arg('--snapshots',
metavar='<snapshots>',
type=int, default=None,
help='New value for the "snapshots" quota.')
@utils.arg('--gigabytes',
metavar='<gigabytes>',
type=int, default=None,
help='New value for the "gigabytes" quota.')
@utils.service_type('share')
def do_quota_class_update(cs, args):
"""Update the quotas for a quota class."""
_quota_update(cs.quota_classes, args.class_name, args)
@utils.service_type('share')
def do_absolute_limits(cs, args):
"""Print a list of absolute limits for a user"""
limits = cs.limits.get().absolute
columns = ['Name', 'Value']
utils.print_list(limits, columns)
@utils.service_type('share')
def do_rate_limits(cs, args):
"""Print a list of rate limits for a user"""
limits = cs.limits.get().rate
columns = ['Verb', 'URI', 'Value', 'Remain', 'Unit', 'Next_Available']
utils.print_list(limits, columns)
@utils.arg(
'share_protocol',
metavar='<share_protocol>',
type=str,
help='Share type (NFS or CIFS)')
@utils.arg(
'size',
metavar='<size>',
type=int,
help='Share size in GB')
@utils.arg(
'--snapshot-id',
metavar='<snapshot-id>',
help='Optional snapshot id to create the share from. (Default=None)',
default=None)
@utils.arg(
'--name',
metavar='<name>',
help='Optional share name. (Default=None)',
default=None)
@utils.arg(
'--description',
metavar='<description>',
help='Optional share description. (Default=None)',
default=None)
@utils.service_type('share')
def do_create(cs, args):
"""Creates new NAS storage (NFS or CIFS)."""
share = cs.shares.create(args.share_protocol, args.size, args.snapshot_id,
args.name, args.description)
_print_share(cs, share)
@utils.arg(
'share',
metavar='<share>',
help='ID of the NAS to delete.')
@utils.service_type('share')
def do_delete(cs, args):
"""Deletes NAS storage."""
cs.shares.delete(args.share)
@utils.arg(
'share',
metavar='<share>',
help='ID of the NAS share.')
@utils.service_type('share')
def do_show(cs, args):
"""Show details about a NAS share."""
share = _find_share(cs, args.share)
_print_share(cs, share)
@utils.arg(
'share',
metavar='<share>',
help='ID of the NAS share to modify.')
@utils.arg(
'access_type',
metavar='<access_type>',
help='access rule type (only "ip" is supported).')
@utils.arg(
'access_to',
metavar='<access_to>',
help='Value that defines access')
@utils.service_type('share')
def do_allow_access(cs, args):
"""Allow access to the share."""
share = _find_share(cs, args.share)
access = share.allow(args.access_type, args.access_to)
utils.print_dict(access)
@utils.arg(
'share',
metavar='<share>',
help='ID of the NAS share to modify.')
@utils.arg(
'id',
metavar='<id>',
help='id of the access rule to be deleted.')
@utils.service_type('share')
def do_deny_access(cs, args):
"""Deny access to a share."""
share = _find_share(cs, args.share)
share.deny(args.id)
@utils.arg(
'share',
metavar='<share>',
help='ID of the share.')
@utils.service_type('share')
def do_access_list(cs, args):
"""Show access list for share."""
share = _find_share(cs, args.share)
access_list = share.access_list()
utils.print_list(access_list, ['id', 'access type', 'access to', 'state'])
@utils.arg(
'--all-tenants',
dest='all_tenants',
metavar='<0|1>',
nargs='?',
type=int,
const=1,
default=0,
help='Display information from all tenants (Admin only).')
@utils.arg(
'--name',
metavar='<name>',
default=None,
help='Filter results by name')
@utils.arg(
'--status',
metavar='<status>',
default=None,
help='Filter results by status')
@utils.service_type('share')
def do_list(cs, args):
"""List all NAS shares."""
all_tenants = int(os.environ.get("ALL_TENANTS", args.all_tenants))
search_opts = {
'all_tenants': all_tenants,
'name': args.name,
'status': args.status,
}
shares = cs.shares.list(search_opts=search_opts)
utils.print_list(shares,
['ID', 'Name', 'Size', 'Share Proto', 'Status',
'Export location'])
@utils.arg(
'--all-tenants',
dest='all_tenants',
metavar='<0|1>',
nargs='?',
type=int,
const=1,
default=0,
help='Display information from all tenants (Admin only).')
@utils.arg(
'--name',
metavar='<name>',
default=None,
help='Filter results by name')
@utils.arg(
'--status',
metavar='<status>',
default=None,
help='Filter results by status')
@utils.arg(
'--share-id',
metavar='<share-id>',
default=None,
help='Filter results by share-id')
@utils.service_type('share')
def do_snapshot_list(cs, args):
"""List all the snapshots."""
all_tenants = int(os.environ.get("ALL_TENANTS", args.all_tenants))
search_opts = {
'all_tenants': all_tenants,
'name': args.name,
'status': args.status,
'share_id': args.share_id,
}
snapshots = cs.share_snapshots.list(search_opts=search_opts)
utils.print_list(snapshots,
['ID', 'Share ID', 'Status', 'Name', 'Share Size'])
@utils.arg(
'snapshot',
metavar='<snapshot>',
help='ID of the snapshot.')
@utils.service_type('share')
def do_snapshot_show(cs, args):
"""Show details about a snapshot."""
snapshot = _find_share_snapshot(cs, args.snapshot)
_print_share_snapshot(cs, snapshot)
@utils.arg(
'share_id',
metavar='<share-id>',
help='ID of the share to snapshot')
@utils.arg(
'--force',
metavar='<True|False>',
help='Optional flag to indicate whether '
'to snapshot a share even if it\'s busy.'
' (Default=False)',
default=False)
@utils.arg(
'--name',
metavar='<name>',
default=None,
help='Optional snapshot name. (Default=None)')
@utils.arg(
'--description',
metavar='<description>',
default=None,
help='Optional snapshot description. (Default=None)')
@utils.service_type('share')
def do_snapshot_create(cs, args):
"""Add a new snapshot."""
snapshot = cs.share_snapshots.create(args.share_id,
args.force,
args.name,
args.description)
_print_share_snapshot(cs, snapshot)
# @utils.arg('share',
# metavar='<share>',
# help='ID of the share to rename.')
# @utils.arg('name',
# nargs='?',
# metavar='<name>',
# help='New name for the share.')
# @utils.arg('--description', metavar='<description>',
# help='Optional share description. (Default=None)',
# default=None)
# @utils.arg('--display-description',
# help=argparse.SUPPRESS)
# @utils.arg('--display_description',
# help=argparse.SUPPRESS)
# @utils.service_type('share')
# def do_rename(cs, args):
# """Rename a share."""
# kwargs = {}
#
# if args.name is not None:
# kwargs['name'] = args.name
# if args.display_description is not None:
# kwargs['description'] = args.display_description
# elif args.description is not None:
# kwargs['description'] = args.description
#
# _find_share(cs, args.share).update(**kwargs)
@utils.arg(
'snapshot_id',
metavar='<snapshot-id>',
help='ID of the snapshot to delete.')
@utils.service_type('share')
def do_snapshot_delete(cs, args):
"""Remove a snapshot."""
snapshot = _find_share_snapshot(cs, args.snapshot_id)
snapshot.delete()

View File

@ -4,4 +4,4 @@
modules=setup,version,strutils
# The base module to hold the copy of openstack.common
base=cinderclient
base=manilaclient

View File

@ -4,7 +4,7 @@ set -eu
function usage {
echo "Usage: $0 [OPTION]..."
echo "Run python-cinderclient test suite"
echo "Run python-manilaclient test suite"
echo ""
echo " -V, --virtual-env Always use virtualenv. Install automatically if not present"
echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment"
@ -94,7 +94,7 @@ function run_tests {
if [ "x$testrargs" = "x" ]; then
testrargs="^(?!.*test_coverage_ext).*$"
fi
export PYTHON="${wrapper} coverage run --source cinderclient --parallel-mode"
export PYTHON="${wrapper} coverage run --source manilaclient --parallel-mode"
fi
# Just run the test suites in current environment
set +e
@ -118,7 +118,7 @@ function copy_subunit_log {
function run_pep8 {
echo "Running pep8 ..."
srcfiles="cinderclient tests"
srcfiles="manilaclient tests"
# Just run PEP8 in current environment
#
# NOTE(sirp): W602 (deprecated 3-arg raise) is being ignored for the
@ -191,5 +191,5 @@ fi
if [ $coverage -eq 1 ]; then
echo "Generating coverage report in covhtml/"
${wrapper} coverage combine
${wrapper} coverage html --include='cinderclient/*' --omit='cinderclient/openstack/common/*' -d covhtml -i
${wrapper} coverage html --include='manilaclient/*' --omit='manilaclient/openstack/common/*' -d covhtml -i
fi

View File

@ -16,12 +16,12 @@ import os
import setuptools
from cinderclient.openstack.common import setup
from manilaclient.openstack.common import setup
requires = setup.parse_requirements()
depend_links = setup.parse_dependency_links()
tests_require = setup.parse_requirements(['tools/test-requires'])
project = 'python-cinderclient'
project = 'python-manilaclient'
def read_file(file_name):
@ -33,10 +33,10 @@ setuptools.setup(
version=setup.get_version(project),
author="OpenStack Contributors",
author_email="openstack-dev@lists.openstack.org",
description="Client library for OpenStack Cinder API.",
description="Client library for OpenStack Manila API.",
long_description=read_file("README.rst"),
license="Apache License, Version 2.0",
url="https://github.com/openstack/python-cinderclient",
url="https://github.com/openstack/python-manilaclient",
packages=setuptools.find_packages(exclude=['tests', 'tests.*']),
cmdclass=setup.get_cmdclass(),
install_requires=requires,
@ -55,6 +55,6 @@ setuptools.setup(
"Programming Language :: Python"
],
entry_points={
"console_scripts": ["cinder = cinderclient.shell:main"]
"console_scripts": ["manila = manilaclient.shell:main"]
}
)

View File

@ -0,0 +1 @@
__author__ = 'vkostenko'

View File

@ -0,0 +1,36 @@
# # Copyright (c) 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 extension
# from manilaclient.v2.contrib import list_extensions
# from tests import utils
# from tests.v1 import fakes
#
#
# extensions = [
# extension.Extension(list_extensions.__name__.split(".")[-1],
# list_extensions),
# ]
# cs = fakes.FakeClient(extensions=extensions)
#
#
# class ListExtensionsTests(utils.TestCase):
# def test_list_extensions(self):
# all_exts = cs.list_extensions.show_all()
# cs.assert_called('GET', '/extensions')
# self.assertTrue(len(all_exts) > 0)
# for r in all_exts:
# self.assertTrue(len(r.summary) > 0)

View File

@ -1,6 +1,6 @@
from cinderclient import base
from cinderclient import exceptions
from cinderclient.v1 import volumes
from manilaclient import base
from manilaclient import exceptions
from manilaclient.v1 import shares
from tests import utils
from tests.v1 import fakes
@ -29,7 +29,7 @@ class BaseTest(utils.TestCase):
# Two resoruces of different types: never equal
r1 = base.Resource(None, {'id': 1})
r2 = volumes.Volume(None, {'id': 1})
r2 = shares.Share(None, {'id': 1})
self.assertNotEqual(r1, r2)
# Two resources with no ID: equal if their info is equal
@ -40,9 +40,9 @@ class BaseTest(utils.TestCase):
def test_findall_invalid_attribute(self):
# Make sure findall with an invalid attribute doesn't cause errors.
# The following should not raise an exception.
cs.volumes.findall(vegetable='carrot')
cs.shares.findall(vegetable='carrot')
# However, find() should raise an error
self.assertRaises(exceptions.NotFound,
cs.volumes.find,
cs.shares.find,
vegetable='carrot')

View File

@ -1,20 +1,16 @@
import cinderclient.client
import cinderclient.v1.client
import cinderclient.v2.client
import manilaclient.client
import manilaclient.v1.client
from tests import utils
class ClientTest(utils.TestCase):
def test_get_client_class_v1(self):
output = cinderclient.client.get_client_class('1')
self.assertEqual(output, cinderclient.v1.client.Client)
def test_get_client_class_v2(self):
output = cinderclient.client.get_client_class('2')
self.assertEqual(output, cinderclient.v2.client.Client)
output = manilaclient.client.get_client_class('1')
self.assertEqual(output, manilaclient.v1.client.Client)
def test_get_client_class_unknown(self):
self.assertRaises(cinderclient.exceptions.UnsupportedVersion,
cinderclient.client.get_client_class, '0')
self.assertRaises(manilaclient.exceptions.UnsupportedVersion,
manilaclient.client.get_client_class, '0')

View File

@ -2,8 +2,8 @@ import mock
import requests
from cinderclient import client
from cinderclient import exceptions
from manilaclient import client
from manilaclient import exceptions
from tests import utils

View File

@ -1,5 +1,5 @@
from cinderclient import exceptions
from cinderclient import service_catalog
from manilaclient import exceptions
from manilaclient import service_catalog
from tests import utils
@ -59,13 +59,13 @@ SERVICE_CATALOG = {
"endpoints_links": [],
},
{
"name": "Nova Volumes",
"type": "volume",
"name": "Nova Shares",
"type": "share",
"endpoints": [
{
"tenantId": "1",
"publicURL": "https://volume1.host/v1/1234",
"internalURL": "https://volume1.host/v1/1234",
"publicURL": "https://share1.host/v1/1234",
"internalURL": "https://share1.host/v1/1234",
"region": "South",
"versionId": "1.0",
"versionInfo": "uri",
@ -73,12 +73,12 @@ SERVICE_CATALOG = {
},
{
"tenantId": "2",
"publicURL": "https://volume1.host/v1/3456",
"internalURL": "https://volume1.host/v1/3456",
"publicURL": "https://share1.host/v1/3456",
"internalURL": "https://share1.host/v1/3456",
"region": "South",
"versionId": "1.1",
"versionInfo": "https://volume1.host/v1/",
"versionList": "https://volume1.host/"
"versionInfo": "https://share1.host/v1/",
"versionList": "https://share1.host/"
},
],
"endpoints_links": [
@ -117,11 +117,11 @@ class ServiceCatalogTest(utils.TestCase):
sc = service_catalog.ServiceCatalog(SERVICE_CATALOG)
self.assertRaises(exceptions.AmbiguousEndpoints, sc.url_for,
service_type='volume')
self.assertEquals(sc.url_for('tenantId', '1', service_type='volume'),
"https://volume1.host/v1/1234")
self.assertEquals(sc.url_for('tenantId', '2', service_type='volume'),
"https://volume1.host/v1/3456")
service_type='share')
self.assertEquals(sc.url_for('tenantId', '1', service_type='share'),
"https://share1.host/v1/1234")
self.assertEquals(sc.url_for('tenantId', '2', service_type='share'),
"https://share1.host/v1/3456")
self.assertRaises(exceptions.EndpointNotFound, sc.url_for,
"region", "North", service_type='volume')
"region", "North", service_type='share')

View File

@ -6,8 +6,8 @@ import sys
import fixtures
from testtools import matchers
from cinderclient import exceptions
import cinderclient.shell
from manilaclient import exceptions
import manilaclient.shell
from tests import utils
@ -31,7 +31,7 @@ class ShellTest(utils.TestCase):
orig = sys.stdout
try:
sys.stdout = cStringIO.StringIO()
_shell = cinderclient.shell.OpenStackCinderShell()
_shell = manilaclient.shell.OpenStackManilaShell()
_shell.main(argstr.split())
except SystemExit:
exc_type, exc_value, exc_traceback = sys.exc_info()
@ -49,8 +49,8 @@ class ShellTest(utils.TestCase):
def test_help(self):
required = [
'.*?^usage: ',
'.*?(?m)^\s+create\s+Add a new volume.',
'.*?(?m)^See "cinder help COMMAND" for help on a specific command',
'.*?^\s+create\s+Creates new NAS storage \(NFS or CIFS\).',
'.*?(?m)^See "manila help COMMAND" for help on a specific command.',
]
help_text = self.shell('help')
for r in required:
@ -59,8 +59,8 @@ class ShellTest(utils.TestCase):
def test_help_on_subcommand(self):
required = [
'.*?^usage: cinder list',
'.*?(?m)^List all the volumes.',
'.*?^usage: manila list',
'.*?(?m)^List all NAS shares.',
]
help_text = self.shell('help list')
for r in required:

View File

@ -1,7 +1,7 @@
from cinderclient import exceptions
from cinderclient import utils
from cinderclient import base
from manilaclient import exceptions
from manilaclient import utils
from manilaclient import base
from tests import utils as test_utils
UUID = '8e8ec658-c7b0-4243-bdf8-6f7f2952c0d0'

View File

@ -1,21 +0,0 @@
from cinderclient import extension
from cinderclient.v1.contrib import list_extensions
from tests import utils
from tests.v1 import fakes
extensions = [
extension.Extension(list_extensions.__name__.split(".")[-1],
list_extensions),
]
cs = fakes.FakeClient(extensions=extensions)
class ListExtensionsTests(utils.TestCase):
def test_list_extensions(self):
all_exts = cs.list_extensions.show_all()
cs.assert_called('GET', '/extensions')
self.assertTrue(len(all_exts) > 0)
for r in all_exts:
self.assertTrue(len(r.summary) > 0)

155
tests/v1/fake_clients.py Normal file
View File

@ -0,0 +1,155 @@
# Copyright 2013 OpenStack, LLC
#
# 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 urlparse
from manilaclient import client as base_client
from manilaclient.v1 import client
from tests import fakes
import tests.utils as utils
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'))
self.client = FakeHTTPClient(**kwargs)
class FakeHTTPClient(base_client.HTTPClient):
def __init__(self, **kwargs):
self.username = 'username'
self.password = 'password'
self.auth_url = 'auth_url'
self.callstack = []
def _cs_request(self, url, method, **kwargs):
# Check that certain things are called correctly
if method in ['GET', 'DELETE']:
assert 'body' not in kwargs
elif method == 'PUT':
assert 'body' in kwargs
# Call the method
args = urlparse.parse_qsl(urlparse.urlparse(url)[4])
kwargs.update(args)
munged_url = url.rsplit('?', 1)[0]
munged_url = munged_url.strip('/').replace('/', '_').replace('.', '_')
munged_url = munged_url.replace('-', '_')
callback = "%s_%s" % (method.lower(), munged_url)
if not hasattr(self, callback):
raise AssertionError('Called unknown API method: %s %s, '
'expected fakes method name: %s' %
(method, url, callback))
# Note the call
self.callstack.append((method, url, kwargs.get('body', None)))
status, headers, body = getattr(self, callback)(**kwargs)
r = utils.TestResponse({
"status_code": status,
"text": body,
"headers": headers,
})
return r, body
if hasattr(status, 'items'):
return utils.TestResponse(status), body
else:
return utils.TestResponse({"status": status}), body
#
# Quotas
#
def get_os_quota_sets_test(self, **kw):
return (200, {}, {'quota_set': {
'tenant_id': 'test',
'metadata_items': [],
'shares': 1,
'snapshots': 1,
'gigabytes': 1}})
def get_os_quota_sets_test_defaults(self):
return (200, {}, {'quota_set': {
'tenant_id': 'test',
'metadata_items': [],
'shares': 1,
'snapshots': 1,
'gigabytes': 1}})
def put_os_quota_sets_test(self, body, **kw):
assert body.keys() == ['quota_set']
fakes.assert_has_keys(body['quota_set'],
required=['tenant_id'])
return (200, {}, {'quota_set': {
'tenant_id': 'test',
'metadata_items': [],
'shares': 2,
'snapshots': 2,
'gigabytes': 1}})
#
# Quota Classes
#
def get_os_quota_class_sets_test(self, **kw):
return (200, {}, {'quota_class_set': {
'class_name': 'test',
'metadata_items': [],
'shares': 1,
'snapshots': 1,
'gigabytes': 1}})
def put_os_quota_class_sets_test(self, body, **kw):
assert body.keys() == ['quota_class_set']
fakes.assert_has_keys(body['quota_class_set'],
required=['class_name'])
return (200, {}, {'quota_class_set': {
'class_name': 'test',
'metadata_items': [],
'shares': 2,
'snapshots': 2,
'gigabytes': 1}})
#
# List all extensions
#
def get_extensions(self, **kw):
exts = [
{
"alias": "FAKE-1",
"description": "Fake extension number 1",
"links": [],
"name": "Fake1",
"namespace": ("http://docs.openstack.org/"
"/ext/fake1/api/v1.1"),
"updated": "2011-06-09T00:00:00+00:00"
},
{
"alias": "FAKE-2",
"description": "Fake extension number 2",
"links": [],
"name": "Fake2",
"namespace": ("http://docs.openstack.org/"
"/ext/fake1/api/v1.1"),
"updated": "2011-06-09T00:00:00+00:00"
},
]
return (200, {}, {"extensions": exts, })

View File

@ -13,105 +13,12 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import urlparse
from cinderclient import client as base_client
from cinderclient.v1 import client
from tests import fakes
import tests.utils as utils
from manilaclient.v1 import shares as shares_ext_module
from manilaclient.v1 import client
from tests.v1 import fake_clients as fakes
def _stub_volume(**kwargs):
volume = {
'id': '1234',
'display_name': None,
'display_description': None,
"attachments": [],
"bootable": "false",
"availability_zone": "cinder",
"created_at": "2012-08-27T00:00:00.000000",
"display_description": None,
"display_name": None,
"id": '00000000-0000-0000-0000-000000000000',
"metadata": {},
"size": 1,
"snapshot_id": None,
"status": "available",
"volume_type": "None",
}
volume.update(kwargs)
return volume
def _stub_snapshot(**kwargs):
snapshot = {
"created_at": "2012-08-28T16:30:31.000000",
"display_description": None,
"display_name": None,
"id": '11111111-1111-1111-1111-111111111111',
"size": 1,
"status": "available",
"volume_id": '00000000-0000-0000-0000-000000000000',
}
snapshot.update(kwargs)
return snapshot
def _self_href(base_uri, tenant_id, backup_id):
return '%s/v1/%s/backups/%s' % (base_uri, tenant_id, backup_id)
def _bookmark_href(base_uri, tenant_id, backup_id):
return '%s/%s/backups/%s' % (base_uri, tenant_id, backup_id)
def _stub_backup_full(id, base_uri, tenant_id):
return {
'id': id,
'name': 'backup',
'description': 'nightly backup',
'volume_id': '712f4980-5ac1-41e5-9383-390aa7c9f58b',
'container': 'volumebackups',
'object_count': 220,
'size': 10,
'availability_zone': 'az1',
'created_at': '2013-04-12T08:16:37.000000',
'status': 'available',
'links': [
{
'href': _self_href(base_uri, tenant_id, id),
'rel': 'self'
},
{
'href': _bookmark_href(base_uri, tenant_id, id),
'rel': 'bookmark'
}
]
}
def _stub_backup(id, base_uri, tenant_id):
return {
'id': id,
'name': 'backup',
'links': [
{
'href': _self_href(base_uri, tenant_id, id),
'rel': 'self'
},
{
'href': _bookmark_href(base_uri, tenant_id, id),
'rel': 'bookmark'
}
]
}
def _stub_restore():
return {'volume_id': '712f4980-5ac1-41e5-9383-390aa7c9f58b'}
class FakeClient(fakes.FakeClient, client.Client):
class FakeClient(fakes.FakeClient):
def __init__(self, *args, **kwargs):
client.Client.__init__(self, 'username', 'password',
@ -120,285 +27,62 @@ class FakeClient(fakes.FakeClient, client.Client):
self.client = FakeHTTPClient(**kwargs)
class FakeHTTPClient(base_client.HTTPClient):
class FakeHTTPClient(fakes.FakeHTTPClient):
def __init__(self, **kwargs):
self.username = 'username'
self.password = 'password'
self.auth_url = 'auth_url'
self.callstack = []
def get_shares_1234(self, **kw):
share = {'share': {'id': 1234, 'name': 'sharename'}}
return (200, {}, share)
def _cs_request(self, url, method, **kwargs):
# Check that certain things are called correctly
if method in ['GET', 'DELETE']:
assert 'body' not in kwargs
elif method == 'PUT':
assert 'body' in kwargs
# Call the method
args = urlparse.parse_qsl(urlparse.urlparse(url)[4])
kwargs.update(args)
munged_url = url.rsplit('?', 1)[0]
munged_url = munged_url.strip('/').replace('/', '_').replace('.', '_')
munged_url = munged_url.replace('-', '_')
callback = "%s_%s" % (method.lower(), munged_url)
if not hasattr(self, callback):
raise AssertionError('Called unknown API method: %s %s, '
'expected fakes method name: %s' %
(method, url, callback))
# Note the call
self.callstack.append((method, url, kwargs.get('body', None)))
status, headers, body = getattr(self, callback)(**kwargs)
r = utils.TestResponse({
"status_code": status,
"text": body,
"headers": headers,
})
return r, body
if hasattr(status, 'items'):
return utils.TestResponse(status), body
else:
return utils.TestResponse({"status": status}), body
#
# Snapshots
#
def get_snapshots_detail(self, **kw):
return (200, {}, {'snapshots': [
_stub_snapshot(),
]})
def get_shares_detail(self, **kw):
shares = {'shares': [{'id': 1234,
'name': 'sharename',
'attachments': [{'server_id': 111}]}]}
return (200, {}, shares)
def get_snapshots_1234(self, **kw):
return (200, {}, {'snapshot': _stub_snapshot(id='1234')})
snapshot = {'snapshot': {'id': 1234, 'name': 'sharename'}}
return (200, {}, snapshot)
def put_snapshots_1234(self, **kw):
snapshot = _stub_snapshot(id='1234')
snapshot.update(kw['body']['snapshot'])
return (200, {}, {'snapshot': snapshot})
def get_snapshots_detail(self, **kw):
print kw
# print kw['share_id']
snapshots = {'snapshots': [{
'id': 1234,
'created_at': '2012-08-27T00:00:00.000000',
'share_size': 1,
'share_id': 4321,
'status': 'available',
'name': 'sharename',
'display_description': 'description',
'share_proto': 'type',
'export_location': 'location',
}]}
return (200, {}, snapshots)
#
# Volumes
#
def put_volumes_1234(self, **kw):
volume = _stub_volume(id='1234')
volume.update(kw['body']['volume'])
return (200, {}, {'volume': volume})
def get_volumes(self, **kw):
return (200, {}, {"volumes": [
{'id': 1234, 'name': 'sample-volume'},
{'id': 5678, 'name': 'sample-volume2'}
]})
# TODO(jdg): This will need to change
# at the very least it's not complete
def get_volumes_detail(self, **kw):
return (200, {}, {"volumes": [
{'id': 1234,
'name': 'sample-volume',
'attachments': [{'server_id': 1234}]},
]})
def get_volumes_1234(self, **kw):
r = {'volume': self.get_volumes_detail()[2]['volumes'][0]}
return (200, {}, r)
def post_volumes_1234_action(self, body, **kw):
def post_shares_1234_action(self, body, **kw):
_body = None
resp = 202
assert len(body.keys()) == 1
action = body.keys()[0]
if action == 'os-attach':
assert body[action].keys() == ['instance_uuid', 'mountpoint']
elif action == 'os-detach':
assert body[action] is None
elif action == 'os-reserve':
assert body[action] is None
elif action == 'os-unreserve':
assert body[action] is None
elif action == 'os-initialize_connection':
assert body[action].keys() == ['connector']
return (202, {}, {'connection_info': 'foos'})
elif action == 'os-terminate_connection':
assert body[action].keys() == ['connector']
elif action == 'os-begin_detaching':
assert body[action] is None
elif action == 'os-roll_detaching':
if action == 'os-allow_access':
assert body[action].keys() == ['access_type', 'access_to']
_body = {'access': {}}
elif action == 'os-deny_access':
assert body[action].keys() == ['access_id']
elif action == 'os-access_list':
assert body[action] is None
else:
raise AssertionError("Unexpected server action: %s" % action)
raise AssertionError("Unexpected share action: %s" % action)
return (resp, {}, _body)
def post_volumes(self, **kw):
return (202, {}, {'volume': {}})
def post_shares(self, **kwargs):
return (202, {}, {'share': {}})
def delete_volumes_1234(self, **kw):
def post_snapshots(self, **kwargs):
return (202, {}, {'snapshot': {}})
def delete_shares_1234(self, **kw):
return (202, {}, None)
#
# Quotas
#
def get_os_quota_sets_test(self, **kw):
return (200, {}, {'quota_set': {
'tenant_id': 'test',
'metadata_items': [],
'volumes': 1,
'snapshots': 1,
'gigabytes': 1}})
def get_os_quota_sets_test_defaults(self):
return (200, {}, {'quota_set': {
'tenant_id': 'test',
'metadata_items': [],
'volumes': 1,
'snapshots': 1,
'gigabytes': 1}})
def put_os_quota_sets_test(self, body, **kw):
assert body.keys() == ['quota_set']
fakes.assert_has_keys(body['quota_set'],
required=['tenant_id'])
return (200, {}, {'quota_set': {
'tenant_id': 'test',
'metadata_items': [],
'volumes': 2,
'snapshots': 2,
'gigabytes': 1}})
#
# Quota Classes
#
def get_os_quota_class_sets_test(self, **kw):
return (200, {}, {'quota_class_set': {
'class_name': 'test',
'metadata_items': [],
'volumes': 1,
'snapshots': 1,
'gigabytes': 1}})
def put_os_quota_class_sets_test(self, body, **kw):
assert body.keys() == ['quota_class_set']
fakes.assert_has_keys(body['quota_class_set'],
required=['class_name'])
return (200, {}, {'quota_class_set': {
'class_name': 'test',
'metadata_items': [],
'volumes': 2,
'snapshots': 2,
'gigabytes': 1}})
#
# VolumeTypes
#
def get_types(self, **kw):
return (200, {}, {
'volume_types': [{'id': 1,
'name': 'test-type-1',
'extra_specs':{}},
{'id': 2,
'name': 'test-type-2',
'extra_specs':{}}]})
def get_types_1(self, **kw):
return (200, {}, {'volume_type': {'id': 1,
'name': 'test-type-1',
'extra_specs': {}}})
def post_types(self, body, **kw):
return (202, {}, {'volume_type': {'id': 3,
'name': 'test-type-3',
'extra_specs': {}}})
def post_types_1_extra_specs(self, body, **kw):
assert body.keys() == ['extra_specs']
return (200, {}, {'extra_specs': {'k': 'v'}})
def delete_types_1_extra_specs_k(self, **kw):
return(204, {}, None)
def delete_types_1(self, **kw):
def delete_snapshots_1234(self, **kwargs):
return (202, {}, None)
#
# Set/Unset metadata
#
def delete_volumes_1234_metadata_test_key(self, **kw):
return (204, {}, None)
def delete_volumes_1234_metadata_key1(self, **kw):
return (204, {}, None)
def delete_volumes_1234_metadata_key2(self, **kw):
return (204, {}, None)
def post_volumes_1234_metadata(self, **kw):
return (204, {}, {'metadata': {'test_key': 'test_value'}})
#
# List all extensions
#
def get_extensions(self, **kw):
exts = [
{
"alias": "FAKE-1",
"description": "Fake extension number 1",
"links": [],
"name": "Fake1",
"namespace": ("http://docs.openstack.org/"
"/ext/fake1/api/v1.1"),
"updated": "2011-06-09T00:00:00+00:00"
},
{
"alias": "FAKE-2",
"description": "Fake extension number 2",
"links": [],
"name": "Fake2",
"namespace": ("http://docs.openstack.org/"
"/ext/fake1/api/v1.1"),
"updated": "2011-06-09T00:00:00+00:00"
},
]
return (200, {}, {"extensions": exts, })
#
# VolumeBackups
#
def get_backups_76a17945_3c6f_435c_975b_b5685db10b62(self, **kw):
base_uri = 'http://localhost:8776'
tenant_id = '0fa851f6668144cf9cd8c8419c1646c1'
backup1 = '76a17945-3c6f-435c-975b-b5685db10b62'
return (200, {},
{'backup': _stub_backup_full(backup1, base_uri, tenant_id)})
def get_backups_detail(self, **kw):
base_uri = 'http://localhost:8776'
tenant_id = '0fa851f6668144cf9cd8c8419c1646c1'
backup1 = '76a17945-3c6f-435c-975b-b5685db10b62'
backup2 = 'd09534c6-08b8-4441-9e87-8976f3a8f699'
return (200, {},
{'backups': [
_stub_backup_full(backup1, base_uri, tenant_id),
_stub_backup_full(backup2, base_uri, tenant_id)]})
def delete_backups_76a17945_3c6f_435c_975b_b5685db10b62(self, **kw):
return (202, {}, None)
def post_backups(self, **kw):
base_uri = 'http://localhost:8776'
tenant_id = '0fa851f6668144cf9cd8c8419c1646c1'
backup1 = '76a17945-3c6f-435c-975b-b5685db10b62'
return (202, {},
{'backup': _stub_backup(backup1, base_uri, tenant_id)})
def post_backups_76a17945_3c6f_435c_975b_b5685db10b62_restore(self, **kw):
return (200, {},
{'restore': _stub_restore()})

View File

@ -1,85 +0,0 @@
# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
# Copyright 2011 OpenStack, LLC
#
# 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 cinderclient.v1 import shares as shares_ext_module
from cinderclient.v1 import client
from tests.v1 import fakes
class FakeClient(fakes.FakeClient):
def __init__(self, *args, **kwargs):
client.Client.__init__(self, 'username', 'password',
'project_id', 'auth_url',
extensions=kwargs.get('extensions'))
self.client = FakeHTTPClient(**kwargs)
class FakeHTTPClient(fakes.FakeHTTPClient):
def get_shares_1234(self, **kw):
share = {'share': {'id': 1234, 'name': 'sharename'}}
return (200, {}, share)
def get_shares_detail(self):
shares = {'shares': [{'id': 1234,
'name': 'sharename',
'attachments': [{'server_id': 111}]}]}
return (200, {}, shares)
def get_share_snapshots_1234(self, **kw):
snapshot = {'share-snapshot': {'id': 1234, 'name': 'sharename'}}
return (200, {}, snapshot)
def get_share_snapshots_detail(self):
snapshots = {'share-snapshots': [{
'id': 1234,
'created_at': '2012-08-27T00:00:00.000000',
'share_size': 1,
'share_id': 4321,
'status': 'available',
'name': 'sharename',
'display_description': 'description',
'share_proto': 'type',
'export_location': 'location',
}]}
return (200, {}, snapshots)
def post_shares_1234_action(self, body, **kw):
_body = None
resp = 202
assert len(body.keys()) == 1
action = body.keys()[0]
if action == 'os-allow_access':
assert body[action].keys() == ['access_type', 'access_to']
elif action == 'os-deny_access':
assert body[action].keys() == ['access_id']
elif action == 'os-access_list':
assert body[action] is None
else:
raise AssertionError("Unexpected share action: %s" % action)
return (resp, {}, _body)
def post_shares(self, **kwargs):
return (202, {}, {'share': {}})
def post_share_snapshots(self, **kwargs):
return (202, {}, {'share-snapshot': {}})
def delete_shares_1234(self, **kw):
return (202, {}, None)
def delete_share_snapshots_1234(self, **kwargs):
return (202, {}, None)

View File

@ -1,44 +0,0 @@
# Copyright 2010 Jacob Kaplan-Moss
# Copyright 2011 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 cinderclient import extension
from cinderclient.v1 import share_snapshots
from tests import utils
from tests.v1.shares import fakes
extensions = [
extension.Extension('share_snapshots', share_snapshots),
]
cs = fakes.FakeClient(extensions=extensions)
class ShareSnapshotsTest(utils.TestCase):
def test_create_share_snapshot(self):
cs.share_snapshots.create(1234)
cs.assert_called('POST', '/share-snapshots')
def test_delete_share(self):
snapshot = cs.share_snapshots.get(1234)
cs.share_snapshots.delete(snapshot)
cs.assert_called('DELETE', '/share-snapshots/1234')
def test_list_shares(self):
cs.share_snapshots.list()
cs.assert_called('GET', '/share-snapshots/detail')

View File

@ -1,54 +0,0 @@
# Copyright 2010 Jacob Kaplan-Moss
# Copyright 2011 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 cinderclient import extension
from cinderclient.v1 import shares
from tests import utils
from tests.v1.shares import fakes
extensions = [
extension.Extension('shares', shares),
]
cs = fakes.FakeClient(extensions=extensions)
class SharesTest(utils.TestCase):
def test_create_nfs_share(self):
cs.shares.create('nfs', 1)
cs.assert_called('POST', '/shares')
def test_create_cifs_share(self):
cs.shares.create('cifs', 2)
cs.assert_called('POST', '/shares')
def test_delete_share(self):
share = cs.shares.get('1234')
cs.shares.delete(share)
cs.assert_called('DELETE', '/shares/1234')
def test_list_shares(self):
cs.shares.list()
cs.assert_called('GET', '/shares/detail')
def test_allow_access_to_share(self):
share = cs.shares.get(1234)
ip = '192.168.0.1'
cs.shares.allow(share, 'ip', ip)
cs.assert_called('POST', '/shares/1234/action')

View File

@ -1,10 +1,26 @@
import json
import mock
# Copyright (c) 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.
import json
import mock
import requests
from cinderclient.v1 import client
from cinderclient import exceptions
from manilaclient import exceptions
from manilaclient.v1 import client
from tests import utils
@ -24,9 +40,9 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
"endpoints": [
{
"region": "RegionOne",
"adminURL": "http://localhost:8774/v1",
"internalURL": "http://localhost:8774/v1",
"publicURL": "http://localhost:8774/v1/",
"adminURL": "http://localhost:8774/v2",
"internalURL": "http://localhost:8774/v2",
"publicURL": "http://localhost:8774/v2/",
},
],
},
@ -96,9 +112,9 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
"endpoints": [
{
"region": "RegionOne",
"adminURL": "http://localhost:8774/v1",
"internalURL": "http://localhost:8774/v1",
"publicURL": "http://localhost:8774/v1/",
"adminURL": "http://localhost:8774/v2",
"internalURL": "http://localhost:8774/v2",
"publicURL": "http://localhost:8774/v2/",
},
],
},
@ -168,7 +184,7 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
def test_auth_redirect(self):
cs = client.Client("username", "password", "project_id",
"auth_url/v1", service_type='compute')
"auth_url/v2", service_type='compute')
dict_correct_response = {
"access": {
"token": {
@ -180,10 +196,10 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
"type": "compute",
"endpoints": [
{
"adminURL": "http://localhost:8774/v1",
"adminURL": "http://localhost:8774/v2",
"region": "RegionOne",
"internalURL": "http://localhost:8774/v1",
"publicURL": "http://localhost:8774/v1/",
"internalURL": "http://localhost:8774/v2",
"publicURL": "http://localhost:8774/v2/",
},
],
},
@ -195,7 +211,7 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
{"headers": {'location':'http://127.0.0.1:5001'},
"status_code": 305,
"text": "Use proxy"},
# Configured on admin port, cinder redirects to v2.0 port.
# Configured on admin port, manila redirects to v2.0 port.
# When trying to connect on it, keystone auth succeed by v1.0
# protocol (through headers) but tokens are being returned in
# body (looks like keystone bug). Leaved for compatibility.
@ -261,25 +277,25 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
},
"serviceCatalog": [
{
"adminURL": "http://localhost:8774/v1",
"adminURL": "http://localhost:8774/v2",
"type": "compute",
"name": "Compute CLoud",
"endpoints": [
{
"region": "RegionOne",
"internalURL": "http://localhost:8774/v1",
"publicURL": "http://localhost:8774/v1/",
"internalURL": "http://localhost:8774/v2",
"publicURL": "http://localhost:8774/v2/",
},
],
},
{
"adminURL": "http://localhost:8774/v1",
"adminURL": "http://localhost:8774/v2",
"type": "compute",
"name": "Hyper-compute Cloud",
"endpoints": [
{
"internalURL": "http://localhost:8774/v1",
"publicURL": "http://localhost:8774/v1/",
"internalURL": "http://localhost:8774/v2",
"publicURL": "http://localhost:8774/v2/",
},
],
},
@ -304,7 +320,7 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
class AuthenticationTests(utils.TestCase):
def test_authenticate_success(self):
cs = client.Client("username", "password", "project_id", "auth_url")
management_url = 'https://localhost/v1.1/443470'
management_url = 'https://localhost/v2.1/443470'
auth_response = utils.TestResponse({
'status_code': 204,
'headers': {

View File

@ -1,4 +1,4 @@
# Copyright 2011 OpenStack LLC.
# Copyright 2013 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -29,14 +29,14 @@ class QuotaClassSetsTest(utils.TestCase):
def test_update_quota(self):
q = cs.quota_classes.get('test')
q.update(volumes=2)
q.update(shares=2)
cs.assert_called('PUT', '/os-quota-class-sets/test')
def test_refresh_quota(self):
q = cs.quota_classes.get('test')
q2 = cs.quota_classes.get('test')
self.assertEqual(q.volumes, q2.volumes)
q2.volumes = 0
self.assertNotEqual(q.volumes, q2.volumes)
self.assertEqual(q.shares, q2.shares)
q2.shares = 0
self.assertNotEqual(q.shares, q2.shares)
q2.get()
self.assertEqual(q.volumes, q2.volumes)
self.assertEqual(q.shares, q2.shares)

View File

@ -1,4 +1,4 @@
# Copyright 2011 OpenStack LLC.
# Copyright 2013 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -34,19 +34,19 @@ class QuotaSetsTest(utils.TestCase):
def test_update_quota(self):
q = cs.quotas.get('test')
q.update(volumes=2)
q.update(shares=2)
q.update(snapshots=2)
cs.assert_called('PUT', '/os-quota-sets/test')
def test_refresh_quota(self):
q = cs.quotas.get('test')
q2 = cs.quotas.get('test')
self.assertEqual(q.volumes, q2.volumes)
self.assertEqual(q.shares, q2.shares)
self.assertEqual(q.snapshots, q2.snapshots)
q2.volumes = 0
self.assertNotEqual(q.volumes, q2.volumes)
q2.shares = 0
self.assertNotEqual(q.shares, q2.shares)
q2.snapshots = 0
self.assertNotEqual(q.snapshots, q2.snapshots)
q2.get()
self.assertEqual(q.volumes, q2.volumes)
self.assertEqual(q.shares, q2.shares)
self.assertEqual(q.snapshots, q2.snapshots)

View File

@ -15,11 +15,11 @@
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient import extension
from cinderclient.v2 import share_snapshots
from manilaclient import extension
from manilaclient.v1 import share_snapshots
from tests import utils
from tests.v2.shares import fakes
from tests.v1 import fakes
extensions = [
@ -32,13 +32,13 @@ class ShareSnapshotsTest(utils.TestCase):
def test_create_share_snapshot(self):
cs.share_snapshots.create(1234)
cs.assert_called('POST', '/share-snapshots')
cs.assert_called('POST', '/snapshots')
def test_delete_share(self):
snapshot = cs.share_snapshots.get(1234)
cs.share_snapshots.delete(snapshot)
cs.assert_called('DELETE', '/share-snapshots/1234')
cs.assert_called('DELETE', '/snapshots/1234')
def test_list_shares(self):
cs.share_snapshots.list()
cs.assert_called('GET', '/share-snapshots/detail')
cs.assert_called('GET', '/snapshots/detail')

View File

@ -15,11 +15,11 @@
# License for the specific language governing permissions and limitations
# under the License.
from cinderclient import extension
from cinderclient.v2 import shares
from manilaclient import extension
from manilaclient.v1 import shares
from tests import utils
from tests.v2.shares import fakes
from tests.v1 import fakes
extensions = [

View File

@ -1,6 +1,4 @@
# Copyright 2010 Jacob Kaplan-Moss
# Copyright 2011 OpenStack LLC.
# Copyright 2013 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -15,25 +13,22 @@
# License for the specific language governing permissions and limitations
# under the License.
import os
import fixtures
from cinderclient import client
from cinderclient import shell
from cinderclient.v1 import shell as shell_v1
from tests.v1 import fakes
from manilaclient import client
from manilaclient import shell
from tests import utils
from tests.v1 import fakes
class ShellTest(utils.TestCase):
FAKE_ENV = {
'CINDER_USERNAME': 'username',
'CINDER_PASSWORD': 'password',
'CINDER_PROJECT_ID': 'project_id',
'OS_VOLUME_API_VERSION': '1.1',
'CINDER_URL': 'http://no.where',
'MANILA_USERNAME': 'username',
'MANILA_PASSWORD': 'password',
'MANILA_PROJECT_ID': 'project_id',
'OS_SHARE_API_VERSION': '2',
'MANILA_URL': 'http://no.where',
}
# Patch os.environ to avoid required auth info.
@ -44,7 +39,7 @@ class ShellTest(utils.TestCase):
self.useFixture(fixtures.EnvironmentVariable(var,
self.FAKE_ENV[var]))
self.shell = shell.OpenStackCinderShell()
self.shell = shell.OpenStackManilaShell()
#HACK(bcwaldon): replace this when we start using stubs
self.old_get_client_class = client.get_client_class
@ -71,113 +66,94 @@ class ShellTest(utils.TestCase):
def assert_called_anytime(self, method, url, body=None):
return self.shell.cs.assert_called_anytime(method, url, body)
def test_extract_metadata(self):
# mimic the result of argparse's parse_args() method
class Arguments:
def __init__(self, metadata=[]):
self.metadata = metadata
inputs = [
([], {}),
(["key=value"], {"key": "value"}),
(["key"], {"key": None}),
(["k1=v1", "k2=v2"], {"k1": "v1", "k2": "v2"}),
(["k1=v1", "k2"], {"k1": "v1", "k2": None}),
(["k1", "k2=v2"], {"k1": None, "k2": "v2"})
]
for input in inputs:
args = Arguments(metadata=input[0])
self.assertEquals(shell_v1._extract_metadata(args), input[1])
def test_list(self):
self.run_command('list')
# NOTE(jdg): we default to detail currently
self.assert_called('GET', '/volumes/detail')
self.assert_called('GET', '/shares/detail')
def test_list_filter_status(self):
self.run_command('list --status=available')
self.assert_called('GET', '/volumes/detail?status=available')
self.assert_called('GET', '/shares/detail?status=available')
def test_list_filter_display_name(self):
self.run_command('list --display-name=1234')
self.assert_called('GET', '/volumes/detail?display_name=1234')
def test_list_filter_name(self):
self.run_command('list --name=1234')
self.assert_called('GET', '/shares/detail?name=1234')
def test_list_all_tenants(self):
self.run_command('list --all-tenants=1')
self.assert_called('GET', '/volumes/detail?all_tenants=1')
self.assert_called('GET', '/shares/detail?all_tenants=1')
def test_show(self):
self.run_command('show 1234')
self.assert_called('GET', '/volumes/1234')
self.assert_called('GET', '/shares/1234')
def test_delete(self):
self.run_command('delete 1234')
self.assert_called('DELETE', '/volumes/1234')
self.assert_called('DELETE', '/shares/1234')
def test_snapshot_list_filter_volume_id(self):
self.run_command('snapshot-list --volume-id=1234')
self.assert_called('GET', '/snapshots/detail?volume_id=1234')
def test_snapshot_list_filter_share_id(self):
self.run_command('snapshot-list --share-id=1234')
self.assert_called('GET', '/snapshots/detail?share_id=1234')
def test_snapshot_list_filter_status_and_volume_id(self):
self.run_command('snapshot-list --status=available --volume-id=1234')
def test_snapshot_list_filter_status_and_share_id(self):
self.run_command('snapshot-list --status=available --share-id=1234')
self.assert_called('GET', '/snapshots/detail?'
'status=available&volume_id=1234')
'status=available&share_id=1234')
def test_rename(self):
# basic rename with positional agruments
self.run_command('rename 1234 new-name')
expected = {'volume': {'display_name': 'new-name'}}
self.assert_called('PUT', '/volumes/1234', body=expected)
# change description only
self.run_command('rename 1234 --display-description=new-description')
expected = {'volume': {'display_description': 'new-description'}}
self.assert_called('PUT', '/volumes/1234', body=expected)
# rename and change description
self.run_command('rename 1234 new-name '
'--display-description=new-description')
expected = {'volume': {
'display_name': 'new-name',
'display_description': 'new-description',
}}
self.assert_called('PUT', '/volumes/1234', body=expected)
# noop, the only all will be the lookup
self.run_command('rename 1234')
self.assert_called('GET', '/volumes/1234')
# def test_rename(self):
# # basic rename with positional agruments
# self.run_command('rename 1234 new-name')
# expected = {'share': {'name': 'new-name'}}
# self.assert_called('PUT', '/shares/1234', body=expected)
# # change description only
# self.run_command('rename 1234 --description=new-description')
# expected = {'share': {'description': 'new-description'}}
# self.assert_called('PUT', '/shares/1234', body=expected)
# # rename and change description
# self.run_command('rename 1234 new-name '
# '--description=new-description')
# expected = {'share': {
# 'name': 'new-name',
# 'description': 'new-description',
# }}
# self.assert_called('PUT', '/shares/1234', body=expected)
# # noop, the only all will be the lookup
# self.run_command('rename 1234')
# self.assert_called('GET', '/shares/1234')
def test_rename_snapshot(self):
# basic rename with positional agruments
self.run_command('snapshot-rename 1234 new-name')
expected = {'snapshot': {'display_name': 'new-name'}}
self.assert_called('PUT', '/snapshots/1234', body=expected)
# change description only
self.run_command('snapshot-rename 1234 '
'--display-description=new-description')
expected = {'snapshot': {'display_description': 'new-description'}}
self.assert_called('PUT', '/snapshots/1234', body=expected)
# snapshot-rename and change description
self.run_command('snapshot-rename 1234 new-name '
'--display-description=new-description')
expected = {'snapshot': {
'display_name': 'new-name',
'display_description': 'new-description',
}}
self.assert_called('PUT', '/snapshots/1234', body=expected)
# noop, the only all will be the lookup
self.run_command('snapshot-rename 1234')
self.assert_called('GET', '/snapshots/1234')
# def test_rename_snapshot(self):
# # basic rename with positional agruments
# self.run_command('snapshot-rename 1234 new-name')
# expected = {'snapshot': {'name': 'new-name'}}
# self.assert_called('PUT', '/snapshots/1234', body=expected)
# # change description only
# self.run_command('snapshot-rename 1234 '
# '--description=new-description')
# expected = {'snapshot': {'description': 'new-description'}}
# self.assert_called('PUT', '/snapshots/1234', body=expected)
# # snapshot-rename and change description
# self.run_command('snapshot-rename 1234 new-name '
# '--description=new-description')
# expected = {'snapshot': {
# 'name': 'new-name',
# 'description': 'new-description',
# }}
# self.assert_called('PUT', '/snapshots/1234', body=expected)
# # noop, the only all will be the lookup
# self.run_command('snapshot-rename 1234')
# self.assert_called('GET', '/snapshots/1234')
def test_set_metadata_set(self):
self.run_command('metadata 1234 set key1=val1 key2=val2')
self.assert_called('POST', '/volumes/1234/metadata',
{'metadata': {'key1': 'val1', 'key2': 'val2'}})
# def test_set_metadata_set(self):
# self.run_command('metadata 1234 set key1=val1 key2=val2')
# self.assert_called('POST', '/shares/1234/metadata',
# {'metadata': {'key1': 'val1', 'key2': 'val2'}})
def test_set_metadata_delete_dict(self):
self.run_command('metadata 1234 unset key1=val1 key2=val2')
self.assert_called('DELETE', '/volumes/1234/metadata/key1')
self.assert_called('DELETE', '/volumes/1234/metadata/key2', pos=-2)
# def test_set_metadata_delete_dict(self):
# self.run_command('metadata 1234 unset key1=val1 key2=val2')
# self.assert_called('DELETE', '/shares/1234/metadata/key1')
# self.assert_called('DELETE', '/shares/1234/metadata/key2', pos=-2)
def test_set_metadata_delete_keys(self):
self.run_command('metadata 1234 unset key1 key2')
self.assert_called('DELETE', '/volumes/1234/metadata/key1')
self.assert_called('DELETE', '/volumes/1234/metadata/key2', pos=-2)
# def test_set_metadata_delete_keys(self):
# self.run_command('metadata 1234 unset key1 key2')
# self.assert_called('DELETE', '/shares/1234/metadata/key1')
# self.assert_called('DELETE', '/shares/1234/metadata/key2', pos=-2)

View File

@ -1,35 +0,0 @@
from cinderclient import exceptions
from cinderclient.v1 import volume_types
from tests import utils
from tests.v1 import fakes
cs = fakes.FakeClient()
class TypesTest(utils.TestCase):
def test_list_types(self):
tl = cs.volume_types.list()
cs.assert_called('GET', '/types')
for t in tl:
self.assertTrue(isinstance(t, volume_types.VolumeType))
def test_create(self):
t = cs.volume_types.create('test-type-3')
cs.assert_called('POST', '/types')
self.assertTrue(isinstance(t, volume_types.VolumeType))
def test_set_key(self):
t = cs.volume_types.get(1)
t.set_keys({'k': 'v'})
cs.assert_called('POST',
'/types/1/extra_specs',
{'extra_specs': {'k': 'v'}})
def test_unsset_keys(self):
t = cs.volume_types.get(1)
t.unset_keys(['k'])
cs.assert_called('DELETE', '/types/1/extra_specs/k')
def test_delete(self):
cs.volume_types.delete(1)
cs.assert_called('DELETE', '/types/1')

View File

@ -1,53 +0,0 @@
# Copyright (C) 2013 Hewlett-Packard Development Company, L.P.
# 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 tests import utils
from tests.v1 import fakes
cs = fakes.FakeClient()
class VolumeBackupsTest(utils.TestCase):
def test_create(self):
cs.backups.create('2b695faf-b963-40c8-8464-274008fbcef4')
cs.assert_called('POST', '/backups')
def test_get(self):
backup_id = '76a17945-3c6f-435c-975b-b5685db10b62'
cs.backups.get(backup_id)
cs.assert_called('GET', '/backups/%s' % backup_id)
def test_list(self):
cs.backups.list()
cs.assert_called('GET', '/backups/detail')
def test_delete(self):
b = cs.backups.list()[0]
b.delete()
cs.assert_called('DELETE',
'/backups/76a17945-3c6f-435c-975b-b5685db10b62')
cs.backups.delete('76a17945-3c6f-435c-975b-b5685db10b62')
cs.assert_called('DELETE',
'/backups/76a17945-3c6f-435c-975b-b5685db10b62')
cs.backups.delete(b)
cs.assert_called('DELETE',
'/backups/76a17945-3c6f-435c-975b-b5685db10b62')
def test_restore(self):
backup_id = '76a17945-3c6f-435c-975b-b5685db10b62'
cs.restores.restore(backup_id)
cs.assert_called('POST', '/backups/%s/restore' % backup_id)

View File

@ -1,71 +0,0 @@
from tests import utils
from tests.v1 import fakes
cs = fakes.FakeClient()
class VolumesTest(utils.TestCase):
def test_delete_volume(self):
v = cs.volumes.list()[0]
v.delete()
cs.assert_called('DELETE', '/volumes/1234')
cs.volumes.delete('1234')
cs.assert_called('DELETE', '/volumes/1234')
cs.volumes.delete(v)
cs.assert_called('DELETE', '/volumes/1234')
def test_create_volume(self):
cs.volumes.create(1)
cs.assert_called('POST', '/volumes')
def test_attach(self):
v = cs.volumes.get('1234')
cs.volumes.attach(v, 1, '/dev/vdc')
cs.assert_called('POST', '/volumes/1234/action')
def test_detach(self):
v = cs.volumes.get('1234')
cs.volumes.detach(v)
cs.assert_called('POST', '/volumes/1234/action')
def test_reserve(self):
v = cs.volumes.get('1234')
cs.volumes.reserve(v)
cs.assert_called('POST', '/volumes/1234/action')
def test_unreserve(self):
v = cs.volumes.get('1234')
cs.volumes.unreserve(v)
cs.assert_called('POST', '/volumes/1234/action')
def test_begin_detaching(self):
v = cs.volumes.get('1234')
cs.volumes.begin_detaching(v)
cs.assert_called('POST', '/volumes/1234/action')
def test_roll_detaching(self):
v = cs.volumes.get('1234')
cs.volumes.roll_detaching(v)
cs.assert_called('POST', '/volumes/1234/action')
def test_initialize_connection(self):
v = cs.volumes.get('1234')
cs.volumes.initialize_connection(v, {})
cs.assert_called('POST', '/volumes/1234/action')
def test_terminate_connection(self):
v = cs.volumes.get('1234')
cs.volumes.terminate_connection(v, {})
cs.assert_called('POST', '/volumes/1234/action')
def test_set_metadata(self):
cs.volumes.set_metadata(1234, {'k1': 'v1'})
cs.assert_called('POST', '/volumes/1234/metadata',
{'metadata': {'k1': 'v1'}})
def test_delete_metadata(self):
keys = ['key1']
cs.volumes.delete_metadata(1234, keys)
cs.assert_called('DELETE', '/volumes/1234/metadata/key1')

View File

@ -1 +0,0 @@
BLAH

View File

@ -1,15 +0,0 @@
# Copyright (c) 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.

View File

@ -1,36 +0,0 @@
# Copyright (c) 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 cinderclient import extension
from cinderclient.v2.contrib import list_extensions
from tests import utils
from tests.v1 import fakes
extensions = [
extension.Extension(list_extensions.__name__.split(".")[-1],
list_extensions),
]
cs = fakes.FakeClient(extensions=extensions)
class ListExtensionsTests(utils.TestCase):
def test_list_extensions(self):
all_exts = cs.list_extensions.show_all()
cs.assert_called('GET', '/extensions')
self.assertTrue(len(all_exts) > 0)
for r in all_exts:
self.assertTrue(len(r.summary) > 0)

View File

@ -1,411 +0,0 @@
# Copyright 2013 OpenStack, LLC
#
# 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 urlparse
from cinderclient import client as base_client
from cinderclient.v2 import client
from tests import fakes
import tests.utils as utils
def _stub_volume(**kwargs):
volume = {
'id': '1234',
'name': None,
'description': None,
"attachments": [],
"bootable": "false",
"availability_zone": "cinder",
"created_at": "2012-08-27T00:00:00.000000",
"id": '00000000-0000-0000-0000-000000000000',
"metadata": {},
"size": 1,
"snapshot_id": None,
"status": "available",
"volume_type": "None",
"links": [
{
"href": "http://localhost/v2/fake/volumes/1234",
"rel": "self"
},
{
"href": "http://localhost/fake/volumes/1234",
"rel": "bookmark"
}
],
}
volume.update(kwargs)
return volume
def _stub_snapshot(**kwargs):
snapshot = {
"created_at": "2012-08-28T16:30:31.000000",
"display_description": None,
"display_name": None,
"id": '11111111-1111-1111-1111-111111111111',
"size": 1,
"status": "available",
"volume_id": '00000000-0000-0000-0000-000000000000',
}
snapshot.update(kwargs)
return snapshot
def _self_href(base_uri, tenant_id, backup_id):
return '%s/v2/%s/backups/%s' % (base_uri, tenant_id, backup_id)
def _bookmark_href(base_uri, tenant_id, backup_id):
return '%s/%s/backups/%s' % (base_uri, tenant_id, backup_id)
def _stub_backup_full(id, base_uri, tenant_id):
return {
'id': id,
'name': 'backup',
'description': 'nightly backup',
'volume_id': '712f4980-5ac1-41e5-9383-390aa7c9f58b',
'container': 'volumebackups',
'object_count': 220,
'size': 10,
'availability_zone': 'az1',
'created_at': '2013-04-12T08:16:37.000000',
'status': 'available',
'links': [
{
'href': _self_href(base_uri, tenant_id, id),
'rel': 'self'
},
{
'href': _bookmark_href(base_uri, tenant_id, id),
'rel': 'bookmark'
}
]
}
def _stub_backup(id, base_uri, tenant_id):
return {
'id': id,
'name': 'backup',
'links': [
{
'href': _self_href(base_uri, tenant_id, id),
'rel': 'self'
},
{
'href': _bookmark_href(base_uri, tenant_id, id),
'rel': 'bookmark'
}
]
}
def _stub_restore():
return {'volume_id': '712f4980-5ac1-41e5-9383-390aa7c9f58b'}
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'))
self.client = FakeHTTPClient(**kwargs)
class FakeHTTPClient(base_client.HTTPClient):
def __init__(self, **kwargs):
self.username = 'username'
self.password = 'password'
self.auth_url = 'auth_url'
self.callstack = []
def _cs_request(self, url, method, **kwargs):
# Check that certain things are called correctly
if method in ['GET', 'DELETE']:
assert 'body' not in kwargs
elif method == 'PUT':
assert 'body' in kwargs
# Call the method
args = urlparse.parse_qsl(urlparse.urlparse(url)[4])
kwargs.update(args)
munged_url = url.rsplit('?', 1)[0]
munged_url = munged_url.strip('/').replace('/', '_').replace('.', '_')
munged_url = munged_url.replace('-', '_')
callback = "%s_%s" % (method.lower(), munged_url)
if not hasattr(self, callback):
raise AssertionError('Called unknown API method: %s %s, '
'expected fakes method name: %s' %
(method, url, callback))
# Note the call
self.callstack.append((method, url, kwargs.get('body', None)))
status, headers, body = getattr(self, callback)(**kwargs)
r = utils.TestResponse({
"status_code": status,
"text": body,
"headers": headers,
})
return r, body
if hasattr(status, 'items'):
return utils.TestResponse(status), body
else:
return utils.TestResponse({"status": status}), body
#
# Snapshots
#
def get_snapshots_detail(self, **kw):
return (200, {}, {'snapshots': [
_stub_snapshot(),
]})
def get_snapshots_1234(self, **kw):
return (200, {}, {'snapshot': _stub_snapshot(id='1234')})
def put_snapshots_1234(self, **kw):
snapshot = _stub_snapshot(id='1234')
snapshot.update(kw['body']['snapshot'])
return (200, {}, {'snapshot': snapshot})
#
# Volumes
#
def put_volumes_1234(self, **kw):
volume = _stub_volume(id='1234')
volume.update(kw['body']['volume'])
return (200, {}, {'volume': volume})
def get_volumes(self, **kw):
return (200, {}, {"volumes": [
{'id': 1234, 'name': 'sample-volume'},
{'id': 5678, 'name': 'sample-volume2'}
]})
# TODO(jdg): This will need to change
# at the very least it's not complete
def get_volumes_detail(self, **kw):
return (200, {}, {"volumes": [
{'id': 1234,
'name': 'sample-volume',
'attachments': [{'server_id': 1234}]},
]})
def get_volumes_1234(self, **kw):
r = {'volume': self.get_volumes_detail()[2]['volumes'][0]}
return (200, {}, r)
def post_volumes_1234_action(self, body, **kw):
_body = None
resp = 202
assert len(body.keys()) == 1
action = body.keys()[0]
if action == 'os-attach':
assert body[action].keys() == ['instance_uuid', 'mountpoint']
elif action == 'os-detach':
assert body[action] is None
elif action == 'os-reserve':
assert body[action] is None
elif action == 'os-unreserve':
assert body[action] is None
elif action == 'os-initialize_connection':
assert body[action].keys() == ['connector']
return (202, {}, {'connection_info': 'foos'})
elif action == 'os-terminate_connection':
assert body[action].keys() == ['connector']
elif action == 'os-begin_detaching':
assert body[action] is None
elif action == 'os-roll_detaching':
assert body[action] is None
else:
raise AssertionError("Unexpected server action: %s" % action)
return (resp, {}, _body)
def post_volumes(self, **kw):
return (202, {}, {'volume': {}})
def delete_volumes_1234(self, **kw):
return (202, {}, None)
#
# Quotas
#
def get_os_quota_sets_test(self, **kw):
return (200, {}, {'quota_set': {
'tenant_id': 'test',
'metadata_items': [],
'volumes': 1,
'snapshots': 1,
'gigabytes': 1}})
def get_os_quota_sets_test_defaults(self):
return (200, {}, {'quota_set': {
'tenant_id': 'test',
'metadata_items': [],
'volumes': 1,
'snapshots': 1,
'gigabytes': 1}})
def put_os_quota_sets_test(self, body, **kw):
assert body.keys() == ['quota_set']
fakes.assert_has_keys(body['quota_set'],
required=['tenant_id'])
return (200, {}, {'quota_set': {
'tenant_id': 'test',
'metadata_items': [],
'volumes': 2,
'snapshots': 2,
'gigabytes': 1}})
#
# Quota Classes
#
def get_os_quota_class_sets_test(self, **kw):
return (200, {}, {'quota_class_set': {
'class_name': 'test',
'metadata_items': [],
'volumes': 1,
'snapshots': 1,
'gigabytes': 1}})
def put_os_quota_class_sets_test(self, body, **kw):
assert body.keys() == ['quota_class_set']
fakes.assert_has_keys(body['quota_class_set'],
required=['class_name'])
return (200, {}, {'quota_class_set': {
'class_name': 'test',
'metadata_items': [],
'volumes': 2,
'snapshots': 2,
'gigabytes': 1}})
#
# VolumeTypes
#
def get_types(self, **kw):
return (200, {}, {
'volume_types': [{'id': 1,
'name': 'test-type-1',
'extra_specs':{}},
{'id': 2,
'name': 'test-type-2',
'extra_specs':{}}]})
def get_types_1(self, **kw):
return (200, {}, {'volume_type': {'id': 1,
'name': 'test-type-1',
'extra_specs': {}}})
def post_types(self, body, **kw):
return (202, {}, {'volume_type': {'id': 3,
'name': 'test-type-3',
'extra_specs': {}}})
def post_types_1_extra_specs(self, body, **kw):
assert body.keys() == ['extra_specs']
return (200, {}, {'extra_specs': {'k': 'v'}})
def delete_types_1_extra_specs_k(self, **kw):
return(204, {}, None)
def delete_types_1(self, **kw):
return (202, {}, None)
#
# Set/Unset metadata
#
def delete_volumes_1234_metadata_test_key(self, **kw):
return (204, {}, None)
def delete_volumes_1234_metadata_key1(self, **kw):
return (204, {}, None)
def delete_volumes_1234_metadata_key2(self, **kw):
return (204, {}, None)
def post_volumes_1234_metadata(self, **kw):
return (204, {}, {'metadata': {'test_key': 'test_value'}})
#
# List all extensions
#
def get_extensions(self, **kw):
exts = [
{
"alias": "FAKE-1",
"description": "Fake extension number 1",
"links": [],
"name": "Fake1",
"namespace": ("http://docs.openstack.org/"
"/ext/fake1/api/v1.1"),
"updated": "2011-06-09T00:00:00+00:00"
},
{
"alias": "FAKE-2",
"description": "Fake extension number 2",
"links": [],
"name": "Fake2",
"namespace": ("http://docs.openstack.org/"
"/ext/fake1/api/v1.1"),
"updated": "2011-06-09T00:00:00+00:00"
},
]
return (200, {}, {"extensions": exts, })
#
# VolumeBackups
#
def get_backups_76a17945_3c6f_435c_975b_b5685db10b62(self, **kw):
base_uri = 'http://localhost:8776'
tenant_id = '0fa851f6668144cf9cd8c8419c1646c1'
backup1 = '76a17945-3c6f-435c-975b-b5685db10b62'
return (200, {},
{'backup': _stub_backup_full(backup1, base_uri, tenant_id)})
def get_backups_detail(self, **kw):
base_uri = 'http://localhost:8776'
tenant_id = '0fa851f6668144cf9cd8c8419c1646c1'
backup1 = '76a17945-3c6f-435c-975b-b5685db10b62'
backup2 = 'd09534c6-08b8-4441-9e87-8976f3a8f699'
return (200, {},
{'backups': [
_stub_backup_full(backup1, base_uri, tenant_id),
_stub_backup_full(backup2, base_uri, tenant_id)]})
def delete_backups_76a17945_3c6f_435c_975b_b5685db10b62(self, **kw):
return (202, {}, None)
def post_backups(self, **kw):
base_uri = 'http://localhost:8776'
tenant_id = '0fa851f6668144cf9cd8c8419c1646c1'
backup1 = '76a17945-3c6f-435c-975b-b5685db10b62'
return (202, {},
{'backup': _stub_backup(backup1, base_uri, tenant_id)})
def post_backups_76a17945_3c6f_435c_975b_b5685db10b62_restore(self, **kw):
return (200, {},
{'restore': _stub_restore()})

View File

@ -1,85 +0,0 @@
# Copyright (c) 2011 X.commerce, a business unit of eBay Inc.
# Copyright 2011 OpenStack, LLC
#
# 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 cinderclient.v2 import shares as shares_ext_module
from cinderclient.v2 import client
from tests.v2 import fakes
class FakeClient(fakes.FakeClient):
def __init__(self, *args, **kwargs):
client.Client.__init__(self, 'username', 'password',
'project_id', 'auth_url',
extensions=kwargs.get('extensions'))
self.client = FakeHTTPClient(**kwargs)
class FakeHTTPClient(fakes.FakeHTTPClient):
def get_shares_1234(self, **kw):
share = {'share': {'id': 1234, 'name': 'sharename'}}
return (200, {}, share)
def get_shares_detail(self):
shares = {'shares': [{'id': 1234,
'name': 'sharename',
'attachments': [{'server_id': 111}]}]}
return (200, {}, shares)
def get_share_snapshots_1234(self, **kw):
snapshot = {'share-snapshot': {'id': 1234, 'name': 'sharename'}}
return (200, {}, snapshot)
def get_share_snapshots_detail(self):
snapshots = {'share-snapshots': [{
'id': 1234,
'created_at': '2012-08-27T00:00:00.000000',
'share_size': 1,
'share_id': 4321,
'status': 'available',
'name': 'sharename',
'display_description': 'description',
'share_proto': 'type',
'export_location': 'location',
}]}
return (200, {}, snapshots)
def post_shares_1234_action(self, body, **kw):
_body = None
resp = 202
assert len(body.keys()) == 1
action = body.keys()[0]
if action == 'os-allow_access':
assert body[action].keys() == ['access_type', 'access_to']
elif action == 'os-deny_access':
assert body[action].keys() == ['access_id']
elif action == 'os-access_list':
assert body[action] is None
else:
raise AssertionError("Unexpected share action: %s" % action)
return (resp, {}, _body)
def post_shares(self, **kwargs):
return (202, {}, {'share': {}})
def post_share_snapshots(self, **kwargs):
return (202, {}, {'share-snapshot': {}})
def delete_shares_1234(self, **kw):
return (202, {}, None)
def delete_share_snapshots_1234(self, **kwargs):
return (202, {}, None)

View File

@ -1,390 +0,0 @@
# Copyright (c) 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.
import json
import mock
import requests
from cinderclient import exceptions
from cinderclient.v2 import client
from tests import utils
class AuthenticateAgainstKeystoneTests(utils.TestCase):
def test_authenticate_success(self):
cs = client.Client("username", "password", "project_id",
"auth_url/v2.0", service_type='compute')
resp = {
"access": {
"token": {
"expires": "12345",
"id": "FAKE_ID",
},
"serviceCatalog": [
{
"type": "compute",
"endpoints": [
{
"region": "RegionOne",
"adminURL": "http://localhost:8774/v2",
"internalURL": "http://localhost:8774/v2",
"publicURL": "http://localhost:8774/v2/",
},
],
},
],
},
}
auth_response = utils.TestResponse({
"status_code": 200,
"text": json.dumps(resp),
})
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
cs.client.authenticate()
headers = {
'User-Agent': cs.client.USER_AGENT,
'Content-Type': 'application/json',
'Accept': 'application/json',
}
body = {
'auth': {
'passwordCredentials': {
'username': cs.client.user,
'password': cs.client.password,
},
'tenantName': cs.client.projectid,
},
}
token_url = cs.client.auth_url + "/tokens"
mock_request.assert_called_with(
"POST",
token_url,
headers=headers,
data=json.dumps(body),
allow_redirects=True,
**self.TEST_REQUEST_BASE)
endpoints = resp["access"]["serviceCatalog"][0]['endpoints']
public_url = endpoints[0]["publicURL"].rstrip('/')
self.assertEqual(cs.client.management_url, public_url)
token_id = resp["access"]["token"]["id"]
self.assertEqual(cs.client.auth_token, token_id)
test_auth_call()
def test_authenticate_tenant_id(self):
cs = client.Client("username", "password", auth_url="auth_url/v2.0",
tenant_id='tenant_id', service_type='compute')
resp = {
"access": {
"token": {
"expires": "12345",
"id": "FAKE_ID",
"tenant": {
"description": None,
"enabled": True,
"id": "tenant_id",
"name": "demo"
} # tenant associated with token
},
"serviceCatalog": [
{
"type": "compute",
"endpoints": [
{
"region": "RegionOne",
"adminURL": "http://localhost:8774/v2",
"internalURL": "http://localhost:8774/v2",
"publicURL": "http://localhost:8774/v2/",
},
],
},
],
},
}
auth_response = utils.TestResponse({
"status_code": 200,
"text": json.dumps(resp),
})
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
cs.client.authenticate()
headers = {
'User-Agent': cs.client.USER_AGENT,
'Content-Type': 'application/json',
'Accept': 'application/json',
}
body = {
'auth': {
'passwordCredentials': {
'username': cs.client.user,
'password': cs.client.password,
},
'tenantId': cs.client.tenant_id,
},
}
token_url = cs.client.auth_url + "/tokens"
mock_request.assert_called_with(
"POST",
token_url,
headers=headers,
data=json.dumps(body),
allow_redirects=True,
**self.TEST_REQUEST_BASE)
endpoints = resp["access"]["serviceCatalog"][0]['endpoints']
public_url = endpoints[0]["publicURL"].rstrip('/')
self.assertEqual(cs.client.management_url, public_url)
token_id = resp["access"]["token"]["id"]
self.assertEqual(cs.client.auth_token, token_id)
tenant_id = resp["access"]["token"]["tenant"]["id"]
self.assertEqual(cs.client.tenant_id, tenant_id)
test_auth_call()
def test_authenticate_failure(self):
cs = client.Client("username", "password", "project_id",
"auth_url/v2.0")
resp = {"unauthorized": {"message": "Unauthorized", "code": "401"}}
auth_response = utils.TestResponse({
"status_code": 401,
"text": json.dumps(resp),
})
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
self.assertRaises(exceptions.Unauthorized, cs.client.authenticate)
test_auth_call()
def test_auth_redirect(self):
cs = client.Client("username", "password", "project_id",
"auth_url/v2", service_type='compute')
dict_correct_response = {
"access": {
"token": {
"expires": "12345",
"id": "FAKE_ID",
},
"serviceCatalog": [
{
"type": "compute",
"endpoints": [
{
"adminURL": "http://localhost:8774/v2",
"region": "RegionOne",
"internalURL": "http://localhost:8774/v2",
"publicURL": "http://localhost:8774/v2/",
},
],
},
],
},
}
correct_response = json.dumps(dict_correct_response)
dict_responses = [
{"headers": {'location':'http://127.0.0.1:5001'},
"status_code": 305,
"text": "Use proxy"},
# Configured on admin port, cinder redirects to v2.0 port.
# When trying to connect on it, keystone auth succeed by v1.0
# protocol (through headers) but tokens are being returned in
# body (looks like keystone bug). Leaved for compatibility.
{"headers": {},
"status_code": 200,
"text": correct_response},
{"headers": {},
"status_code": 200,
"text": correct_response}
]
responses = [(utils.TestResponse(resp)) for resp in dict_responses]
def side_effect(*args, **kwargs):
return responses.pop(0)
mock_request = mock.Mock(side_effect=side_effect)
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
cs.client.authenticate()
headers = {
'User-Agent': cs.client.USER_AGENT,
'Content-Type': 'application/json',
'Accept': 'application/json',
}
body = {
'auth': {
'passwordCredentials': {
'username': cs.client.user,
'password': cs.client.password,
},
'tenantName': cs.client.projectid,
},
}
token_url = cs.client.auth_url + "/tokens"
mock_request.assert_called_with(
"POST",
token_url,
headers=headers,
data=json.dumps(body),
allow_redirects=True,
**self.TEST_REQUEST_BASE)
resp = dict_correct_response
endpoints = resp["access"]["serviceCatalog"][0]['endpoints']
public_url = endpoints[0]["publicURL"].rstrip('/')
self.assertEqual(cs.client.management_url, public_url)
token_id = resp["access"]["token"]["id"]
self.assertEqual(cs.client.auth_token, token_id)
test_auth_call()
def test_ambiguous_endpoints(self):
cs = client.Client("username", "password", "project_id",
"auth_url/v2.0", service_type='compute')
resp = {
"access": {
"token": {
"expires": "12345",
"id": "FAKE_ID",
},
"serviceCatalog": [
{
"adminURL": "http://localhost:8774/v2",
"type": "compute",
"name": "Compute CLoud",
"endpoints": [
{
"region": "RegionOne",
"internalURL": "http://localhost:8774/v2",
"publicURL": "http://localhost:8774/v2/",
},
],
},
{
"adminURL": "http://localhost:8774/v2",
"type": "compute",
"name": "Hyper-compute Cloud",
"endpoints": [
{
"internalURL": "http://localhost:8774/v2",
"publicURL": "http://localhost:8774/v2/",
},
],
},
],
},
}
auth_response = utils.TestResponse({
"status_code": 200,
"text": json.dumps(resp),
})
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
self.assertRaises(exceptions.AmbiguousEndpoints,
cs.client.authenticate)
test_auth_call()
class AuthenticationTests(utils.TestCase):
def test_authenticate_success(self):
cs = client.Client("username", "password", "project_id", "auth_url")
management_url = 'https://localhost/v2.1/443470'
auth_response = utils.TestResponse({
'status_code': 204,
'headers': {
'x-server-management-url': management_url,
'x-auth-token': '1b751d74-de0c-46ae-84f0-915744b582d1',
},
})
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
cs.client.authenticate()
headers = {
'Accept': 'application/json',
'X-Auth-User': 'username',
'X-Auth-Key': 'password',
'X-Auth-Project-Id': 'project_id',
'User-Agent': cs.client.USER_AGENT
}
mock_request.assert_called_with(
"GET",
cs.client.auth_url,
headers=headers,
**self.TEST_REQUEST_BASE)
self.assertEqual(cs.client.management_url,
auth_response.headers['x-server-management-url'])
self.assertEqual(cs.client.auth_token,
auth_response.headers['x-auth-token'])
test_auth_call()
def test_authenticate_failure(self):
cs = client.Client("username", "password", "project_id", "auth_url")
auth_response = utils.TestResponse({"status_code": 401})
mock_request = mock.Mock(return_value=(auth_response))
@mock.patch.object(requests, "request", mock_request)
def test_auth_call():
self.assertRaises(exceptions.Unauthorized, cs.client.authenticate)
test_auth_call()
def test_auth_automatic(self):
cs = client.Client("username", "password", "project_id", "auth_url")
http_client = cs.client
http_client.management_url = ''
mock_request = mock.Mock(return_value=(None, None))
@mock.patch.object(http_client, 'request', mock_request)
@mock.patch.object(http_client, 'authenticate')
def test_auth_call(m):
http_client.get('/')
m.assert_called()
mock_request.assert_called()
test_auth_call()
def test_auth_manual(self):
cs = client.Client("username", "password", "project_id", "auth_url")
@mock.patch.object(cs.client, 'authenticate')
def test_auth_call(m):
cs.authenticate()
m.assert_called()
test_auth_call()

View File

@ -1,42 +0,0 @@
# 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 tests import utils
from tests.v2 import fakes
cs = fakes.FakeClient()
class QuotaClassSetsTest(utils.TestCase):
def test_class_quotas_get(self):
class_name = 'test'
cs.quota_classes.get(class_name)
cs.assert_called('GET', '/os-quota-class-sets/%s' % class_name)
def test_update_quota(self):
q = cs.quota_classes.get('test')
q.update(volumes=2)
cs.assert_called('PUT', '/os-quota-class-sets/test')
def test_refresh_quota(self):
q = cs.quota_classes.get('test')
q2 = cs.quota_classes.get('test')
self.assertEqual(q.volumes, q2.volumes)
q2.volumes = 0
self.assertNotEqual(q.volumes, q2.volumes)
q2.get()
self.assertEqual(q.volumes, q2.volumes)

View File

@ -1,52 +0,0 @@
# 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 tests import utils
from tests.v2 import fakes
cs = fakes.FakeClient()
class QuotaSetsTest(utils.TestCase):
def test_tenant_quotas_get(self):
tenant_id = 'test'
cs.quotas.get(tenant_id)
cs.assert_called('GET', '/os-quota-sets/%s' % tenant_id)
def test_tenant_quotas_defaults(self):
tenant_id = 'test'
cs.quotas.defaults(tenant_id)
cs.assert_called('GET', '/os-quota-sets/%s/defaults' % tenant_id)
def test_update_quota(self):
q = cs.quotas.get('test')
q.update(volumes=2)
q.update(snapshots=2)
cs.assert_called('PUT', '/os-quota-sets/test')
def test_refresh_quota(self):
q = cs.quotas.get('test')
q2 = cs.quotas.get('test')
self.assertEqual(q.volumes, q2.volumes)
self.assertEqual(q.snapshots, q2.snapshots)
q2.volumes = 0
self.assertNotEqual(q.volumes, q2.volumes)
q2.snapshots = 0
self.assertNotEqual(q.snapshots, q2.snapshots)
q2.get()
self.assertEqual(q.volumes, q2.volumes)
self.assertEqual(q.snapshots, q2.snapshots)

View File

@ -1,159 +0,0 @@
# 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.
import fixtures
from cinderclient import client
from cinderclient import shell
from tests import utils
from tests.v2 import fakes
class ShellTest(utils.TestCase):
FAKE_ENV = {
'CINDER_USERNAME': 'username',
'CINDER_PASSWORD': 'password',
'CINDER_PROJECT_ID': 'project_id',
'OS_VOLUME_API_VERSION': '2',
'CINDER_URL': 'http://no.where',
}
# Patch os.environ to avoid required auth info.
def setUp(self):
"""Run before each test."""
super(ShellTest, self).setUp()
for var in self.FAKE_ENV:
self.useFixture(fixtures.EnvironmentVariable(var,
self.FAKE_ENV[var]))
self.shell = shell.OpenStackCinderShell()
#HACK(bcwaldon): replace this when we start using stubs
self.old_get_client_class = client.get_client_class
client.get_client_class = lambda *_: fakes.FakeClient
def tearDown(self):
# For some method like test_image_meta_bad_action we are
# testing a SystemExit to be thrown and object self.shell has
# no time to get instantatiated which is OK in this case, so
# we make sure the method is there before launching it.
if hasattr(self.shell, 'cs'):
self.shell.cs.clear_callstack()
#HACK(bcwaldon): replace this when we start using stubs
client.get_client_class = self.old_get_client_class
super(ShellTest, self).tearDown()
def run_command(self, cmd):
self.shell.main(cmd.split())
def assert_called(self, method, url, body=None, **kwargs):
return self.shell.cs.assert_called(method, url, body, **kwargs)
def assert_called_anytime(self, method, url, body=None):
return self.shell.cs.assert_called_anytime(method, url, body)
def test_list(self):
self.run_command('list')
# NOTE(jdg): we default to detail currently
self.assert_called('GET', '/volumes/detail')
def test_list_filter_status(self):
self.run_command('list --status=available')
self.assert_called('GET', '/volumes/detail?status=available')
def test_list_filter_name(self):
self.run_command('list --name=1234')
self.assert_called('GET', '/volumes/detail?name=1234')
def test_list_all_tenants(self):
self.run_command('list --all-tenants=1')
self.assert_called('GET', '/volumes/detail?all_tenants=1')
def test_show(self):
self.run_command('show 1234')
self.assert_called('GET', '/volumes/1234')
def test_delete(self):
self.run_command('delete 1234')
self.assert_called('DELETE', '/volumes/1234')
def test_snapshot_list_filter_volume_id(self):
self.run_command('snapshot-list --volume-id=1234')
self.assert_called('GET', '/snapshots/detail?volume_id=1234')
def test_snapshot_list_filter_status_and_volume_id(self):
self.run_command('snapshot-list --status=available --volume-id=1234')
self.assert_called('GET', '/snapshots/detail?'
'status=available&volume_id=1234')
def test_rename(self):
# basic rename with positional agruments
self.run_command('rename 1234 new-name')
expected = {'volume': {'name': 'new-name'}}
self.assert_called('PUT', '/volumes/1234', body=expected)
# change description only
self.run_command('rename 1234 --description=new-description')
expected = {'volume': {'description': 'new-description'}}
self.assert_called('PUT', '/volumes/1234', body=expected)
# rename and change description
self.run_command('rename 1234 new-name '
'--description=new-description')
expected = {'volume': {
'name': 'new-name',
'description': 'new-description',
}}
self.assert_called('PUT', '/volumes/1234', body=expected)
# noop, the only all will be the lookup
self.run_command('rename 1234')
self.assert_called('GET', '/volumes/1234')
def test_rename_snapshot(self):
# basic rename with positional agruments
self.run_command('snapshot-rename 1234 new-name')
expected = {'snapshot': {'name': 'new-name'}}
self.assert_called('PUT', '/snapshots/1234', body=expected)
# change description only
self.run_command('snapshot-rename 1234 '
'--description=new-description')
expected = {'snapshot': {'description': 'new-description'}}
self.assert_called('PUT', '/snapshots/1234', body=expected)
# snapshot-rename and change description
self.run_command('snapshot-rename 1234 new-name '
'--description=new-description')
expected = {'snapshot': {
'name': 'new-name',
'description': 'new-description',
}}
self.assert_called('PUT', '/snapshots/1234', body=expected)
# noop, the only all will be the lookup
self.run_command('snapshot-rename 1234')
self.assert_called('GET', '/snapshots/1234')
def test_set_metadata_set(self):
self.run_command('metadata 1234 set key1=val1 key2=val2')
self.assert_called('POST', '/volumes/1234/metadata',
{'metadata': {'key1': 'val1', 'key2': 'val2'}})
def test_set_metadata_delete_dict(self):
self.run_command('metadata 1234 unset key1=val1 key2=val2')
self.assert_called('DELETE', '/volumes/1234/metadata/key1')
self.assert_called('DELETE', '/volumes/1234/metadata/key2', pos=-2)
def test_set_metadata_delete_keys(self):
self.run_command('metadata 1234 unset key1 key2')
self.assert_called('DELETE', '/volumes/1234/metadata/key1')
self.assert_called('DELETE', '/volumes/1234/metadata/key2', pos=-2)

View File

@ -1,50 +0,0 @@
# Copyright (c) 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 cinderclient.v2 import volume_types
from tests import utils
from tests.v2 import fakes
cs = fakes.FakeClient()
class TypesTest(utils.TestCase):
def test_list_types(self):
tl = cs.volume_types.list()
cs.assert_called('GET', '/types')
for t in tl:
self.assertTrue(isinstance(t, volume_types.VolumeType))
def test_create(self):
t = cs.volume_types.create('test-type-3')
cs.assert_called('POST', '/types')
self.assertTrue(isinstance(t, volume_types.VolumeType))
def test_set_key(self):
t = cs.volume_types.get(1)
t.set_keys({'k': 'v'})
cs.assert_called('POST',
'/types/1/extra_specs',
{'extra_specs': {'k': 'v'}})
def test_unsset_keys(self):
t = cs.volume_types.get(1)
t.unset_keys(['k'])
cs.assert_called('DELETE', '/types/1/extra_specs/k')
def test_delete(self):
cs.volume_types.delete(1)
cs.assert_called('DELETE', '/types/1')

View File

@ -1,53 +0,0 @@
# Copyright (C) 2013 Hewlett-Packard Development Company, L.P.
# 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 tests import utils
from tests.v2 import fakes
cs = fakes.FakeClient()
class VolumeBackupsTest(utils.TestCase):
def test_create(self):
cs.backups.create('2b695faf-b963-40c8-8464-274008fbcef4')
cs.assert_called('POST', '/backups')
def test_get(self):
backup_id = '76a17945-3c6f-435c-975b-b5685db10b62'
cs.backups.get(backup_id)
cs.assert_called('GET', '/backups/%s' % backup_id)
def test_list(self):
cs.backups.list()
cs.assert_called('GET', '/backups/detail')
def test_delete(self):
b = cs.backups.list()[0]
b.delete()
cs.assert_called('DELETE',
'/backups/76a17945-3c6f-435c-975b-b5685db10b62')
cs.backups.delete('76a17945-3c6f-435c-975b-b5685db10b62')
cs.assert_called('DELETE',
'/backups/76a17945-3c6f-435c-975b-b5685db10b62')
cs.backups.delete(b)
cs.assert_called('DELETE',
'/backups/76a17945-3c6f-435c-975b-b5685db10b62')
def test_restore(self):
backup_id = '76a17945-3c6f-435c-975b-b5685db10b62'
cs.restores.restore(backup_id)
cs.assert_called('POST', '/backups/%s/restore' % backup_id)

View File

@ -1,87 +0,0 @@
# Copyright (c) 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 tests import utils
from tests.v2 import fakes
cs = fakes.FakeClient()
class VolumesTest(utils.TestCase):
def test_delete_volume(self):
v = cs.volumes.list()[0]
v.delete()
cs.assert_called('DELETE', '/volumes/1234')
cs.volumes.delete('1234')
cs.assert_called('DELETE', '/volumes/1234')
cs.volumes.delete(v)
cs.assert_called('DELETE', '/volumes/1234')
def test_create_volume(self):
cs.volumes.create(1)
cs.assert_called('POST', '/volumes')
def test_attach(self):
v = cs.volumes.get('1234')
cs.volumes.attach(v, 1, '/dev/vdc')
cs.assert_called('POST', '/volumes/1234/action')
def test_detach(self):
v = cs.volumes.get('1234')
cs.volumes.detach(v)
cs.assert_called('POST', '/volumes/1234/action')
def test_reserve(self):
v = cs.volumes.get('1234')
cs.volumes.reserve(v)
cs.assert_called('POST', '/volumes/1234/action')
def test_unreserve(self):
v = cs.volumes.get('1234')
cs.volumes.unreserve(v)
cs.assert_called('POST', '/volumes/1234/action')
def test_begin_detaching(self):
v = cs.volumes.get('1234')
cs.volumes.begin_detaching(v)
cs.assert_called('POST', '/volumes/1234/action')
def test_roll_detaching(self):
v = cs.volumes.get('1234')
cs.volumes.roll_detaching(v)
cs.assert_called('POST', '/volumes/1234/action')
def test_initialize_connection(self):
v = cs.volumes.get('1234')
cs.volumes.initialize_connection(v, {})
cs.assert_called('POST', '/volumes/1234/action')
def test_terminate_connection(self):
v = cs.volumes.get('1234')
cs.volumes.terminate_connection(v, {})
cs.assert_called('POST', '/volumes/1234/action')
def test_set_metadata(self):
cs.volumes.set_metadata(1234, {'k1': 'v2'})
cs.assert_called('POST', '/volumes/1234/metadata',
{'metadata': {'k1': 'v2'}})
def test_delete_metadata(self):
keys = ['key1']
cs.volumes.delete_metadata(1234, keys)
cs.assert_called('DELETE', '/volumes/1234/metadata/key1')

View File

@ -1,15 +1,15 @@
_cinder()
_manila()
{
local cur prev opts
COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
opts="$(cinder bash_completion)"
opts="$(manila bash_completion)"
COMPLETION_CACHE=~/.cinderclient/*/*-cache
COMPLETION_CACHE=~/.manilaclient/*/*-cache
opts+=" "$(cat $COMPLETION_CACHE 2> /dev/null | tr '\n' ' ')
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) )
}
complete -F _cinder cinder
complete -F _manila manila

View File

@ -190,9 +190,9 @@ def install_dependencies(venv=VENV):
pip_install('-r', PIP_REQUIRES)
pip_install('-r', TEST_REQUIRES)
# Tell the virtual env how to "import cinder"
# Tell the virtual env how to "import manila"
pthfile = os.path.join(venv, "lib", PY_VERSION, "site-packages",
"cinderclient.pth")
"manilaclient.pth")
f = open(pthfile, 'w')
f.write("%s\n" % ROOT)
@ -203,12 +203,12 @@ def post_process():
def print_help():
help = """
python-cinderclient development environment setup is complete.
python-manilaclient development environment setup is complete.
python-cinderclient development uses virtualenv to track and manage Python
python-manilaclient development uses virtualenv to track and manage Python
dependencies while in development and testing.
To activate the python-cinderclient virtualenv for the extent of your
To activate the python-manilaclient virtualenv for the extent of your
current shell session you can run:
$ source .venv/bin/activate

View File

@ -1,4 +1,4 @@
argparse
prettytable>=0.6,<0.8
requests>=0.8
PrettyTable>=0.6,<0.8
requests>=1.1
simplejson>=2.0.9

View File

@ -1,10 +1,8 @@
distribute>=0.6.24
coverage
coverage>=3.6
discover
fixtures
mock
fixtures>=0.3.14
mock>=1.0
pep8==1.3.3
sphinx>=1.1.2
testrepository>=0.0.13
testtools>=0.9.22
testrepository>=0.0.17
testtools>=0.9.32

Some files were not shown because too many files have changed in this diff Show More