trunk merge
This commit is contained in:
commit
f53675a4dd
27
README.rst
27
README.rst
@ -36,11 +36,11 @@ Installing this package gets you a shell command, ``nova``, that you
|
||||
can use to interact with any Rackspace compatible API (including OpenStack).
|
||||
|
||||
You'll need to provide your OpenStack username and API key. You can do this
|
||||
with the ``--username``, ``--apikey`` and ``--projectid`` params, but it's easier to just
|
||||
with the ``--username``, ``--password`` and ``--projectid`` params, but it's easier to just
|
||||
set them as environment variables::
|
||||
|
||||
export NOVA_USERNAME=openstack
|
||||
export NOVA_API_KEY=yadayada
|
||||
export NOVA_PASSWORD=yadayada
|
||||
export NOVA_PROJECT_ID=myproject
|
||||
|
||||
You will also need to define the authentication url with ``--url`` and the
|
||||
@ -56,14 +56,15 @@ endpoint::
|
||||
export NOVA_URL=http://example.com:5000/v2.0/
|
||||
|
||||
Since Keystone can return multiple regions in the Service Catalog, you
|
||||
can specify the one you want with ``--region_name`` (or
|
||||
can specify the one you want with ``--region_name`` (or
|
||||
``export NOVA_REGION_NAME``). It defaults to the first in the list returned.
|
||||
|
||||
You'll find complete documentation on the shell by running
|
||||
``nova help``::
|
||||
|
||||
usage: nova [--username USERNAME] [--apikey APIKEY] [--projectid PROJECTID]
|
||||
usage: nova [--username USERNAME] [--password PASSWORD] [--projectid PROJECTID]
|
||||
[--url URL] [--version VERSION] [--region_name NAME]
|
||||
[--endpoint_name NAME]
|
||||
<subcommand> ...
|
||||
|
||||
Command-line interface to the OpenStack Nova API.
|
||||
@ -124,6 +125,8 @@ You'll find complete documentation on the shell by running
|
||||
secgroup-list List security groups for the curent tenant.
|
||||
secgroup-list-rules List rules for a security group.
|
||||
show Show details about the given server.
|
||||
suspend Suspend a server.
|
||||
unpause Unpause a server.
|
||||
unrescue Unrescue a server.
|
||||
volume-attach Attach a volume to a server.
|
||||
volume-create Add a new volume.
|
||||
@ -131,6 +134,14 @@ You'll find complete documentation on the shell by running
|
||||
volume-detach Detach a volume from a server.
|
||||
volume-list List all the volumes.
|
||||
volume-show Show details about a volume.
|
||||
volume-snapshot-create
|
||||
Add a new snapshot.
|
||||
volume-snapshot-delete
|
||||
Remove a snapshot.
|
||||
volume-snapshot-list
|
||||
List all the snapshots.
|
||||
volume-snapshot-show
|
||||
Show details about a snapshot.
|
||||
zone Show or edit a Child Zone
|
||||
zone-add Add a Child Zone.
|
||||
zone-boot Boot a server, considering Zones.
|
||||
@ -141,8 +152,8 @@ You'll find complete documentation on the shell by running
|
||||
|
||||
Optional arguments:
|
||||
--username USERNAME Defaults to env[NOVA_USERNAME].
|
||||
--apikey APIKEY Defaults to env[NOVA_API_KEY].
|
||||
--apikey PROJECTID Defaults to env[NOVA_PROJECT_ID].
|
||||
--password PASSWORD Defaults to env[NOVA_PASSWORD].
|
||||
--projectid PROJECTID Defaults to env[NOVA_PROJECT_ID].
|
||||
--url AUTH_URL Defaults to env[NOVA_URL] or
|
||||
https://auth.api.rackspacecloud.com/v1.0
|
||||
if undefined.
|
||||
@ -164,7 +175,7 @@ __ http://packages.python.org/python-novaclient/
|
||||
By way of a quick-start::
|
||||
|
||||
>>> import novaclient
|
||||
>>> nt = novaclient.OpenStack(USERNAME, API_KEY,PROJECT_ID [, AUTH_URL])
|
||||
>>> nt = novaclient.OpenStack(USERNAME, PASSWORD, PROJECT_ID [, AUTH_URL])
|
||||
>>> nt.flavors.list()
|
||||
[...]
|
||||
>>> nt.servers.list()
|
||||
@ -190,7 +201,7 @@ Quick-start using keystone::
|
||||
[...]
|
||||
>>> nt.keypairs.list()
|
||||
[...]
|
||||
|
||||
|
||||
# if you want to use the keystone api to modify users/tenants:
|
||||
>>> from novaclient import client
|
||||
>>> conn = client.HTTPClient(USER, PASS, TENANT, KEYSTONE_URL)
|
||||
|
@ -12,7 +12,7 @@ Usage
|
||||
First create an instance of :class:`OpenStack` with your credentials::
|
||||
|
||||
>>> from novaclient import OpenStack
|
||||
>>> nova = OpenStack(USERNAME, API_KEY, AUTH_URL)
|
||||
>>> nova = OpenStack(USERNAME, PASSWORD, AUTH_URL)
|
||||
|
||||
Then call methods on the :class:`OpenStack` object:
|
||||
|
||||
|
@ -11,7 +11,7 @@ First, you'll need an OpenStack Nova account and an API key. You get this
|
||||
by using the `nova-manage` command in OpenStack Nova.
|
||||
|
||||
You'll need to provide :program:`nova` with your OpenStack username and
|
||||
API key. You can do this with the :option:`--username`, :option:`--apikey`
|
||||
API key. You can do this with the :option:`--username`, :option:`--password`
|
||||
and :option:`--projectid` options, but it's easier to just set them as
|
||||
environment variables by setting two environment variables:
|
||||
|
||||
@ -19,9 +19,9 @@ environment variables by setting two environment variables:
|
||||
|
||||
Your OpenStack Nova username.
|
||||
|
||||
.. envvar:: NOVA_API_KEY
|
||||
.. envvar:: NOVA_PASSWORD
|
||||
|
||||
Your API key.
|
||||
Your password.
|
||||
|
||||
.. envvar:: NOVA_PROJECT_ID
|
||||
|
||||
@ -38,7 +38,7 @@ environment variables by setting two environment variables:
|
||||
For example, in Bash you'd use::
|
||||
|
||||
export NOVA_USERNAME=yourname
|
||||
export NOVA_API_KEY=yadayadayada
|
||||
export NOVA_PASSWORD=yadayadayada
|
||||
export NOVA_PROJECT_ID=myproject
|
||||
export NOVA_URL=http://...
|
||||
export NOVA_VERSION=1.0
|
||||
|
@ -37,12 +37,12 @@ class HTTPClient(httplib2.Http):
|
||||
|
||||
USER_AGENT = 'python-novaclient'
|
||||
|
||||
def __init__(self, user, apikey, projectid, auth_url, insecure=False,
|
||||
def __init__(self, user, password, projectid, auth_url, insecure=False,
|
||||
timeout=None, token=None, region_name=None,
|
||||
endpoint_name='publicURL'):
|
||||
super(HTTPClient, self).__init__(timeout=timeout)
|
||||
self.user = user
|
||||
self.apikey = apikey
|
||||
self.password = password
|
||||
self.projectid = projectid
|
||||
self.auth_url = auth_url
|
||||
self.version = 'v1.0'
|
||||
@ -242,7 +242,7 @@ class HTTPClient(httplib2.Http):
|
||||
raise NoTokenLookupException()
|
||||
|
||||
headers = {'X-Auth-User': self.user,
|
||||
'X-Auth-Key': self.apikey}
|
||||
'X-Auth-Key': self.password}
|
||||
if self.projectid:
|
||||
headers['X-Auth-Project-Id'] = self.projectid
|
||||
|
||||
@ -263,13 +263,22 @@ class HTTPClient(httplib2.Http):
|
||||
"""Authenticate against a v2.0 auth service."""
|
||||
body = {"auth": {
|
||||
"passwordCredentials": {"username": self.user,
|
||||
"password": self.apikey}}}
|
||||
"password": self.password}}}
|
||||
|
||||
if self.projectid:
|
||||
body['auth']['tenantName'] = self.projectid
|
||||
|
||||
token_url = urlparse.urljoin(url, "tokens")
|
||||
resp, body = self.request(token_url, "POST", body=body)
|
||||
|
||||
# Make sure we follow redirects when trying to reach Keystone
|
||||
tmp_follow_all_redirects = self.follow_all_redirects
|
||||
self.follow_all_redirects = True
|
||||
|
||||
try:
|
||||
resp, body = self.request(token_url, "POST", body=body)
|
||||
finally:
|
||||
self.follow_all_redirects = tmp_follow_all_redirects
|
||||
|
||||
return self._extract_service_catalog(url, resp, body)
|
||||
|
||||
def _munge_get_url(self, url):
|
||||
|
@ -62,9 +62,9 @@ class OpenStackComputeShell(object):
|
||||
default=env('NOVA_USERNAME'),
|
||||
help='Defaults to env[NOVA_USERNAME].')
|
||||
|
||||
parser.add_argument('--apikey',
|
||||
default=env('NOVA_API_KEY'),
|
||||
help='Defaults to env[NOVA_API_KEY].')
|
||||
parser.add_argument('--password',
|
||||
default=env('NOVA_PASSWORD'),
|
||||
help='Defaults to env[NOVA_PASSWORD].')
|
||||
|
||||
parser.add_argument('--projectid',
|
||||
default=env('NOVA_PROJECT_ID'),
|
||||
@ -157,7 +157,7 @@ class OpenStackComputeShell(object):
|
||||
self.do_help(args)
|
||||
return 0
|
||||
|
||||
user, apikey, projectid, url, region_name, endpoint_name, insecure = \
|
||||
user, password, projectid, url, region_name, endpoint_name, insecure =\
|
||||
args.username, args.apikey, args.projectid, args.url, \
|
||||
args.region_name, args.endpoint_name, args.insecure
|
||||
|
||||
@ -165,16 +165,16 @@ class OpenStackComputeShell(object):
|
||||
endpoint_name = 'publicURL'
|
||||
|
||||
#FIXME(usrleon): Here should be restrict for project id same as
|
||||
# for username or apikey but for compatibility it is not.
|
||||
# for username or password but for compatibility it is not.
|
||||
|
||||
if not user:
|
||||
raise exc.CommandError("You must provide a username, either"
|
||||
"via --username or via "
|
||||
"env[NOVA_USERNAME]")
|
||||
if not apikey:
|
||||
raise exc.CommandError("You must provide an API key, either"
|
||||
"via --apikey or via"
|
||||
"env[NOVA_API_KEY]")
|
||||
if not password:
|
||||
raise exc.CommandError("You must provide a password, either"
|
||||
"via --password or via"
|
||||
"env[NOVA_PASSWORD]")
|
||||
if options.version and options.version != '1.0':
|
||||
if not projectid:
|
||||
raise exc.CommandError("You must provide an projectid, either"
|
||||
@ -186,7 +186,7 @@ class OpenStackComputeShell(object):
|
||||
"via --url or via"
|
||||
"env[NOVA_URL")
|
||||
|
||||
self.cs = self.get_api_class(options.version)(user, apikey, projectid,
|
||||
self.cs = self.get_api_class(options.version)(user, password, projectid,
|
||||
url, insecure, region_name=region_name,
|
||||
endpoint_name=endpoint_name)
|
||||
|
||||
|
@ -14,7 +14,7 @@ class Client(object):
|
||||
|
||||
Create an instance with your creds::
|
||||
|
||||
>>> client = Client(USERNAME, API_KEY, PROJECT_ID, AUTH_URL)
|
||||
>>> client = Client(USERNAME, PASSWORD, PROJECT_ID, AUTH_URL)
|
||||
|
||||
Then call methods on its managers::
|
||||
|
||||
@ -25,7 +25,7 @@ class Client(object):
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, username, api_key, project_id, auth_url=None,
|
||||
def __init__(self, username, password, project_id, auth_url=None,
|
||||
insecure=False, timeout=None, token=None, region_name=None,
|
||||
endpoint_name='publicURL'):
|
||||
|
||||
@ -40,7 +40,7 @@ class Client(object):
|
||||
_auth_url = auth_url or 'https://auth.api.rackspacecloud.com/v1.0'
|
||||
|
||||
self.client = client.HTTPClient(username,
|
||||
api_key,
|
||||
password,
|
||||
project_id,
|
||||
_auth_url,
|
||||
insecure=insecure,
|
||||
|
@ -674,7 +674,8 @@ def do_delete(cs, args):
|
||||
@utils.arg('--api_url', dest='api_url', default=None, help='New URL.')
|
||||
@utils.arg('--zone_username', dest='zone_username', default=None,
|
||||
help='New zone username.')
|
||||
@utils.arg('--password', dest='password', default=None, help='New password.')
|
||||
@utils.arg('--zone_password', dest='zone_password', default=None,
|
||||
help='New password.')
|
||||
@utils.arg('--weight_offset', dest='weight_offset', default=None,
|
||||
help='Child Zone weight offset.')
|
||||
@utils.arg('--weight_scale', dest='weight_scale', default=None,
|
||||
@ -689,8 +690,8 @@ def do_zone(cs, args):
|
||||
zone_delta['api_url'] = args.api_url
|
||||
if args.zone_username:
|
||||
zone_delta['username'] = args.zone_username
|
||||
if args.password:
|
||||
zone_delta['password'] = args.password
|
||||
if args.zone_password:
|
||||
zone_delta['password'] = args.zone_password
|
||||
if args.weight_offset:
|
||||
zone_delta['weight_offset'] = args.weight_offset
|
||||
if args.weight_scale:
|
||||
@ -713,7 +714,7 @@ def do_zone_info(cs, args):
|
||||
@utils.arg('--zone_username', metavar='<zone_username>',
|
||||
help='Optional Authentication username. (Default=None)',
|
||||
default=None)
|
||||
@utils.arg('--password', metavar='<password>',
|
||||
@utils.arg('--zone_password', metavar='<zone_password>',
|
||||
help='Authentication password. (Default=None)',
|
||||
default=None)
|
||||
@utils.arg('--weight_offset', metavar='<weight_offset>',
|
||||
@ -725,7 +726,7 @@ def do_zone_info(cs, args):
|
||||
def do_zone_add(cs, args):
|
||||
"""Add a new child zone."""
|
||||
zone = cs.zones.create(args.zone_name, args.api_url,
|
||||
args.zone_username, args.password,
|
||||
args.zone_username, args.zone_password,
|
||||
args.weight_offset, args.weight_scale)
|
||||
utils.print_dict(zone._info)
|
||||
|
||||
|
@ -26,7 +26,7 @@ class BootingManagerWithFind(base.ManagerWithFind):
|
||||
meta=None, files=None, zone_blob=None, userdata=None,
|
||||
reservation_id=None, return_raw=False, min_count=None,
|
||||
max_count=None, security_groups=None, key_name=None,
|
||||
availability_zone=None):
|
||||
availability_zone=None, block_device_mapping=None, nics=None):
|
||||
"""
|
||||
Create (boot) a new server.
|
||||
|
||||
@ -51,6 +51,11 @@ class BootingManagerWithFind(base.ManagerWithFind):
|
||||
:param key_name: (optional extension) name of keypair to inject into
|
||||
the instance
|
||||
:param availability_zone: The :class:`Zone`.
|
||||
:param block_device_mapping: A dict of block device mappings for this
|
||||
server.
|
||||
:param nics: (optional extension) an ordered list of nics to be
|
||||
added to this server, with information about
|
||||
connected networks, fixed ips, etc.
|
||||
"""
|
||||
body = {"server": {
|
||||
"name": name,
|
||||
@ -99,5 +104,45 @@ class BootingManagerWithFind(base.ManagerWithFind):
|
||||
|
||||
if availability_zone:
|
||||
body["server"]["availability_zone"] = availability_zone
|
||||
|
||||
# Block device mappings are passed as a list of dictionaries
|
||||
if block_device_mapping:
|
||||
bdm = body['server']['block_device_mapping'] = []
|
||||
for device_name, mapping in block_device_mapping.items():
|
||||
#
|
||||
# The mapping is in the format:
|
||||
# <id>:[<type>]:[<size(GB)>]:[<delete_on_terminate>]
|
||||
#
|
||||
bdm_dict = {
|
||||
'device_name': device_name }
|
||||
|
||||
mapping_parts = mapping.split(':')
|
||||
id = mapping_parts[0]
|
||||
if len(mapping_parts) == 1:
|
||||
bdm_dict['volume_id'] = id
|
||||
if len(mapping_parts) > 1:
|
||||
type = mapping_parts[1]
|
||||
if type.startswith('snap'):
|
||||
bdm_dict['snapshot_id'] = id
|
||||
else:
|
||||
bdm_dict['volume_id'] = id
|
||||
if len(mapping_parts) > 2:
|
||||
bdm_dict['volume_size'] = mapping_parts[2]
|
||||
if len(mapping_parts) > 3:
|
||||
bdm_dict['delete_on_termination'] = mapping_parts[3]
|
||||
bdm.append(bdm_dict)
|
||||
|
||||
if nics:
|
||||
all_net_data = []
|
||||
for nic_info in nics:
|
||||
net_data = {}
|
||||
# if value is empty string, do not send value in body
|
||||
if nic_info['net-id']:
|
||||
net_data['uuid'] = nic_info['net-id']
|
||||
if nic_info['v4-fixed-ip']:
|
||||
net_data['fixed_ip'] = nic_info['v4-fixed-ip']
|
||||
all_net_data.append(net_data)
|
||||
body['server']['networks'] = all_net_data
|
||||
|
||||
return self._create(resource_url, body, response_key,
|
||||
return_raw=return_raw)
|
||||
|
@ -8,6 +8,7 @@ from novaclient.v1_1 import security_groups
|
||||
from novaclient.v1_1 import servers
|
||||
from novaclient.v1_1 import quotas
|
||||
from novaclient.v1_1 import volumes
|
||||
from novaclient.v1_1 import volume_snapshots
|
||||
from novaclient.v1_1 import zones
|
||||
|
||||
|
||||
@ -17,7 +18,7 @@ class Client(object):
|
||||
|
||||
Create an instance with your creds::
|
||||
|
||||
>>> client = Client(USERNAME, API_KEY, PROJECT_ID, AUTH_URL)
|
||||
>>> client = Client(USERNAME, PASSWORD, PROJECT_ID, AUTH_URL)
|
||||
|
||||
Then call methods on its managers::
|
||||
|
||||
@ -29,7 +30,7 @@ class Client(object):
|
||||
"""
|
||||
|
||||
# FIXME(jesse): project_id isn't required to authenticate
|
||||
def __init__(self, username, api_key, project_id, auth_url,
|
||||
def __init__(self, username, password, project_id, auth_url,
|
||||
insecure=False, timeout=None, token=None, region_name=None,
|
||||
endpoint_name='publicURL'):
|
||||
self.flavors = flavors.FlavorManager(self)
|
||||
@ -39,6 +40,7 @@ class Client(object):
|
||||
|
||||
# extensions
|
||||
self.volumes = volumes.VolumeManager(self)
|
||||
self.volume_snapshots = volume_snapshots.SnapshotManager(self)
|
||||
self.keypairs = keypairs.KeypairManager(self)
|
||||
self.zones = zones.ZoneManager(self)
|
||||
self.quotas = quotas.QuotaSetManager(self)
|
||||
@ -47,7 +49,7 @@ class Client(object):
|
||||
security_group_rules.SecurityGroupRuleManager(self)
|
||||
|
||||
self.client = client.HTTPClient(username,
|
||||
api_key,
|
||||
password,
|
||||
project_id,
|
||||
auth_url,
|
||||
insecure=insecure,
|
||||
|
@ -327,7 +327,8 @@ class ServerManager(local_base.BootingManagerWithFind):
|
||||
def create(self, name, image, flavor, meta=None, files=None,
|
||||
zone_blob=None, reservation_id=None, min_count=None,
|
||||
max_count=None, security_groups=None, userdata=None,
|
||||
key_name=None, availability_zone=None):
|
||||
key_name=None, availability_zone=None,
|
||||
block_device_mapping=None, nics=None):
|
||||
# TODO: (anthony) indicate in doc string if param is an extension
|
||||
# and/or optional
|
||||
"""
|
||||
@ -354,6 +355,11 @@ class ServerManager(local_base.BootingManagerWithFind):
|
||||
:param key_name: (optional extension) name of previously created
|
||||
keypair to inject into the instance.
|
||||
:param availability_zone: The :class:`Zone`.
|
||||
:param block_device_mapping: (optional extension) A dict of block device
|
||||
mappings for this server.
|
||||
:param nics: (optional extension) an ordered list of nics to be
|
||||
added to this server, with information about
|
||||
connected networks, fixed ips, etc.
|
||||
"""
|
||||
if not min_count:
|
||||
min_count = 1
|
||||
@ -361,12 +367,22 @@ class ServerManager(local_base.BootingManagerWithFind):
|
||||
max_count = min_count
|
||||
if min_count > max_count:
|
||||
min_count = max_count
|
||||
return self._boot("/servers", "server", name, image, flavor,
|
||||
if block_device_mapping:
|
||||
return self._boot("/os-volumes_boot", "server",
|
||||
name, image, flavor,
|
||||
meta=meta, files=files, userdata=userdata,
|
||||
zone_blob=zone_blob, reservation_id=reservation_id,
|
||||
min_count=min_count, max_count=max_count,
|
||||
security_groups=security_groups, key_name=key_name,
|
||||
availability_zone=availability_zone,
|
||||
block_device_mapping=block_device_mapping)
|
||||
else:
|
||||
return self._boot("/servers", "server", name, image, flavor,
|
||||
meta=meta, files=files, userdata=userdata,
|
||||
zone_blob=zone_blob, reservation_id=reservation_id,
|
||||
min_count=min_count, max_count=max_count,
|
||||
security_groups=security_groups, key_name=key_name,
|
||||
availability_zone=availability_zone)
|
||||
availability_zone=availability_zone, nics=nics)
|
||||
|
||||
def update(self, server, name=None):
|
||||
"""
|
||||
|
@ -105,9 +105,23 @@ def _boot(cs, args, reservation_id=None, min_count=None, max_count=None):
|
||||
security_groups = args.security_groups.split(',')
|
||||
else:
|
||||
security_groups = None
|
||||
|
||||
block_device_mapping = {}
|
||||
for bdm in args.block_device_mapping:
|
||||
device_name, mapping = bdm.split('=', 1)
|
||||
block_device_mapping[device_name] = mapping
|
||||
|
||||
nics = []
|
||||
for nic_str in args.nics:
|
||||
nic_info = {"net-id": "", "v4-fixed-ip": ""}
|
||||
for kv_str in nic_str.split(","):
|
||||
k,v = kv_str.split("=")
|
||||
nic_info[k] = v
|
||||
nics.append(nic_info)
|
||||
|
||||
return (args.name, image, flavor, metadata, files, key_name,
|
||||
reservation_id, min_count, max_count, user_data, \
|
||||
availability_zone, security_groups)
|
||||
availability_zone, security_groups, block_device_mapping, nics)
|
||||
|
||||
|
||||
@utils.arg('--flavor',
|
||||
@ -155,11 +169,26 @@ def _boot(cs, args, reservation_id=None, min_count=None, max_count=None):
|
||||
default=None,
|
||||
metavar='<security_groups>',
|
||||
help="comma separated list of security group names.")
|
||||
@utils.arg('--block_device_mapping',
|
||||
metavar="<dev_name=mapping>",
|
||||
action='append',
|
||||
default=[],
|
||||
help="Block device mapping in the format "
|
||||
"<dev_name=<id>:<type>:<size(GB)>:<delete_on_terminate>.")
|
||||
@utils.arg('--nic',
|
||||
metavar="<net-id=net-uuid,v4-fixed-ip=ip-addr>",
|
||||
action='append',
|
||||
dest='nics',
|
||||
default=[],
|
||||
help="Create a NIC on the server.\n"
|
||||
"Specify option multiple times to create multiple NICs.\n"
|
||||
"net-id: attach NIC to network with this UUID (optional)\n"
|
||||
"v4-fixed-ip: IPv4 fixed address for NIC (optional).")
|
||||
def do_boot(cs, args):
|
||||
"""Boot a new server."""
|
||||
name, image, flavor, metadata, files, key_name, reservation_id, \
|
||||
min_count, max_count, user_data, availability_zone, \
|
||||
security_groups = _boot(cs, args)
|
||||
security_groups, block_device_mapping, nics = _boot(cs, args)
|
||||
|
||||
server = cs.servers.create(args.name, image, flavor,
|
||||
meta=metadata,
|
||||
@ -169,7 +198,9 @@ def do_boot(cs, args):
|
||||
userdata=user_data,
|
||||
availability_zone=availability_zone,
|
||||
security_groups=security_groups,
|
||||
key_name=key_name)
|
||||
key_name=key_name,
|
||||
block_device_mapping=block_device_mapping,
|
||||
nics=nics)
|
||||
|
||||
# Keep any information (like adminPass) returned by create
|
||||
info = server._info
|
||||
@ -455,15 +486,16 @@ def do_reboot(cs, args):
|
||||
|
||||
@utils.arg('server', metavar='<server>', help='Name or ID of server.')
|
||||
@utils.arg('image', metavar='<image>', help="Name or ID of new image.")
|
||||
@utils.arg('--password', dest='password', metavar='<password>', default=False,
|
||||
@utils.arg('--rebuild_password', dest='rebuild_password',
|
||||
metavar='<rebuild_password>', default=False,
|
||||
help="Set the provided password on the rebuild instance.")
|
||||
def do_rebuild(cs, args):
|
||||
"""Shutdown, re-image, and re-boot a server."""
|
||||
server = _find_server(cs, args.server)
|
||||
image = _find_image(cs, args.image)
|
||||
|
||||
if args.password != False:
|
||||
_password = args.password
|
||||
if args.rebuild_password != False:
|
||||
_password = args.rebuild_password
|
||||
else:
|
||||
_password = None
|
||||
|
||||
@ -675,7 +707,8 @@ def _find_flavor(cs, flavor):
|
||||
@utils.arg('--api_url', dest='api_url', default=None, help='New URL.')
|
||||
@utils.arg('--zone_username', dest='zone_username', default=None,
|
||||
help='New zone username.')
|
||||
@utils.arg('--password', dest='password', default=None, help='New password.')
|
||||
@utils.arg('--zone_password', dest='zone_password', default=None,
|
||||
help='New password.')
|
||||
@utils.arg('--weight_offset', dest='weight_offset', default=None,
|
||||
help='Child Zone weight offset.')
|
||||
@utils.arg('--weight_scale', dest='weight_scale', default=None,
|
||||
@ -690,8 +723,8 @@ def do_zone(cs, args):
|
||||
zone_delta['api_url'] = args.api_url
|
||||
if args.zone_username:
|
||||
zone_delta['username'] = args.zone_username
|
||||
if args.password:
|
||||
zone_delta['password'] = args.password
|
||||
if args.zone_password:
|
||||
zone_delta['password'] = args.zone_password
|
||||
if args.weight_offset:
|
||||
zone_delta['weight_offset'] = args.weight_offset
|
||||
if args.weight_scale:
|
||||
@ -714,7 +747,7 @@ def do_zone_info(cs, args):
|
||||
@utils.arg('--zone_username', metavar='<zone_username>',
|
||||
help='Optional Authentication username. (Default=None)',
|
||||
default=None)
|
||||
@utils.arg('--password', metavar='<password>',
|
||||
@utils.arg('--zone_password', metavar='<zone_password>',
|
||||
help='Authentication password. (Default=None)',
|
||||
default=None)
|
||||
@utils.arg('--weight_offset', metavar='<weight_offset>',
|
||||
@ -726,7 +759,7 @@ def do_zone_info(cs, args):
|
||||
def do_zone_add(cs, args):
|
||||
"""Add a new child zone."""
|
||||
zone = cs.zones.create(args.zone_name, args.api_url,
|
||||
args.zone_username, args.password,
|
||||
args.zone_username, args.zone_password,
|
||||
args.weight_offset, args.weight_scale)
|
||||
utils.print_dict(zone._info)
|
||||
|
||||
@ -764,10 +797,19 @@ def _find_volume(cs, volume):
|
||||
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 _print_volume(cs, volume):
|
||||
utils.print_dict(volume._info)
|
||||
|
||||
|
||||
def _print_volume_snapshot(cs, snapshot):
|
||||
utils.print_dict(snapshot._info)
|
||||
|
||||
|
||||
def _translate_volume_keys(collection):
|
||||
convert = [('displayName', 'display_name')]
|
||||
for item in collection:
|
||||
@ -777,6 +819,15 @@ def _translate_volume_keys(collection):
|
||||
setattr(item, to_key, item._info[from_key])
|
||||
|
||||
|
||||
def _translate_volume_snapshot_keys(collection):
|
||||
convert = [('displayName', 'display_name'), ('volumeId', 'volume_id')]
|
||||
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 do_volume_list(cs, args):
|
||||
"""List all the volumes."""
|
||||
volumes = cs.volumes.list()
|
||||
@ -801,6 +852,10 @@ def do_volume_show(cs, args):
|
||||
metavar='<size>',
|
||||
type=int,
|
||||
help='Size of volume in GB')
|
||||
@utils.arg('--snapshot_id',
|
||||
metavar='<snapshot_id>',
|
||||
help='Optional snapshot id to create the volume from. (Default=None)',
|
||||
default=None)
|
||||
@utils.arg('--display_name', metavar='<display_name>',
|
||||
help='Optional volume name. (Default=None)',
|
||||
default=None)
|
||||
@ -809,7 +864,10 @@ def do_volume_show(cs, args):
|
||||
default=None)
|
||||
def do_volume_create(cs, args):
|
||||
"""Add a new volume."""
|
||||
cs.volumes.create(args.size, args.display_name, args.display_description)
|
||||
cs.volumes.create(args.size,
|
||||
args.snapshot_id,
|
||||
args.display_name,
|
||||
args.display_description)
|
||||
|
||||
|
||||
@utils.arg('volume', metavar='<volume>', help='ID of the volume to delete.')
|
||||
@ -847,6 +905,52 @@ def do_volume_detach(cs, args):
|
||||
cs.volumes.delete_server_volume(_find_server(cs, args.server).id,
|
||||
args.attachment_id)
|
||||
|
||||
def do_volume_snapshot_list(cs, args):
|
||||
"""List all the snapshots."""
|
||||
snapshots = cs.volume_snapshots.list()
|
||||
_translate_volume_snapshot_keys(snapshots)
|
||||
utils.print_list(snapshots, ['ID', 'Volume ID', 'Status', 'Display Name',
|
||||
'Size'])
|
||||
|
||||
|
||||
@utils.arg('snapshot', metavar='<snapshot>', help='ID of the snapshot.')
|
||||
def do_volume_snapshot_show(cs, args):
|
||||
"""Show details about a snapshot."""
|
||||
snapshot = _find_volume_snapshot(cs, args.snapshot)
|
||||
_print_volume_snapshot(cs, snapshot)
|
||||
|
||||
|
||||
@utils.arg('volume_id',
|
||||
metavar='<volume_id>',
|
||||
type=int,
|
||||
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 its '
|
||||
'attached to an instance. (Default=False)',
|
||||
default=False)
|
||||
@utils.arg('--display_name', metavar='<display_name>',
|
||||
help='Optional snapshot name. (Default=None)',
|
||||
default=None)
|
||||
@utils.arg('--display_description', metavar='<display_description>',
|
||||
help='Optional snapshot description. (Default=None)',
|
||||
default=None)
|
||||
def do_volume_snapshot_create(cs, args):
|
||||
"""Add a new snapshot."""
|
||||
cs.volume_snapshots.create(args.volume_id,
|
||||
args.force,
|
||||
args.display_name,
|
||||
args.display_description)
|
||||
|
||||
|
||||
@utils.arg('snapshot_id',
|
||||
metavar='<snapshot_id>',
|
||||
help='ID of the snapshot to delete.')
|
||||
def do_volume_snapshot_delete(cs, args):
|
||||
"""Remove a snapshot."""
|
||||
snapshot = _find_volume_snapshot(cs, args.snapshot_id)
|
||||
snapshot.delete()
|
||||
|
||||
|
||||
def _print_floating_ip_list(floating_ips):
|
||||
utils.print_list(floating_ips, ['Ip', 'Instance Id', 'Fixed Ip'])
|
||||
|
88
novaclient/v1_1/volume_snapshots.py
Normal file
88
novaclient/v1_1/volume_snapshots.py
Normal file
@ -0,0 +1,88 @@
|
||||
# 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).
|
||||
"""
|
||||
|
||||
from novaclient 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.
|
||||
"""
|
||||
return self.manager.delete(self)
|
||||
|
||||
|
||||
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('/os-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("/os-snapshots/%s" % snapshot_id, "snapshot")
|
||||
|
||||
def list(self, detailed=True):
|
||||
"""
|
||||
Get a list of all snapshots.
|
||||
|
||||
:rtype: list of :class:`Snapshot`
|
||||
"""
|
||||
if detailed is True:
|
||||
return self._list("/os-snapshots/detail", "snapshots")
|
||||
else:
|
||||
return self._list("/os-snapshots", "snapshots")
|
||||
|
||||
def delete(self, snapshot):
|
||||
"""
|
||||
Delete a snapshot.
|
||||
|
||||
:param snapshot: The :class:`Snapshot` to delete.
|
||||
"""
|
||||
self._delete("/os-snapshots/%s" % base.getid(snapshot))
|
@ -40,16 +40,19 @@ class VolumeManager(base.ManagerWithFind):
|
||||
"""
|
||||
resource_class = Volume
|
||||
|
||||
def create(self, size, display_name=None, display_description=None):
|
||||
def create(self, size, snapshot_id=None,
|
||||
display_name=None, display_description=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
|
||||
:rtype: :class:`Volume`
|
||||
"""
|
||||
body = {'volume': {'size': size,
|
||||
'snapshot_id': snapshot_id,
|
||||
'display_name': display_name,
|
||||
'display_description': display_description}}
|
||||
return self._create('/os-volumes', body, 'volume')
|
||||
|
@ -12,7 +12,7 @@ mock_request = mock.Mock(return_value=(fake_response, fake_body))
|
||||
|
||||
|
||||
def get_client():
|
||||
cl = client.HTTPClient("username", "apikey",
|
||||
cl = client.HTTPClient("username", "password",
|
||||
"project_id", "auth_test")
|
||||
return cl
|
||||
|
||||
|
@ -14,7 +14,7 @@ class ShellTest(utils.TestCase):
|
||||
global _old_env
|
||||
fake_env = {
|
||||
'NOVA_USERNAME': 'username',
|
||||
'NOVA_API_KEY': 'password',
|
||||
'NOVA_PASSWORD': 'password',
|
||||
'NOVA_PROJECT_ID': 'project_id',
|
||||
'NOVA_URL': 'http://no.where',
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ from tests import fakes
|
||||
class FakeClient(fakes.FakeClient, client.Client):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
client.Client.__init__(self, 'username', 'apikey',
|
||||
client.Client.__init__(self, 'username', 'password',
|
||||
'project_id', 'auth_url')
|
||||
self.client = FakeHTTPClient(**kwargs)
|
||||
|
||||
@ -18,7 +18,7 @@ class FakeClient(fakes.FakeClient, client.Client):
|
||||
class FakeHTTPClient(base_client.HTTPClient):
|
||||
def __init__(self, **kwargs):
|
||||
self.username = 'username'
|
||||
self.apikey = 'apikey'
|
||||
self.password = 'password'
|
||||
self.auth_url = 'auth_url'
|
||||
self.callstack = []
|
||||
|
||||
|
@ -9,7 +9,7 @@ from tests import utils
|
||||
class AuthenticationTests(utils.TestCase):
|
||||
|
||||
def test_authenticate_success(self):
|
||||
cs = client.Client("username", "apikey", "project_id")
|
||||
cs = client.Client("username", "password", "project_id")
|
||||
management_url = 'https://servers.api.rackspacecloud.com/v1.0/443470'
|
||||
auth_response = httplib2.Response({
|
||||
'status': 204,
|
||||
@ -23,7 +23,7 @@ class AuthenticationTests(utils.TestCase):
|
||||
cs.client.authenticate()
|
||||
headers = {
|
||||
'X-Auth-User': 'username',
|
||||
'X-Auth-Key': 'apikey',
|
||||
'X-Auth-Key': 'password',
|
||||
'X-Auth-Project-Id': 'project_id',
|
||||
'User-Agent': cs.client.USER_AGENT
|
||||
}
|
||||
@ -37,7 +37,7 @@ class AuthenticationTests(utils.TestCase):
|
||||
test_auth_call()
|
||||
|
||||
def test_authenticate_failure(self):
|
||||
cs = client.Client("username", "apikey", "project_id")
|
||||
cs = client.Client("username", "password", "project_id")
|
||||
auth_response = httplib2.Response({'status': 401})
|
||||
mock_request = mock.Mock(return_value=(auth_response, None))
|
||||
|
||||
@ -48,7 +48,7 @@ class AuthenticationTests(utils.TestCase):
|
||||
test_auth_call()
|
||||
|
||||
def test_auth_automatic(self):
|
||||
cs = client.Client("username", "apikey", "project_id")
|
||||
cs = client.Client("username", "password", "project_id")
|
||||
http_client = cs.client
|
||||
http_client.management_url = ''
|
||||
mock_request = mock.Mock(return_value=(None, None))
|
||||
@ -63,7 +63,7 @@ class AuthenticationTests(utils.TestCase):
|
||||
test_auth_call()
|
||||
|
||||
def test_auth_manual(self):
|
||||
cs = client.Client("username", "apikey", "project_id")
|
||||
cs = client.Client("username", "password", "project_id")
|
||||
|
||||
@mock.patch.object(cs.client, 'authenticate')
|
||||
def test_auth_call(m):
|
||||
|
@ -14,7 +14,7 @@ class ShellTest(utils.TestCase):
|
||||
self.old_environment = os.environ.copy()
|
||||
os.environ = {
|
||||
'NOVA_USERNAME': 'username',
|
||||
'NOVA_API_KEY': 'password',
|
||||
'NOVA_PASSWORD': 'password',
|
||||
'NOVA_PROJECT_ID': 'project_id',
|
||||
'NOVA_VERSION': '1.0',
|
||||
}
|
||||
@ -304,7 +304,7 @@ class ShellTest(utils.TestCase):
|
||||
self.assert_called('GET', '/zones/1')
|
||||
|
||||
self.run_command('zone 1 --api_url=http://zzz '
|
||||
'--zone_username=frank --password=xxx')
|
||||
'--zone_username=frank --zone_password=xxx')
|
||||
self.assert_called(
|
||||
'PUT', '/zones/1',
|
||||
{'zone': {'username': 'frank', 'password': 'xxx',
|
||||
@ -313,7 +313,7 @@ class ShellTest(utils.TestCase):
|
||||
|
||||
def test_zone_add(self):
|
||||
self.run_command('zone-add child_zone http://zzz '
|
||||
'--zone_username=frank --password=xxx '
|
||||
'--zone_username=frank --zone_password=xxx '
|
||||
'--weight_offset=0.0 --weight_scale=1.0')
|
||||
self.assert_called(
|
||||
'POST', '/zones',
|
||||
|
@ -8,7 +8,7 @@ from tests import fakes
|
||||
class FakeClient(fakes.FakeClient, client.Client):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
client.Client.__init__(self, 'username', 'apikey',
|
||||
client.Client.__init__(self, 'username', 'password',
|
||||
'project_id', 'auth_url')
|
||||
self.client = FakeHTTPClient(**kwargs)
|
||||
|
||||
@ -17,7 +17,7 @@ class FakeHTTPClient(base_client.HTTPClient):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.username = 'username'
|
||||
self.apikey = 'apikey'
|
||||
self.password = 'password'
|
||||
self.auth_url = 'auth_url'
|
||||
self.callstack = []
|
||||
|
||||
@ -324,7 +324,7 @@ class FakeHTTPClient(base_client.HTTPClient):
|
||||
]})
|
||||
|
||||
def get_os_floating_ips_1(self, **kw):
|
||||
return (200, {'floating_ip':
|
||||
return (200, {'floating_ip':
|
||||
{'id': 1, 'fixed_ip': '10.0.0.1', 'ip': '11.0.0.1'}
|
||||
})
|
||||
|
||||
|
@ -19,7 +19,7 @@ def to_http_response(resp_dict):
|
||||
|
||||
class AuthenticateAgainstKeystoneTests(utils.TestCase):
|
||||
def test_authenticate_success(self):
|
||||
cs = client.Client("username", "apikey", "project_id", "auth_url/v2.0")
|
||||
cs = client.Client("username", "password", "project_id", "auth_url/v2.0")
|
||||
resp = {"access":
|
||||
{"token": {"expires": "12345", "id": "FAKE_ID"},
|
||||
"serviceCatalog": [
|
||||
@ -44,7 +44,7 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
|
||||
'Content-Type': 'application/json', }
|
||||
body = {'auth': {
|
||||
'passwordCredentials': {'username': cs.client.user,
|
||||
'password': cs.client.apikey},
|
||||
'password': cs.client.password},
|
||||
'tenantName': cs.client.projectid, }}
|
||||
|
||||
token_url = urlparse.urljoin(cs.client.auth_url, "tokens")
|
||||
@ -60,7 +60,7 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
|
||||
test_auth_call()
|
||||
|
||||
def test_authenticate_failure(self):
|
||||
cs = client.Client("username", "apikey", "project_id", "auth_url/v2.0")
|
||||
cs = client.Client("username", "password", "project_id", "auth_url/v2.0")
|
||||
resp = {"unauthorized": {"message": "Unauthorized", "code": "401"}}
|
||||
auth_response = httplib2.Response({
|
||||
"status": 401,
|
||||
@ -77,7 +77,7 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
|
||||
test_auth_call()
|
||||
|
||||
def test_auth_redirect(self):
|
||||
cs = client.Client("username", "apikey", "project_id", "auth_url/v1.0")
|
||||
cs = client.Client("username", "password", "project_id", "auth_url/v1.0")
|
||||
dict_correct_response = {"access": {"token": {"expires": "12345", "id": "FAKE_ID"},
|
||||
"serviceCatalog": [{
|
||||
"type": "compute",
|
||||
@ -116,7 +116,7 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
|
||||
'Content-Type': 'application/json',}
|
||||
body = {'auth': {
|
||||
'passwordCredentials': {'username': cs.client.user,
|
||||
'password': cs.client.apikey},
|
||||
'password': cs.client.password},
|
||||
'tenantName': cs.client.projectid,}}
|
||||
|
||||
token_url = urlparse.urljoin(cs.client.auth_url, "tokens")
|
||||
@ -135,7 +135,7 @@ class AuthenticateAgainstKeystoneTests(utils.TestCase):
|
||||
|
||||
class AuthenticationTests(utils.TestCase):
|
||||
def test_authenticate_success(self):
|
||||
cs = client.Client("username", "apikey", "project_id", "auth_url")
|
||||
cs = client.Client("username", "password", "project_id", "auth_url")
|
||||
management_url = 'https://servers.api.rackspacecloud.com/v1.1/443470'
|
||||
auth_response = httplib2.Response({
|
||||
'status': 204,
|
||||
@ -149,7 +149,7 @@ class AuthenticationTests(utils.TestCase):
|
||||
cs.client.authenticate()
|
||||
headers = {
|
||||
'X-Auth-User': 'username',
|
||||
'X-Auth-Key': 'apikey',
|
||||
'X-Auth-Key': 'password',
|
||||
'X-Auth-Project-Id': 'project_id',
|
||||
'User-Agent': cs.client.USER_AGENT
|
||||
}
|
||||
@ -163,7 +163,7 @@ class AuthenticationTests(utils.TestCase):
|
||||
test_auth_call()
|
||||
|
||||
def test_authenticate_failure(self):
|
||||
cs = client.Client("username", "apikey", "project_id", "auth_url")
|
||||
cs = client.Client("username", "password", "project_id", "auth_url")
|
||||
auth_response = httplib2.Response({'status': 401})
|
||||
mock_request = mock.Mock(return_value=(auth_response, None))
|
||||
|
||||
@ -174,7 +174,7 @@ class AuthenticationTests(utils.TestCase):
|
||||
test_auth_call()
|
||||
|
||||
def test_auth_automatic(self):
|
||||
cs = client.Client("username", "apikey", "project_id", "auth_url")
|
||||
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))
|
||||
@ -189,7 +189,7 @@ class AuthenticationTests(utils.TestCase):
|
||||
test_auth_call()
|
||||
|
||||
def test_auth_manual(self):
|
||||
cs = client.Client("username", "apikey", "project_id", "auth_url")
|
||||
cs = client.Client("username", "password", "project_id", "auth_url")
|
||||
|
||||
@mock.patch.object(cs.client, 'authenticate')
|
||||
def test_auth_call(m):
|
||||
|
@ -17,7 +17,7 @@ class ShellTest(utils.TestCase):
|
||||
self.old_environment = os.environ.copy()
|
||||
os.environ = {
|
||||
'NOVA_USERNAME': 'username',
|
||||
'NOVA_API_KEY': 'password',
|
||||
'NOVA_PASSWORD': 'password',
|
||||
'NOVA_PROJECT_ID': 'project_id',
|
||||
'NOVA_VERSION': '1.1',
|
||||
'NOVA_URL': 'http://no.where',
|
||||
@ -228,7 +228,7 @@ class ShellTest(utils.TestCase):
|
||||
# {'rebuild': {'imageRef': 1}})
|
||||
self.assert_called('GET', '/images/2')
|
||||
|
||||
self.run_command('rebuild sample-server 1 --password asdf')
|
||||
self.run_command('rebuild sample-server 1 --rebuild_password asdf')
|
||||
# XXX need a way to test multiple calls
|
||||
#self.assert_called('POST', '/servers/1234/action',
|
||||
# {'rebuild': {'imageRef': 1, 'adminPass': 'asdf'}})
|
||||
@ -267,7 +267,7 @@ class ShellTest(utils.TestCase):
|
||||
self.assert_called('GET', '/images/2')
|
||||
|
||||
def test_show_bad_id(self):
|
||||
self.assertRaises(exceptions.CommandError,
|
||||
self.assertRaises(exceptions.CommandError,
|
||||
self.run_command, 'show xxx')
|
||||
|
||||
def test_delete(self):
|
||||
|
Loading…
x
Reference in New Issue
Block a user