trunk merge

This commit is contained in:
Sandy Walsh 2011-11-16 08:44:31 -08:00
commit f53675a4dd
21 changed files with 363 additions and 84 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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