Merged v1.0 functionality into v1.1 so we don't lose any features by...upgrading?
This commit is contained in:
parent
46a2d5a3d9
commit
cc7156933f
@ -128,7 +128,9 @@ class ManagerWithFind(Manager):
|
|||||||
class BootingManagerWithFind(ManagerWithFind):
|
class BootingManagerWithFind(ManagerWithFind):
|
||||||
"""Like a `ManagerWithFind`, but has the ability to boot servers."""
|
"""Like a `ManagerWithFind`, but has the ability to boot servers."""
|
||||||
def _boot(self, resource_url, response_key, name, image, flavor,
|
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.
|
Create (boot) a new server.
|
||||||
|
|
||||||
@ -143,6 +145,10 @@ class BootingManagerWithFind(ManagerWithFind):
|
|||||||
are the file contents (either as a string or as a
|
are the file contents (either as a string or as a
|
||||||
file-like object). A maximum of five entries is allowed,
|
file-like object). A maximum of five entries is allowed,
|
||||||
and each file must be 10k or less.
|
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
|
:param return_raw: If True, don't try to coearse the result into
|
||||||
a Resource object.
|
a Resource object.
|
||||||
"""
|
"""
|
||||||
@ -153,6 +159,17 @@ class BootingManagerWithFind(ManagerWithFind):
|
|||||||
}}
|
}}
|
||||||
if meta:
|
if meta:
|
||||||
body["server"]["metadata"] = 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"
|
# 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
|
# list to the POST. Each item is a dict giving a file name and the
|
||||||
|
@ -4,6 +4,7 @@ from novaclient import client
|
|||||||
from novaclient.v1_1 import flavors
|
from novaclient.v1_1 import flavors
|
||||||
from novaclient.v1_1 import images
|
from novaclient.v1_1 import images
|
||||||
from novaclient.v1_1 import servers
|
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.flavors = flavors.FlavorManager(self)
|
||||||
self.images = images.ImageManager(self)
|
self.images = images.ImageManager(self)
|
||||||
self.servers = servers.ServerManager(self)
|
self.servers = servers.ServerManager(self)
|
||||||
|
self.zones = zones.ZoneManager(self)
|
||||||
|
|
||||||
self.client = client.HTTPClient(username,
|
self.client = client.HTTPClient(username,
|
||||||
api_key,
|
api_key,
|
||||||
|
@ -47,6 +47,72 @@ class Server(base.Resource):
|
|||||||
"""
|
"""
|
||||||
self.manager.update(self, name=name)
|
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):
|
def change_password(self, password):
|
||||||
"""
|
"""
|
||||||
Update the password for a server.
|
Update the password for a server.
|
||||||
@ -113,7 +179,7 @@ class Server(base.Resource):
|
|||||||
try:
|
try:
|
||||||
for network_label, address_list in self.addresses.items():
|
for network_label, address_list in self.addresses.items():
|
||||||
networks[network_label] = [a['addr'] for a in address_list]
|
networks[network_label] = [a['addr'] for a in address_list]
|
||||||
return networks
|
return networks
|
||||||
except Exception:
|
except Exception:
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
@ -155,7 +221,78 @@ class ServerManager(local_base.BootingManagerWithFind):
|
|||||||
detail = "/detail"
|
detail = "/detail"
|
||||||
return self._list("/servers%s%s" % (detail, query_string), "servers")
|
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.
|
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
|
are the file contents (either as a string or as a
|
||||||
file-like object). A maximum of five entries is allowed,
|
file-like object). A maximum of five entries is allowed,
|
||||||
and each file must be 10k or less.
|
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 = []
|
if not min_count:
|
||||||
|
min_count = 1
|
||||||
for file_path, filelike in files.items():
|
if not max_count:
|
||||||
try:
|
max_count = min_count
|
||||||
data = filelike.read()
|
if min_count > max_count:
|
||||||
except AttributeError:
|
min_count = max_count
|
||||||
data = str(filelike)
|
return self._boot("/servers", "server", name, image, flavor,
|
||||||
|
meta=meta, files=files,
|
||||||
personality.append({
|
zone_blob=zone_blob, reservation_id=reservation_id,
|
||||||
"path": file_path,
|
min_count=min_count, max_count=max_count)
|
||||||
"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)
|
|
||||||
|
|
||||||
def update(self, server, name=None):
|
def update(self, server, name=None):
|
||||||
"""
|
"""
|
||||||
@ -245,6 +372,14 @@ class ServerManager(local_base.BootingManagerWithFind):
|
|||||||
"""
|
"""
|
||||||
self._action('rebuild', server, {'imageRef': base.getid(image)})
|
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):
|
def resize(self, server, flavor):
|
||||||
"""
|
"""
|
||||||
Resize a server's resources.
|
Resize a server's resources.
|
||||||
|
@ -31,68 +31,16 @@ CLIENT_CLASS = client.Client
|
|||||||
AUTO_KEY = object()
|
AUTO_KEY = object()
|
||||||
|
|
||||||
|
|
||||||
def _translate_flavor_keys(collection):
|
def _boot(cs, args, reservation_id=None, min_count=None, max_count=None):
|
||||||
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='<flavor>',
|
|
||||||
help="Flavor ID (see 'nova flavors'). "\
|
|
||||||
"Defaults to 256MB RAM instance.")
|
|
||||||
@utils.arg('--image',
|
|
||||||
default=None,
|
|
||||||
metavar='<image>',
|
|
||||||
help="Image ID (see 'nova images'). "\
|
|
||||||
"Defaults to Ubuntu 10.04 LTS.")
|
|
||||||
@utils.arg('--meta',
|
|
||||||
metavar="<key=value>",
|
|
||||||
action='append',
|
|
||||||
default=[],
|
|
||||||
help="Record arbitrary key/value metadata. "\
|
|
||||||
"May be give multiple times.")
|
|
||||||
@utils.arg('--file',
|
|
||||||
metavar="<dst-path=src-path>",
|
|
||||||
action='append',
|
|
||||||
dest='files',
|
|
||||||
default=[],
|
|
||||||
help="Store arbitrary files from <src-path> locally to <dst-path> "\
|
|
||||||
"on the new server. You may store up to 5 files.")
|
|
||||||
@utils.arg('--key',
|
|
||||||
metavar='<path>',
|
|
||||||
nargs='?',
|
|
||||||
const=AUTO_KEY,
|
|
||||||
help="Key the server with an SSH keypair. "\
|
|
||||||
"Looks in ~/.ssh for a key, "\
|
|
||||||
"or takes an explicit <path> to one.")
|
|
||||||
@utils.arg('name', metavar='<name>', 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):
|
|
||||||
"""Boot a new server."""
|
"""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)
|
flavor = args.flavor or cs.flavors.find(ram=256)
|
||||||
image = args.image or cs.images.find(name="Ubuntu 10.04 LTS "\
|
image = args.image or cs.images.find(name="Ubuntu 10.04 LTS "\
|
||||||
@ -129,7 +77,129 @@ def _boot(cs, args):
|
|||||||
except IOError, e:
|
except IOError, e:
|
||||||
raise exceptions.CommandError("Can't open '%s': %s" % (keyfile, 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='<flavor>',
|
||||||
|
help="Flavor ID (see 'nova flavors'). "\
|
||||||
|
"Defaults to 256MB RAM instance.")
|
||||||
|
@utils.arg('--image',
|
||||||
|
default=None,
|
||||||
|
metavar='<image>',
|
||||||
|
help="Image ID (see 'nova images'). "\
|
||||||
|
"Defaults to Ubuntu 10.04 LTS.")
|
||||||
|
@utils.arg('--meta',
|
||||||
|
metavar="<key=value>",
|
||||||
|
action='append',
|
||||||
|
default=[],
|
||||||
|
help="Record arbitrary key/value metadata. "\
|
||||||
|
"May be give multiple times.")
|
||||||
|
@utils.arg('--file',
|
||||||
|
metavar="<dst-path=src-path>",
|
||||||
|
action='append',
|
||||||
|
dest='files',
|
||||||
|
default=[],
|
||||||
|
help="Store arbitrary files from <src-path> locally to <dst-path> "\
|
||||||
|
"on the new server. You may store up to 5 files.")
|
||||||
|
@utils.arg('--key',
|
||||||
|
metavar='<path>',
|
||||||
|
nargs='?',
|
||||||
|
const=AUTO_KEY,
|
||||||
|
help="Key the server with an SSH keypair. "\
|
||||||
|
"Looks in ~/.ssh for a key, "\
|
||||||
|
"or takes an explicit <path> to one.")
|
||||||
|
@utils.arg('name', metavar='<name>', 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='<flavor>',
|
||||||
|
help="Flavor ID (see 'nova flavors'). "\
|
||||||
|
"Defaults to 256MB RAM instance.")
|
||||||
|
@utils.arg('--image',
|
||||||
|
default=None,
|
||||||
|
metavar='<image>',
|
||||||
|
help="Image ID (see 'nova images'). "\
|
||||||
|
"Defaults to Ubuntu 10.04 LTS.")
|
||||||
|
@utils.arg('--meta',
|
||||||
|
metavar="<key=value>",
|
||||||
|
action='append',
|
||||||
|
default=[],
|
||||||
|
help="Record arbitrary key/value metadata. "\
|
||||||
|
"May be give multiple times.")
|
||||||
|
@utils.arg('--file',
|
||||||
|
metavar="<dst-path=src-path>",
|
||||||
|
action='append',
|
||||||
|
dest='files',
|
||||||
|
default=[],
|
||||||
|
help="Store arbitrary files from <src-path> locally to <dst-path> "\
|
||||||
|
"on the new server. You may store up to 5 files.")
|
||||||
|
@utils.arg('--key',
|
||||||
|
metavar='<path>',
|
||||||
|
nargs='?',
|
||||||
|
const=AUTO_KEY,
|
||||||
|
help="Key the server with an SSH keypair. "\
|
||||||
|
"Looks in ~/.ssh for a key, "\
|
||||||
|
"or takes an explicit <path> to one.")
|
||||||
|
@utils.arg('--reservation_id',
|
||||||
|
default=None,
|
||||||
|
metavar='<reservation_id>',
|
||||||
|
help="Reservation ID (a UUID). "\
|
||||||
|
"If unspecified will be generated by the server.")
|
||||||
|
@utils.arg('--min_instances',
|
||||||
|
default=None,
|
||||||
|
type=int,
|
||||||
|
metavar='<number>',
|
||||||
|
help="The minimum number of instances to build. "\
|
||||||
|
"Defaults to 1.")
|
||||||
|
@utils.arg('--max_instances',
|
||||||
|
default=None,
|
||||||
|
type=int,
|
||||||
|
metavar='<number>',
|
||||||
|
help="The maximum number of instances to build. "\
|
||||||
|
"Defaults to 'min_instances' setting.")
|
||||||
|
@utils.arg('name', metavar='<name>', 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):
|
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)."""
|
"""Revert a previous resize (and return to the previous VM)."""
|
||||||
_find_server(cs, args.server).revert_resize()
|
_find_server(cs, args.server).revert_resize()
|
||||||
|
|
||||||
|
@utils.arg('server', metavar='<server>', help='Name or ID of server.')
|
||||||
|
def do_migrate(cs, args):
|
||||||
|
"""Migrate a server."""
|
||||||
|
_find_server(cs, args.server).migrate()
|
||||||
|
|
||||||
|
@utils.arg('server', metavar='<server>', help='Name or ID of server.')
|
||||||
|
def do_pause(cs, args):
|
||||||
|
"""Pause a server."""
|
||||||
|
_find_server(cs, args.server).pause()
|
||||||
|
|
||||||
|
@utils.arg('server', metavar='<server>', help='Name or ID of server.')
|
||||||
|
def do_unpause(cs, args):
|
||||||
|
"""Unpause a server."""
|
||||||
|
_find_server(cs, args.server).unpause()
|
||||||
|
|
||||||
|
@utils.arg('server', metavar='<server>', help='Name or ID of server.')
|
||||||
|
def do_suspend(cs, args):
|
||||||
|
"""Suspend a server."""
|
||||||
|
_find_server(cs, args.server).suspend()
|
||||||
|
|
||||||
|
@utils.arg('server', metavar='<server>', help='Name or ID of server.')
|
||||||
|
def do_resume(cs, args):
|
||||||
|
"""Resume a server."""
|
||||||
|
_find_server(cs, args.server).resume()
|
||||||
|
|
||||||
|
@utils.arg('server', metavar='<server>', help='Name or ID of server.')
|
||||||
|
def do_rescue(cs, args):
|
||||||
|
"""Rescue a server."""
|
||||||
|
_find_server(cs, args.server).rescue()
|
||||||
|
|
||||||
|
@utils.arg('server', metavar='<server>', help='Name or ID of server.')
|
||||||
|
def do_unrescue(cs, args):
|
||||||
|
"""Unrescue a server."""
|
||||||
|
_find_server(cs, args.server).unrescue()
|
||||||
|
|
||||||
|
@utils.arg('server', metavar='<server>', 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='<server>', 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='<server>', help='Name or ID of server.')
|
@utils.arg('server', metavar='<server>', help='Name or ID of server.')
|
||||||
def do_root_password(cs, args):
|
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." %
|
raise exceptions.CommandError("No %s with a name or ID of '%s' exists." %
|
||||||
(manager.resource_class.__name__.lower(), name_or_id))
|
(manager.resource_class.__name__.lower(), name_or_id))
|
||||||
|
|
||||||
|
# --zone_username is required since --username is already used.
|
||||||
|
@utils.arg('zone', metavar='<zone_id>', 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='<api_url>', help="URL for the Zone's API")
|
||||||
|
@utils.arg('zone_username', metavar='<zone_username>',
|
||||||
|
help='Authentication username.')
|
||||||
|
@utils.arg('password', metavar='<password>', help='Authentication password.')
|
||||||
|
@utils.arg('weight_offset', metavar='<weight_offset>',
|
||||||
|
help='Child Zone weight offset (typically 0.0).')
|
||||||
|
@utils.arg('weight_scale', metavar='<weight_scale>',
|
||||||
|
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='<zone>', 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='<server>', help='Name or ID of server.')
|
||||||
|
@utils.arg('network_id', metavar='<network_id>', 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='<server>', help='Name or ID of server.')
|
||||||
|
@utils.arg('address', metavar='<address>', 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)
|
||||||
|
@ -12,11 +12,12 @@ import novaclient.client
|
|||||||
def assert_has_keys(dict, required=[], optional=[]):
|
def assert_has_keys(dict, required=[], optional=[]):
|
||||||
keys = dict.keys()
|
keys = dict.keys()
|
||||||
for k in required:
|
for k in required:
|
||||||
assert k in keys
|
try:
|
||||||
allowed_keys = set(required) | set(optional)
|
assert k in keys
|
||||||
extra_keys = set(keys).difference(set(required + optional))
|
except AssertionError:
|
||||||
if extra_keys:
|
allowed_keys = set(required) | set(optional)
|
||||||
raise AssertionError("found unexpected keys: %s" % list(extra_keys))
|
extra_keys = set(keys).difference(set(required + optional))
|
||||||
|
raise AssertionError("found unexpected keys: %s" % list(extra_keys))
|
||||||
|
|
||||||
|
|
||||||
class FakeClient(object):
|
class FakeClient(object):
|
||||||
@ -58,7 +59,13 @@ class FakeClient(object):
|
|||||||
assert found, 'Expected %s %s; got %s' % \
|
assert found, 'Expected %s %s; got %s' % \
|
||||||
(expected, self.client.callstack)
|
(expected, self.client.callstack)
|
||||||
if body is not None:
|
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 = []
|
self.client.callstack = []
|
||||||
|
|
||||||
|
@ -247,6 +247,12 @@ class FakeHTTPClient(base_client.HTTPClient):
|
|||||||
return (204, None)
|
return (204, None)
|
||||||
elif action == 'revertResize':
|
elif action == 'revertResize':
|
||||||
assert body[action] is None
|
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':
|
elif action == 'createImage':
|
||||||
assert set(body[action].keys()) == set(['name', 'metadata'])
|
assert set(body[action].keys()) == set(['name', 'metadata'])
|
||||||
elif action == 'changePassword':
|
elif action == 'changePassword':
|
||||||
@ -319,3 +325,49 @@ class FakeHTTPClient(base_client.HTTPClient):
|
|||||||
|
|
||||||
def delete_images_1(self, **kw):
|
def delete_images_1(self, **kw):
|
||||||
return (204, None)
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
@ -112,3 +112,24 @@ class ServersTest(utils.TestCase):
|
|||||||
cs.assert_called('POST', '/servers/1234/action')
|
cs.assert_called('POST', '/servers/1234/action')
|
||||||
cs.servers.revert_resize(s)
|
cs.servers.revert_resize(s)
|
||||||
cs.assert_called('POST', '/servers/1234/action')
|
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')
|
||||||
|
@ -44,8 +44,8 @@ class ShellTest(utils.TestCase):
|
|||||||
'flavorRef': 1,
|
'flavorRef': 1,
|
||||||
'name': 'some-server',
|
'name': 'some-server',
|
||||||
'imageRef': '1',
|
'imageRef': '1',
|
||||||
'personality': [],
|
'min_count': 1,
|
||||||
'metadata': {},
|
'max_count': 1,
|
||||||
}}
|
}}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -57,7 +57,8 @@ class ShellTest(utils.TestCase):
|
|||||||
'name': 'some-server',
|
'name': 'some-server',
|
||||||
'imageRef': '1',
|
'imageRef': '1',
|
||||||
'metadata': {'foo': 'bar', 'spam': 'eggs'},
|
'metadata': {'foo': 'bar', 'spam': 'eggs'},
|
||||||
'personality': [],
|
'min_count': 1,
|
||||||
|
'max_count': 1,
|
||||||
}}
|
}}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -74,7 +75,8 @@ class ShellTest(utils.TestCase):
|
|||||||
'flavorRef': 1,
|
'flavorRef': 1,
|
||||||
'name': 'some-server',
|
'name': 'some-server',
|
||||||
'imageRef': '1',
|
'imageRef': '1',
|
||||||
'metadata': {},
|
'min_count': 1,
|
||||||
|
'max_count': 1,
|
||||||
'personality': [
|
'personality': [
|
||||||
{'path': '/tmp/bar', 'contents': expected_file_data},
|
{'path': '/tmp/bar', 'contents': expected_file_data},
|
||||||
{'path': '/tmp/foo', 'contents': expected_file_data}
|
{'path': '/tmp/foo', 'contents': expected_file_data}
|
||||||
@ -103,7 +105,8 @@ class ShellTest(utils.TestCase):
|
|||||||
'flavorRef': 1,
|
'flavorRef': 1,
|
||||||
'name': 'some-server',
|
'name': 'some-server',
|
||||||
'imageRef': '1',
|
'imageRef': '1',
|
||||||
'metadata': {},
|
'min_count': 1,
|
||||||
|
'max_count': 1,
|
||||||
'personality': [{
|
'personality': [{
|
||||||
'path': '/root/.ssh/authorized_keys2',
|
'path': '/root/.ssh/authorized_keys2',
|
||||||
'contents': ('SSHKEY').encode('base64')},
|
'contents': ('SSHKEY').encode('base64')},
|
||||||
@ -131,12 +134,14 @@ class ShellTest(utils.TestCase):
|
|||||||
'POST', '/servers',
|
'POST', '/servers',
|
||||||
{'server': {
|
{'server': {
|
||||||
'flavorRef': 1,
|
'flavorRef': 1,
|
||||||
'name': 'some-server', 'imageRef': '1',
|
'name': 'some-server',
|
||||||
'metadata': {},
|
'imageRef': '1',
|
||||||
'personality': [
|
'min_count': 1,
|
||||||
{'path': '/root/.ssh/authorized_keys2',
|
'max_count': 1,
|
||||||
'contents':expected_file_data},
|
'personality': [
|
||||||
]}
|
{'path': '/root/.ssh/authorized_keys2',
|
||||||
|
'contents':expected_file_data},
|
||||||
|
]}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
76
tests/v1_1/test_zones.py
Normal file
76
tests/v1_1/test_zones.py
Normal file
@ -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])
|
Loading…
Reference in New Issue
Block a user