summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrian Waldon <brian.waldon@rackspace.com>2011-07-29 10:06:05 -0400
committerBrian Waldon <brian.waldon@rackspace.com>2011-07-29 10:06:05 -0400
commitca8aa158fc0d1ab9f653f79f4bb5169a7884b0b6 (patch)
tree03a77d762a518d91bcd584e083991bda4ae3ca49
parent20251ccc2ff7a8d030647c92c9a5a288ed5c1229 (diff)
downloadpython-novaclient-ca8aa158fc0d1ab9f653f79f4bb5169a7884b0b6.tar.gz
updating server backup action; pep8 fixes
-rw-r--r--novaclient/accounts.py2
-rw-r--r--novaclient/base.py2
-rw-r--r--novaclient/client.py12
-rw-r--r--novaclient/exceptions.py1
-rw-r--r--novaclient/images.py21
-rw-r--r--novaclient/servers.py39
-rw-r--r--novaclient/shell.py44
-rw-r--r--novaclient/zones.py2
-rw-r--r--setup.py35
-rw-r--r--tests/fakeserver.py8
-rw-r--r--tests/test_accounts.py1
-rw-r--r--tests/test_servers.py8
-rw-r--r--tests/test_shell.py23
-rw-r--r--tests/test_zones.py2
14 files changed, 117 insertions, 83 deletions
diff --git a/novaclient/accounts.py b/novaclient/accounts.py
index 01ff2f52..28a9dedc 100644
--- a/novaclient/accounts.py
+++ b/novaclient/accounts.py
@@ -1,8 +1,10 @@
from novaclient import base
+
class Account(base.Resource):
pass
+
class AccountManager(base.BootingManagerWithFind):
resource_class = Account
diff --git a/novaclient/base.py b/novaclient/base.py
index e402039f..ee24ea76 100644
--- a/novaclient/base.py
+++ b/novaclient/base.py
@@ -68,7 +68,7 @@ class Manager(object):
obj_class = self.resource_class
return [obj_class(self, res)
for res in body[response_key] if res]
-
+
def _get(self, url, response_key):
resp, body = self.api.client.get(url)
return self.resource_class(self, body[response_key])
diff --git a/novaclient/client.py b/novaclient/client.py
index 103679a4..2d2f4846 100644
--- a/novaclient/client.py
+++ b/novaclient/client.py
@@ -24,6 +24,7 @@ from novaclient import exceptions
_logger = logging.getLogger(__name__)
+
class OpenStackClient(httplib2.Http):
USER_AGENT = 'python-novaclient/%s' % novaclient.__version__
@@ -45,19 +46,20 @@ class OpenStackClient(httplib2.Http):
def http_log(self, args, kwargs, resp, body):
if not _logger.isEnabledFor(logging.DEBUG):
return
-
+
string_parts = ['curl -i']
for element in args:
- if element in ('GET','POST'):
+ if element in ('GET', 'POST'):
string_parts.append(' -X %s' % element)
else:
string_parts.append(' %s' % element)
for element in kwargs['headers']:
- string_parts.append(' -H "%s: %s"' % (element,kwargs['headers'][element]))
+ header = ' -H "%s: %s"' % (element, kwargs['headers'][element])
+ string_parts.append(header)
_logger.debug("REQ: %s\n" % "".join(string_parts))
- _logger.debug("RESP:%s %s\n", resp,body)
+ _logger.debug("RESP:%s %s\n", resp, body)
def request(self, *args, **kwargs):
kwargs.setdefault('headers', {})
@@ -69,7 +71,7 @@ class OpenStackClient(httplib2.Http):
resp, body = super(OpenStackClient, self).request(*args, **kwargs)
self.http_log(args, kwargs, resp, body)
-
+
if body:
try:
body = json.loads(body)
diff --git a/novaclient/exceptions.py b/novaclient/exceptions.py
index 1709d806..64efb4b7 100644
--- a/novaclient/exceptions.py
+++ b/novaclient/exceptions.py
@@ -3,6 +3,7 @@
Exception definitions.
"""
+
class OpenStackException(Exception):
"""
The base exception class for all exceptions this library raises.
diff --git a/novaclient/images.py b/novaclient/images.py
index d911cc74..70691582 100644
--- a/novaclient/images.py
+++ b/novaclient/images.py
@@ -46,8 +46,7 @@ class ImageManager(base.ManagerWithFind):
detail = "/detail"
return self._list("/images%s" % detail, "images")
-
- def create(self, server, name, image_type=None, backup_type=None, rotation=None):
+ def create(self, server, name):
"""
Create a new image by snapshotting a running :class:`Server`
@@ -55,23 +54,7 @@ class ImageManager(base.ManagerWithFind):
:param server: The :class:`Server` (or its ID) to make a snapshot of.
:rtype: :class:`Image`
"""
- if image_type is None:
- image_type = "snapshot"
-
- if image_type not in ("backup", "snapshot"):
- raise Exception("Invalid image_type: must be backup or snapshot")
-
- if image_type == "backup":
- if not rotation:
- raise Exception("rotation is required for backups")
- elif not backup_type:
- raise Exception("backup_type required for backups")
- elif backup_type not in ("daily", "weekly"):
- raise Exception("Invalid backup_type: must be daily or weekly")
-
- data = {"image": {"serverId": base.getid(server), "name": name,
- "image_type": image_type, "backup_type": backup_type,
- "rotation": rotation}}
+ data = {"image": {"serverId": base.getid(server), "name": name}}
return self._create("/images", data, "image")
def delete(self, image):
diff --git a/novaclient/servers.py b/novaclient/servers.py
index 18392e4d..c9166228 100644
--- a/novaclient/servers.py
+++ b/novaclient/servers.py
@@ -154,6 +154,18 @@ class Server(base.Resource):
"""
self.manager.resize(self, flavor)
+ def backup(self, image_name, backup_type, rotation):
+ """
+ Create a server backup.
+
+ :param server: The :class:`Server` (or its ID).
+ :param image_name: The name to assign the newly create image.
+ :param backup_type: 'daily' or 'weekly'
+ :param rotation: number of backups of type 'backup_type' to keep
+ :returns Newly created :class:`Image` object
+ """
+ return self.manager.backup(self, image_name, backup_type, rotation)
+
def confirm_resize(self):
"""
Confirm that the resize worked, thus removing the original server.
@@ -228,7 +240,7 @@ class ServerManager(base.BootingManagerWithFind):
qparams[opt] = val
query_string = "?%s" % urllib.urlencode(qparams) if qparams else ""
-
+
detail = ""
if detailed:
detail = "/detail"
@@ -370,6 +382,31 @@ class ServerManager(base.BootingManagerWithFind):
"""
self._action('resize', server, {'flavorId': base.getid(flavor)})
+ def backup(self, server, image_name, backup_type, rotation):
+ """
+ Create a server backup.
+
+ :param server: The :class:`Server` (or its ID).
+ :param image_name: The name to assign the newly create image.
+ :param backup_type: 'daily' or 'weekly'
+ :param rotation: number of backups of type 'backup_type' to keep
+ :returns Newly created :class:`Image` object
+ """
+ if not rotation:
+ raise Exception("rotation is required for backups")
+ elif not backup_type:
+ raise Exception("backup_type required for backups")
+ elif backup_type not in ("daily", "weekly"):
+ raise Exception("Invalid backup_type: must be daily or weekly")
+
+ data = {
+ "name": image_name,
+ "rotation": rotation,
+ "backup_type": backup_type,
+ }
+
+ self._action('createBackup', server, data)
+
def pause(self, server):
"""
Pause the server.
diff --git a/novaclient/shell.py b/novaclient/shell.py
index b420e1c3..f9f5252e 100644
--- a/novaclient/shell.py
+++ b/novaclient/shell.py
@@ -98,7 +98,7 @@ class OpenStackShell(object):
help='Defaults to env[NOVA_API_KEY].')
self.parser.add_argument('--projectid',
- default=env('NOVA_PROJECT_ID'),
+ default=env('NOVA_PROJECT_ID'),
help='Defaults to env[NOVA_PROJECT_ID].')
auth_url = env('NOVA_URL')
@@ -281,7 +281,7 @@ class OpenStackShell(object):
except IOError, e:
raise CommandError("Can't open '%s': %s" % (keyfile, e))
- return (args.name, image, flavor, ipgroup, metadata, files,
+ return (args.name, image, flavor, ipgroup, metadata, files,
reservation_id, min_count, max_count)
@arg('--flavor',
@@ -461,7 +461,7 @@ class OpenStackShell(object):
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_flavor_list(self, args):
"""Print a list of available 'flavors' (sizes of servers)."""
flavors = self.cs.flavors.list()
@@ -481,27 +481,11 @@ class OpenStackShell(object):
print_list(self.cs.images.list(), ['ID', 'Name', 'Status'])
@arg('server', metavar='<server>', help='Name or ID of server.')
- @arg('name', metavar='<name>', help='Name of backup or snapshot.')
- @arg('--image-type',
- metavar='<backup|snapshot>',
- default='snapshot',
- help='type of image (default: snapshot)')
- @arg('--backup-type',
- metavar='<daily|weekly>',
- default=None,
- help='type of backup')
- @arg('--rotation',
- default=None,
- type=int,
- metavar='<rotation>',
- help="Number of backups to retain. Used for backup image_type.")
+ @arg('name', metavar='<name>', help='Name of snapshot.')
def do_image_create(self, args):
"""Create a new image by taking a snapshot of a running server."""
server = self._find_server(args.server)
- image = self.cs.images.create(server, args.name,
- image_type=args.image_type,
- backup_type=args.backup_type,
- rotation=args.rotation)
+ image = self.cs.images.create(server, args.name)
print_dict(image._info)
@arg('image', metavar='<image>', help='Name or ID of image.')
@@ -661,6 +645,16 @@ class OpenStackShell(object):
server.resize(flavor)
@arg('server', metavar='<server>', help='Name or ID of server.')
+ @arg('name', metavar='<name>', help='Name of snapshot.')
+ @arg('backup_type', metavar='<daily|weekly>', help='type of backup')
+ @arg('rotation', type=int, metavar='<rotation>',
+ help="Number of backups to retain. Used for backup image_type.")
+ def do_backup(self, args):
+ """Resize a server."""
+ server = self._find_server(args.server)
+ server.backup(args.name, args.backup_type, args.rotation)
+
+ @arg('server', metavar='<server>', help='Name or ID of server.')
def do_migrate(self, args):
"""Migrate a server."""
self._find_server(args.server).migrate()
@@ -766,7 +760,7 @@ class OpenStackShell(object):
def do_zone(self, args):
"""Show or edit a child zone. No zone arg for this zone."""
zone = self.cs.zones.get(args.zone)
-
+
# If we have some flags, update the zone
zone_delta = {}
if args.api_url:
@@ -790,7 +784,7 @@ class OpenStackShell(object):
print_dict(zone._info)
@arg('api_url', metavar='<api_url>', help="URL for the Zone's API")
- @arg('zone_username', metavar='<zone_username>',
+ @arg('zone_username', metavar='<zone_username>',
help='Authentication username.')
@arg('password', metavar='<password>', help='Authentication password.')
@arg('weight_offset', metavar='<weight_offset>',
@@ -799,7 +793,7 @@ class OpenStackShell(object):
help='Child Zone weight scale (typically 1.0).')
def do_zone_add(self, args):
"""Add a new child zone."""
- zone = self.cs.zones.create(args.api_url, args.zone_username,
+ zone = self.cs.zones.create(args.api_url, args.zone_username,
args.password, args.weight_offset,
args.weight_scale)
print_dict(zone._info)
@@ -820,7 +814,7 @@ class OpenStackShell(object):
"""Add new IP address to network."""
server = self._find_server(args.server)
server.add_fixed_ip(args.network_id)
-
+
@arg('server', metavar='<server>', help='Name or ID of server.')
@arg('address', metavar='<address>', help='IP Address.')
def do_remove_fixed_ip(self, args):
diff --git a/novaclient/zones.py b/novaclient/zones.py
index 5bcee550..28d0d667 100644
--- a/novaclient/zones.py
+++ b/novaclient/zones.py
@@ -24,7 +24,7 @@ class Weighting(base.Resource):
def __init__(self, manager, info):
self.name = "n/a"
super(Weighting, self).__init__(manager, info)
-
+
def __repr__(self):
return "<Weighting: %s>" % self.name
diff --git a/setup.py b/setup.py
index 6adef8c3..4e66ec2b 100644
--- a/setup.py
+++ b/setup.py
@@ -2,24 +2,25 @@ import os
import sys
from setuptools import setup, find_packages
+
def read(fname):
return open(os.path.join(os.path.dirname(__file__), fname)).read()
requirements = ['httplib2', 'argparse', 'prettytable']
-if sys.version_info < (2,6):
+if sys.version_info < (2, 6):
requirements.append('simplejson')
setup(
- name = "python-novaclient",
- version = "2.5.8",
- description = "Client library for OpenStack Nova API",
- long_description = read('README.rst'),
- url = 'https://github.com/rackspace/python-novaclient',
- license = 'Apache',
- author = 'Rackspace, based on work by Jacob Kaplan-Moss',
- author_email = 'github@racklabs.com',
- packages = find_packages(exclude=['tests']),
- classifiers = [
+ name="python-novaclient",
+ version="2.5.8",
+ description="Client library for OpenStack Nova API",
+ long_description=read('README.rst'),
+ url='https://github.com/rackspace/python-novaclient',
+ license='Apache',
+ author='Rackspace, based on work by Jacob Kaplan-Moss',
+ author_email='github@racklabs.com',
+ packages=find_packages(exclude=['tests']),
+ classifiers=[
'Development Status :: 5 - Production/Stable',
'Environment :: Console',
'Intended Audience :: Developers',
@@ -28,12 +29,12 @@ setup(
'Operating System :: OS Independent',
'Programming Language :: Python',
],
- install_requires = requirements,
-
- tests_require = ["nose", "mock"],
- test_suite = "nose.collector",
-
- entry_points = {
+ install_requires=requirements,
+
+ tests_require=["nose", "mock"],
+ test_suite="nose.collector",
+
+ entry_points={
'console_scripts': ['nova = novaclient.shell:main']
}
)
diff --git a/tests/fakeserver.py b/tests/fakeserver.py
index 232c7b35..a1aa2260 100644
--- a/tests/fakeserver.py
+++ b/tests/fakeserver.py
@@ -267,6 +267,9 @@ class FakeClient(OpenStackClient):
assert_equal(body[action].keys(), ['imageId'])
elif action == 'resize':
assert_equal(body[action].keys(), ['flavorId'])
+ elif action == 'createBackup':
+ assert_equal(set(body[action].keys()),
+ set(['name', 'rotation', 'backup_type']))
elif action == 'confirmResize':
assert_equal(body[action], None)
# This one method returns a different response code
@@ -342,7 +345,7 @@ class FakeClient(OpenStackClient):
def post_images(self, body, **kw):
assert_equal(body.keys(), ['image'])
- assert_has_keys(body['image'], required=['serverId', 'name', 'image_type', 'backup_type', 'rotation'])
+ assert_has_keys(body['image'], required=['serverId', 'name'])
return (202, self.get_images_1()[1])
def delete_images_1(self, **kw):
@@ -409,10 +412,9 @@ class FakeClient(OpenStackClient):
{'id': 2, 'api_url': 'http://foo.com', 'username': 'alice'},
]})
-
def get_zones_detail(self, **kw):
return (200, {'zones': [
- {'id': 1, 'api_url': 'http://foo.com', 'username': 'bob',
+ {'id': 1, 'api_url': 'http://foo.com', 'username': 'bob',
'password': 'qwerty'},
{'id': 2, 'api_url': 'http://foo.com', 'username': 'alice',
'password': 'password'}
diff --git a/tests/test_accounts.py b/tests/test_accounts.py
index 37e37c55..8c488efe 100644
--- a/tests/test_accounts.py
+++ b/tests/test_accounts.py
@@ -7,6 +7,7 @@ from novaclient import Account
cs = FakeServer()
+
def test_instance_creation_for_account():
s = cs.accounts.create_instance_for(
account_id='test_account',
diff --git a/tests/test_servers.py b/tests/test_servers.py
index b88ed921..29c3069a 100644
--- a/tests/test_servers.py
+++ b/tests/test_servers.py
@@ -150,6 +150,14 @@ def test_revert_resized_server():
cs.assert_called('POST', '/servers/1234/action')
+def test_backup_server():
+ s = cs.servers.get(1234)
+ s.backup("ImageName", "daily", 10)
+ cs.assert_called('POST', '/servers/1234/action')
+ cs.servers.backup(s, "ImageName", "daily", 10)
+ cs.assert_called('POST', '/servers/1234/action')
+
+
def test_migrate_server():
s = cs.servers.get(1234)
s.migrate()
diff --git a/tests/test_shell.py b/tests/test_shell.py
index 17ba5a44..0d8b1075 100644
--- a/tests/test_shell.py
+++ b/tests/test_shell.py
@@ -195,15 +195,7 @@ def test_snapshot_create():
shell('image-create sample-server mysnapshot')
assert_called(
'POST', '/images',
- {'image': {'name': 'mysnapshot', 'serverId': 1234, 'image_type': 'snapshot', 'backup_type': None, 'rotation': None}}
- )
-
-
-def test_backup_create():
- shell('image-create sample-server mybackup --image-type backup --backup-type daily --rotation 1')
- assert_called(
- 'POST', '/images',
- {'image': {'name': 'mybackup', 'serverId': 1234, 'image_type': 'backup', 'backup_type': 'daily', 'rotation': 1}}
+ {'image': {'name': 'mysnapshot', 'serverId': 1234}}
)
@@ -295,6 +287,15 @@ def test_resize_revert():
assert_called('POST', '/servers/1234/action', {'revertResize': None})
+def test_backup():
+ shell('backup sample-server mybackup daily 1')
+ assert_called(
+ 'POST', '/servers/1234/action',
+ {'createBackup': {'name': 'mybackup', 'backup_type': 'daily',
+ 'rotation': 1}}
+ )
+
+
@mock.patch('getpass.getpass', mock.Mock(return_value='p'))
def test_root_password():
shell('root-password sample-server')
@@ -326,15 +327,17 @@ def test_zone():
'password': 'xxx'}}
)
+
def test_zone_add():
shell('zone-add http://zzz frank xxx 0.0 1.0')
assert_called(
'POST', '/zones',
- {'zone': {'api_url': 'http://zzz', 'username': 'frank',
+ {'zone': {'api_url': 'http://zzz', 'username': 'frank',
'password': 'xxx',
'weight_offset': '0.0', 'weight_scale': '1.0'}}
)
+
def test_zone_delete():
shell('zone-delete 1')
assert_called('DELETE', '/zones/1')
diff --git a/tests/test_zones.py b/tests/test_zones.py
index cd773ad3..11194189 100644
--- a/tests/test_zones.py
+++ b/tests/test_zones.py
@@ -49,7 +49,7 @@ def test_update_zone():
# Update via manager
os.zones.update(s, api_url='http://blah.com')
os.assert_called('PUT', '/zones/1')
- os.zones.update(1, api_url= 'http://blah.com')
+ os.zones.update(1, api_url='http://blah.com')
os.assert_called('PUT', '/zones/1')
os.zones.update(s, api_url='http://blah.com', username='fred',
password='zip')