diff --git a/novaclient/v1_1/base.py b/novaclient/v1_1/base.py index 9113d53bb..9762f73eb 100644 --- a/novaclient/v1_1/base.py +++ b/novaclient/v1_1/base.py @@ -128,7 +128,9 @@ class ManagerWithFind(Manager): class BootingManagerWithFind(ManagerWithFind): """Like a `ManagerWithFind`, but has the ability to boot servers.""" def _boot(self, resource_url, response_key, name, image, flavor, - meta=None, files=None, return_raw=False): + meta=None, files=None, zone_blob=None, + reservation_id=None, return_raw=False, min_count=None, + max_count=None): """ Create (boot) a new server. @@ -143,6 +145,10 @@ class BootingManagerWithFind(ManagerWithFind): are the file contents (either as a string or as a file-like object). A maximum of five entries is allowed, and each file must be 10k or less. + :param zone_blob: a single (encrypted) string which is used internally + by Nova for routing between Zones. Users cannot populate + this field. + :param reservation_id: a UUID for the set of servers being requested. :param return_raw: If True, don't try to coearse the result into a Resource object. """ @@ -153,6 +159,17 @@ class BootingManagerWithFind(ManagerWithFind): }} if meta: body["server"]["metadata"] = meta + if reservation_id: + body["server"]["reservation_id"] = reservation_id + if zone_blob: + body["server"]["zone_blob"] = zone_blob + + if not min_count: + min_count = 1 + if not max_count: + max_count = min_count + body["server"]["min_count"] = min_count + body["server"]["max_count"] = max_count # Files are a slight bit tricky. They're passed in a "personality" # list to the POST. Each item is a dict giving a file name and the diff --git a/novaclient/v1_1/client.py b/novaclient/v1_1/client.py index ee7195896..fbbc62c0e 100644 --- a/novaclient/v1_1/client.py +++ b/novaclient/v1_1/client.py @@ -4,6 +4,7 @@ from novaclient import client from novaclient.v1_1 import flavors from novaclient.v1_1 import images from novaclient.v1_1 import servers +from novaclient.v1_1 import zones @@ -28,6 +29,7 @@ class Client(object): self.flavors = flavors.FlavorManager(self) self.images = images.ImageManager(self) self.servers = servers.ServerManager(self) + self.zones = zones.ZoneManager(self) self.client = client.HTTPClient(username, api_key, diff --git a/novaclient/v1_1/servers.py b/novaclient/v1_1/servers.py index e478ca4fe..4ab7f5bc1 100644 --- a/novaclient/v1_1/servers.py +++ b/novaclient/v1_1/servers.py @@ -47,6 +47,72 @@ class Server(base.Resource): """ self.manager.update(self, name=name) + def add_fixed_ip(self, network_id): + """ + Add an IP address on a network. + + :param network_id: The ID of the network the IP should be on. + """ + self.manager.add_fixed_ip(self, network_id) + + def pause(self): + """ + Pause -- Pause the running server. + """ + self.manager.pause(self) + + def unpause(self): + """ + Unpause -- Unpause the paused server. + """ + self.manager.unpause(self) + + def suspend(self): + """ + Suspend -- Suspend the running server. + """ + self.manager.suspend(self) + + def resume(self): + """ + Resume -- Resume the suspended server. + """ + self.manager.resume(self) + + def rescue(self): + """ + Rescue -- Rescue the problematic server. + """ + self.manager.rescue(self) + + def unrescue(self): + """ + Unrescue -- Unrescue the rescued server. + """ + self.manager.unrescue(self) + + def diagnostics(self): + """Diagnostics -- Retrieve server diagnostics.""" + self.manager.diagnostics(self) + + def actions(self): + """Actions -- Retrieve server actions.""" + self.manager.actions(self) + + def migrate(self): + """ + Migrate a server to a new host in the same zone. + """ + self.manager.migrate(self) + + def remove_fixed_ip(self, address): + """ + Remove an IP address. + + :param address: The IP address to remove. + """ + self.manager.remove_fixed_ip(self, address) + def change_password(self, password): """ Update the password for a server. @@ -113,7 +179,7 @@ class Server(base.Resource): try: for network_label, address_list in self.addresses.items(): networks[network_label] = [a['addr'] for a in address_list] - return networks + return networks except Exception: return {} @@ -155,7 +221,78 @@ class ServerManager(local_base.BootingManagerWithFind): detail = "/detail" return self._list("/servers%s%s" % (detail, query_string), "servers") - def create(self, name, image, flavor, meta=None, files=None): + def add_fixed_ip(self, server, network_id): + """ + Add an IP address on a network. + + :param server: The :class:`Server` (or its ID) to add an IP to. + :param network_id: The ID of the network the IP should be on. + """ + self._action('addFixedIp', server, {'networkId': network_id}) + + def remove_fixed_ip(self, server, address): + """ + Remove an IP address. + + :param server: The :class:`Server` (or its ID) to add an IP to. + :param address: The IP address to remove. + """ + self._action('removeFixedIp', server, {'address': address}) + + def pause(self, server): + """ + Pause the server. + """ + self.api.client.post('/servers/%s/pause' % base.getid(server), body={}) + + def unpause(self, server): + """ + Unpause the server. + """ + self.api.client.post('/servers/%s/unpause' % base.getid(server), + body={}) + + def suspend(self, server): + """ + Suspend the server. + """ + self.api.client.post('/servers/%s/suspend' % base.getid(server), + body={}) + + def resume(self, server): + """ + Resume the server. + """ + self.api.client.post('/servers/%s/resume' % base.getid(server), + body={}) + + def rescue(self, server): + """ + Rescue the server. + """ + self.api.client.post('/servers/%s/rescue' % base.getid(server), + body={}) + + def unrescue(self, server): + """ + Unrescue the server. + """ + self.api.client.post('/servers/%s/unrescue' % base.getid(server), + body={}) + + def diagnostics(self, server): + """Retrieve server diagnostics.""" + return self.api.client.get("/servers/%s/diagnostics" % + base.getid(server)) + + def actions(self, server): + """Retrieve server actions.""" + return self._list("/servers/%s/actions" % base.getid(server), + "actions") + + def create(self, name, image, flavor, meta=None, files=None, + zone_blob=None, reservation_id=None, min_count=None, + max_count=None): """ Create (boot) a new server. @@ -170,31 +307,21 @@ class ServerManager(local_base.BootingManagerWithFind): are the file contents (either as a string or as a file-like object). A maximum of five entries is allowed, and each file must be 10k or less. + :param zone_blob: a single (encrypted) string which is used internally + by Nova for routing between Zones. Users cannot populate + this field. + :param reservation_id: a UUID for the set of servers being requested. """ - personality = [] - - for file_path, filelike in files.items(): - try: - data = filelike.read() - except AttributeError: - data = str(filelike) - - personality.append({ - "path": file_path, - "contents": data.encode("base64"), - }) - - body = { - "server": { - "name": name, - "imageRef": base.getid(image), - "flavorRef": base.getid(flavor), - "metadata": meta or {}, - "personality": personality, - }, - } - - return self._create("/servers", body, "server", return_raw=False) + if not min_count: + min_count = 1 + if not max_count: + max_count = min_count + if min_count > max_count: + min_count = max_count + return self._boot("/servers", "server", name, image, flavor, + meta=meta, files=files, + zone_blob=zone_blob, reservation_id=reservation_id, + min_count=min_count, max_count=max_count) def update(self, server, name=None): """ @@ -245,6 +372,14 @@ class ServerManager(local_base.BootingManagerWithFind): """ self._action('rebuild', server, {'imageRef': base.getid(image)}) + def migrate(self, server): + """ + Migrate a server to a new host in the same zone. + + :param server: The :class:`Server` (or its ID). + """ + self._action('migrate', server) + def resize(self, server, flavor): """ Resize a server's resources. diff --git a/novaclient/v1_1/shell.py b/novaclient/v1_1/shell.py index b29b85c2d..dfa26a14e 100644 --- a/novaclient/v1_1/shell.py +++ b/novaclient/v1_1/shell.py @@ -31,68 +31,16 @@ CLIENT_CLASS = client.Client AUTO_KEY = object() -def _translate_flavor_keys(collection): - convert = [('ram', 'memory_mb'), ('disk', 'local_gb')] - 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]) - -@utils.arg('--flavor', - default=None, - metavar='', - help="Flavor ID (see 'nova flavors'). "\ - "Defaults to 256MB RAM instance.") -@utils.arg('--image', - default=None, - metavar='', - help="Image ID (see 'nova images'). "\ - "Defaults to Ubuntu 10.04 LTS.") -@utils.arg('--meta', - metavar="", - action='append', - default=[], - help="Record arbitrary key/value metadata. "\ - "May be give multiple times.") -@utils.arg('--file', - metavar="", - action='append', - dest='files', - default=[], - help="Store arbitrary files from locally to "\ - "on the new server. You may store up to 5 files.") -@utils.arg('--key', - metavar='', - nargs='?', - const=AUTO_KEY, - help="Key the server with an SSH keypair. "\ - "Looks in ~/.ssh for a key, "\ - "or takes an explicit to one.") -@utils.arg('name', metavar='', help='Name for the new server') -def do_boot(cs, args): - """Boot a new server.""" - name, image, flavor, metadata, files = _boot(cs, args) - server = cs.servers.create(args.name, image, flavor, - meta=metadata, files=files) - info = server._info - - flavor = info.get('flavor', {}) - flavor_id = flavor.get('id', '') - info['flavor'] = _find_flavor(cs, flavor_id).name - - image = info.get('image', {}) - image_id = image.get('id', '') - info['image'] = _find_image(cs, image_id).name - - info.pop('links', None) - info.pop('addresses', None) - - utils.print_dict(info) - - -def _boot(cs, args): +def _boot(cs, args, reservation_id=None, min_count=None, max_count=None): """Boot a new server.""" + if min_count is None: + min_count = 1 + if max_count is None: + max_count = min_count + if min_count > max_count: + raise exceptions.CommandError("min_instances should be <= max_instances") + if not min_count or not max_count: + raise exceptions.CommandError("min_instances nor max_instances should be 0") flavor = args.flavor or cs.flavors.find(ram=256) image = args.image or cs.images.find(name="Ubuntu 10.04 LTS "\ @@ -129,7 +77,129 @@ def _boot(cs, args): except IOError, e: raise exceptions.CommandError("Can't open '%s': %s" % (keyfile, e)) - return (args.name, image, flavor, metadata, files) + return (args.name, image, flavor, metadata, files, + reservation_id, min_count, max_count) + +@utils.arg('--flavor', + default=None, + metavar='', + help="Flavor ID (see 'nova flavors'). "\ + "Defaults to 256MB RAM instance.") +@utils.arg('--image', + default=None, + metavar='', + help="Image ID (see 'nova images'). "\ + "Defaults to Ubuntu 10.04 LTS.") +@utils.arg('--meta', + metavar="", + action='append', + default=[], + help="Record arbitrary key/value metadata. "\ + "May be give multiple times.") +@utils.arg('--file', + metavar="", + action='append', + dest='files', + default=[], + help="Store arbitrary files from locally to "\ + "on the new server. You may store up to 5 files.") +@utils.arg('--key', + metavar='', + nargs='?', + const=AUTO_KEY, + help="Key the server with an SSH keypair. "\ + "Looks in ~/.ssh for a key, "\ + "or takes an explicit to one.") +@utils.arg('name', metavar='', help='Name for the new server') +def do_boot(cs, args): + """Boot a new server.""" + name, image, flavor, metadata, files, reservation_id, \ + min_count, max_count = _boot(cs, args) + + server = cs.servers.create(args.name, image, flavor, + meta=metadata, + files=files, + min_count=min_count, + max_count=max_count) + utils.print_dict(server._info) + + +@utils.arg('--flavor', + default=None, + metavar='', + help="Flavor ID (see 'nova flavors'). "\ + "Defaults to 256MB RAM instance.") +@utils.arg('--image', + default=None, + metavar='', + help="Image ID (see 'nova images'). "\ + "Defaults to Ubuntu 10.04 LTS.") +@utils.arg('--meta', + metavar="", + action='append', + default=[], + help="Record arbitrary key/value metadata. "\ + "May be give multiple times.") +@utils.arg('--file', + metavar="", + action='append', + dest='files', + default=[], + help="Store arbitrary files from locally to "\ + "on the new server. You may store up to 5 files.") +@utils.arg('--key', + metavar='', + nargs='?', + const=AUTO_KEY, + help="Key the server with an SSH keypair. "\ + "Looks in ~/.ssh for a key, "\ + "or takes an explicit to one.") +@utils.arg('--reservation_id', + default=None, + metavar='', + help="Reservation ID (a UUID). "\ + "If unspecified will be generated by the server.") +@utils.arg('--min_instances', + default=None, + type=int, + metavar='', + help="The minimum number of instances to build. "\ + "Defaults to 1.") +@utils.arg('--max_instances', + default=None, + type=int, + metavar='', + help="The maximum number of instances to build. "\ + "Defaults to 'min_instances' setting.") +@utils.arg('name', metavar='', help='Name for the new server') +def do_zone_boot(cs, args): + """Boot a new server, potentially across Zones.""" + reservation_id = args.reservation_id + min_count = args.min_instances + max_count = args.max_instances + name, image, flavor, metadata, \ + files, reservation_id, min_count, max_count = \ + _boot(cs, args, + reservation_id=reservation_id, + min_count=min_count, + max_count=max_count) + + reservation_id = cs.zones.boot(args.name, image, flavor, + meta=metadata, + files=files, + reservation_id=reservation_id, + min_count=min_count, + max_count=max_count) + print "Reservation ID=", reservation_id + + +def _translate_flavor_keys(collection): + convert = [('ram', 'memory_mb'), ('disk', 'local_gb')] + 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_flavor_list(cs, args): @@ -282,6 +352,55 @@ def do_resize_revert(cs, args): """Revert a previous resize (and return to the previous VM).""" _find_server(cs, args.server).revert_resize() +@utils.arg('server', metavar='', help='Name or ID of server.') +def do_migrate(cs, args): + """Migrate a server.""" + _find_server(cs, args.server).migrate() + +@utils.arg('server', metavar='', help='Name or ID of server.') +def do_pause(cs, args): + """Pause a server.""" + _find_server(cs, args.server).pause() + +@utils.arg('server', metavar='', help='Name or ID of server.') +def do_unpause(cs, args): + """Unpause a server.""" + _find_server(cs, args.server).unpause() + +@utils.arg('server', metavar='', help='Name or ID of server.') +def do_suspend(cs, args): + """Suspend a server.""" + _find_server(cs, args.server).suspend() + +@utils.arg('server', metavar='', help='Name or ID of server.') +def do_resume(cs, args): + """Resume a server.""" + _find_server(cs, args.server).resume() + +@utils.arg('server', metavar='', help='Name or ID of server.') +def do_rescue(cs, args): + """Rescue a server.""" + _find_server(cs, args.server).rescue() + +@utils.arg('server', metavar='', help='Name or ID of server.') +def do_unrescue(cs, args): + """Unrescue a server.""" + _find_server(cs, args.server).unrescue() + +@utils.arg('server', metavar='', help='Name or ID of server.') +def do_diagnostics(cs, args): + """Retrieve server diagnostics.""" + utils.print_dict(cs.servers.diagnostics(args.server)[1]) + +@utils.arg('server', metavar='', help='Name or ID of server.') +def do_actions(cs, args): + """Retrieve server actions.""" + utils.print_list( + cs.servers.actions(args.server), + ["Created_At", "Action", "Error"]) + + + @utils.arg('server', metavar='', help='Name or ID of server.') def do_root_password(cs, args): """ @@ -360,3 +479,77 @@ def _find_resource(manager, name_or_id): raise exceptions.CommandError("No %s with a name or ID of '%s' exists." % (manager.resource_class.__name__.lower(), name_or_id)) +# --zone_username is required since --username is already used. +@utils.arg('zone', metavar='', help='ID of the zone', default=None) +@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('--weight_offset', dest='weight_offset', default=None, + help='Child Zone weight offset.') +@utils.arg('--weight_scale', dest='weight_scale', default=None, + help='Child Zone weight scale.') +def do_zone(cs, args): + """Show or edit a child zone. No zone arg for this zone.""" + zone = cs.zones.get(args.zone) + + # If we have some flags, update the zone + zone_delta = {} + if args.api_url: + 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.weight_offset: + zone_delta['weight_offset'] = args.weight_offset + if args.weight_scale: + zone_delta['weight_scale'] = args.weight_scale + if zone_delta: + zone.update(**zone_delta) + else: + utils.print_dict(zone._info) + +def do_zone_info(cs, args): + """Get this zones name and capabilities.""" + zone = cs.zones.info() + utils.print_dict(zone._info) + +@utils.arg('api_url', metavar='', help="URL for the Zone's API") +@utils.arg('zone_username', metavar='', + help='Authentication username.') +@utils.arg('password', metavar='', help='Authentication password.') +@utils.arg('weight_offset', metavar='', + help='Child Zone weight offset (typically 0.0).') +@utils.arg('weight_scale', metavar='', + help='Child Zone weight scale (typically 1.0).') +def do_zone_add(cs, args): + """Add a new child zone.""" + zone = cs.zones.create(args.api_url, args.zone_username, + args.password, args.weight_offset, + args.weight_scale) + utils.print_dict(zone._info) + +@utils.arg('zone', metavar='', help='Name or ID of the zone') +def do_zone_delete(cs, args): + """Delete a zone.""" + cs.zones.delete(args.zone) + +def do_zone_list(cs, args): + """List the children of a zone.""" + utils.print_list(cs.zones.list(), ['ID', 'Name', 'Is Active', \ + 'API URL', 'Weight Offset', 'Weight Scale']) + +@utils.arg('server', metavar='', help='Name or ID of server.') +@utils.arg('network_id', metavar='', help='Network ID.') +def do_add_fixed_ip(cs, args): + """Add new IP address to network.""" + server = _find_server(cs, args.server) + server.add_fixed_ip(args.network_id) + +@utils.arg('server', metavar='', help='Name or ID of server.') +@utils.arg('address', metavar='
', help='IP Address.') +def do_remove_fixed_ip(cs, args): + """Remove an IP address from a server.""" + server = _find_server(cs, args.server) + server.remove_fixed_ip(args.address) diff --git a/tests/fakes.py b/tests/fakes.py index b63cc8087..93709b8ce 100644 --- a/tests/fakes.py +++ b/tests/fakes.py @@ -12,11 +12,12 @@ import novaclient.client def assert_has_keys(dict, required=[], optional=[]): keys = dict.keys() for k in required: - assert k in keys - allowed_keys = set(required) | set(optional) - extra_keys = set(keys).difference(set(required + optional)) - if extra_keys: - raise AssertionError("found unexpected keys: %s" % list(extra_keys)) + try: + assert k in keys + except AssertionError: + allowed_keys = set(required) | set(optional) + extra_keys = set(keys).difference(set(required + optional)) + raise AssertionError("found unexpected keys: %s" % list(extra_keys)) class FakeClient(object): @@ -58,7 +59,13 @@ class FakeClient(object): assert found, 'Expected %s %s; got %s' % \ (expected, self.client.callstack) if body is not None: - assert entry[2] == body + try: + assert entry[2] == body + except AssertionError: + print entry[2] + print "!=" + print body + raise self.client.callstack = [] diff --git a/tests/v1_1/fakes.py b/tests/v1_1/fakes.py index c13fe695f..3985a7843 100644 --- a/tests/v1_1/fakes.py +++ b/tests/v1_1/fakes.py @@ -247,6 +247,12 @@ class FakeHTTPClient(base_client.HTTPClient): return (204, None) elif action == 'revertResize': assert body[action] is None + elif action == 'migrate': + assert body[action] is None + elif action == 'addFixedIp': + assert body[action].keys() == ['networkId'] + elif action == 'removeFixedIp': + assert body[action].keys() == ['address'] elif action == 'createImage': assert set(body[action].keys()) == set(['name', 'metadata']) elif action == 'changePassword': @@ -319,3 +325,49 @@ class FakeHTTPClient(base_client.HTTPClient): def delete_images_1(self, **kw): return (204, None) + + # + # Zones + # + def get_zones(self, **kw): + return (200, {'zones': [ + {'id': 1, 'api_url': 'http://foo.com', 'username': 'bob'}, + {'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', + 'password': 'qwerty'}, + {'id': 2, 'api_url': 'http://foo.com', 'username': 'alice', + 'password': 'password'} + ]}) + + def get_zones_1(self, **kw): + r = {'zone': self.get_zones_detail()[1]['zones'][0]} + return (200, r) + + def get_zones_2(self, **kw): + r = {'zone': self.get_zones_detail()[1]['zones'][1]} + return (200, r) + + def post_zones(self, body, **kw): + assert body.keys() == ['zone'] + fakes.assert_has_keys(body['zone'], + required=['api_url', 'username', 'password'], + optional=['weight_offset', 'weight_scale']) + + return (202, self.get_zones_1()[1]) + + def put_zones_1(self, body, **kw): + assert body.keys() == ['zone'] + fakes.assert_has_keys(body['zone'], optional=['api_url', 'username', + 'password', + 'weight_offset', + 'weight_scale']) + return (204, None) + + def delete_zones_1(self, **kw): + return (202, None) + + diff --git a/tests/v1_1/test_servers.py b/tests/v1_1/test_servers.py index 5a7ff73ac..74192965f 100644 --- a/tests/v1_1/test_servers.py +++ b/tests/v1_1/test_servers.py @@ -112,3 +112,24 @@ class ServersTest(utils.TestCase): cs.assert_called('POST', '/servers/1234/action') cs.servers.revert_resize(s) cs.assert_called('POST', '/servers/1234/action') + + def test_migrate_server(self): + s = cs.servers.get(1234) + s.migrate() + cs.assert_called('POST', '/servers/1234/action') + cs.servers.migrate(s) + cs.assert_called('POST', '/servers/1234/action') + + def test_add_fixed_ip(self): + s = cs.servers.get(1234) + s.add_fixed_ip(1) + cs.assert_called('POST', '/servers/1234/action') + cs.servers.add_fixed_ip(s, 1) + cs.assert_called('POST', '/servers/1234/action') + + def test_remove_fixed_ip(self): + s = cs.servers.get(1234) + s.remove_fixed_ip('10.0.0.1') + cs.assert_called('POST', '/servers/1234/action') + cs.servers.remove_fixed_ip(s, '10.0.0.1') + cs.assert_called('POST', '/servers/1234/action') diff --git a/tests/v1_1/test_shell.py b/tests/v1_1/test_shell.py index 4e79f112b..870df1fff 100644 --- a/tests/v1_1/test_shell.py +++ b/tests/v1_1/test_shell.py @@ -44,8 +44,8 @@ class ShellTest(utils.TestCase): 'flavorRef': 1, 'name': 'some-server', 'imageRef': '1', - 'personality': [], - 'metadata': {}, + 'min_count': 1, + 'max_count': 1, }} ) @@ -57,7 +57,8 @@ class ShellTest(utils.TestCase): 'name': 'some-server', 'imageRef': '1', 'metadata': {'foo': 'bar', 'spam': 'eggs'}, - 'personality': [], + 'min_count': 1, + 'max_count': 1, }} ) @@ -74,7 +75,8 @@ class ShellTest(utils.TestCase): 'flavorRef': 1, 'name': 'some-server', 'imageRef': '1', - 'metadata': {}, + 'min_count': 1, + 'max_count': 1, 'personality': [ {'path': '/tmp/bar', 'contents': expected_file_data}, {'path': '/tmp/foo', 'contents': expected_file_data} @@ -103,7 +105,8 @@ class ShellTest(utils.TestCase): 'flavorRef': 1, 'name': 'some-server', 'imageRef': '1', - 'metadata': {}, + 'min_count': 1, + 'max_count': 1, 'personality': [{ 'path': '/root/.ssh/authorized_keys2', 'contents': ('SSHKEY').encode('base64')}, @@ -131,12 +134,14 @@ class ShellTest(utils.TestCase): 'POST', '/servers', {'server': { 'flavorRef': 1, - 'name': 'some-server', 'imageRef': '1', - 'metadata': {}, - 'personality': [ - {'path': '/root/.ssh/authorized_keys2', - 'contents':expected_file_data}, - ]} + 'name': 'some-server', + 'imageRef': '1', + 'min_count': 1, + 'max_count': 1, + 'personality': [ + {'path': '/root/.ssh/authorized_keys2', + 'contents':expected_file_data}, + ]} } ) diff --git a/tests/v1_1/test_zones.py b/tests/v1_1/test_zones.py new file mode 100644 index 000000000..a10382c67 --- /dev/null +++ b/tests/v1_1/test_zones.py @@ -0,0 +1,76 @@ + +import StringIO + +from novaclient.v1_1 import zones +from tests.v1_1 import fakes +from tests import utils + + +os = fakes.FakeClient() + + +class ZonesTest(utils.TestCase): + + def test_list_zones(self): + sl = os.zones.list() + os.assert_called('GET', '/zones/detail') + [self.assertTrue(isinstance(s, zones.Zone)) for s in sl] + + def test_list_zones_undetailed(self): + sl = os.zones.list(detailed=False) + os.assert_called('GET', '/zones') + [self.assertTrue(isinstance(s, zones.Zone)) for s in sl] + + def test_get_zone_details(self): + s = os.zones.get(1) + os.assert_called('GET', '/zones/1') + self.assertTrue(isinstance(s, zones.Zone)) + self.assertEqual(s.id, 1) + self.assertEqual(s.api_url, 'http://foo.com') + + def test_create_zone(self): + s = os.zones.create(api_url="http://foo.com", username='bob', + password='xxx') + os.assert_called('POST', '/zones') + self.assertTrue(isinstance(s, zones.Zone)) + + def test_update_zone(self): + s = os.zones.get(1) + + # Update via instance + s.update(api_url='http://blah.com') + os.assert_called('PUT', '/zones/1') + s.update(api_url='http://blah.com', username='alice', password='xxx') + os.assert_called('PUT', '/zones/1') + + # Silly, but not an error + s.update() + + # 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.assert_called('PUT', '/zones/1') + os.zones.update(s, api_url='http://blah.com', username='fred', + password='zip') + os.assert_called('PUT', '/zones/1') + + def test_delete_zone(self): + s = os.zones.get(1) + s.delete() + os.assert_called('DELETE', '/zones/1') + os.zones.delete(1) + os.assert_called('DELETE', '/zones/1') + os.zones.delete(s) + os.assert_called('DELETE', '/zones/1') + + def test_find_zone(self): + s = os.zones.find(password='qwerty') + os.assert_called('GET', '/zones/detail') + self.assertEqual(s.username, 'bob') + + # Find with multiple results returns the first item + s = os.zones.find(api_url='http://foo.com') + sl = os.zones.findall(api_url='http://foo.com') + self.assertEqual(sl[0], s) + self.assertEqual([s.id for s in sl], [1, 2])