works properly with zone-boot
This commit is contained in:
parent
6cb04897af
commit
7090a6c8e0
novaclient
@ -29,6 +29,17 @@ except NameError:
|
||||
return True not in (not x for x in iterable)
|
||||
|
||||
|
||||
def getid(obj):
|
||||
"""
|
||||
Abstracts the common pattern of allowing both an object or an object's ID
|
||||
(integer) as a parameter when dealing with relationships.
|
||||
"""
|
||||
try:
|
||||
return obj.id
|
||||
except AttributeError:
|
||||
return int(obj)
|
||||
|
||||
|
||||
class Manager(object):
|
||||
"""
|
||||
Managers interact with a particular type of API (servers, flavors, images,
|
||||
@ -55,8 +66,10 @@ class Manager(object):
|
||||
resp, body = self.api.client.get(url)
|
||||
return self.resource_class(self, body[response_key])
|
||||
|
||||
def _create(self, url, body, response_key):
|
||||
def _create(self, url, body, response_key, return_raw=False):
|
||||
resp, body = self.api.client.post(url, body=body)
|
||||
if return_raw:
|
||||
return body[response_key]
|
||||
return self.resource_class(self, body[response_key])
|
||||
|
||||
def _delete(self, url):
|
||||
@ -105,6 +118,67 @@ class ManagerWithFind(Manager):
|
||||
return found
|
||||
|
||||
|
||||
class BootingManagerWithFind(ManagerWithFind):
|
||||
"""Like a `ManagerWithFind`, but has the ability to boot servers."""
|
||||
def _boot(self, resource_url, response_key, name, image, flavor,
|
||||
ipgroup=None, meta=None, files=None, zone_blob=None,
|
||||
reservation_id=None, return_raw=False):
|
||||
"""
|
||||
Create (boot) a new server.
|
||||
|
||||
:param name: Something to name the server.
|
||||
:param image: The :class:`Image` to boot with.
|
||||
:param flavor: The :class:`Flavor` to boot onto.
|
||||
:param ipgroup: An initial :class:`IPGroup` for this server.
|
||||
:param meta: A dict of arbitrary key/value metadata to store for this
|
||||
server. A maximum of five entries is allowed, and both
|
||||
keys and values must be 255 characters or less.
|
||||
:param files: A dict of files to overrwrite on the server upon boot.
|
||||
Keys are file names (i.e. ``/etc/passwd``) and values
|
||||
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.
|
||||
"""
|
||||
body = {"server": {
|
||||
"name": name,
|
||||
"imageId": getid(image),
|
||||
"flavorId": getid(flavor),
|
||||
}}
|
||||
if ipgroup:
|
||||
body["server"]["sharedIpGroupId"] = getid(ipgroup)
|
||||
if meta:
|
||||
body["server"]["metadata"] = meta
|
||||
if reservation_id:
|
||||
body["server"]["reservation_id"] = reservation_id
|
||||
if zone_blob:
|
||||
body["server"]["zone_blob"] = zone_blob
|
||||
|
||||
# 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
|
||||
# base64-encoded contents of the file. We want to allow passing
|
||||
# either an open file *or* some contents as files here.
|
||||
if files:
|
||||
personality = body['server']['personality'] = []
|
||||
for filepath, file_or_string in files.items():
|
||||
if hasattr(file_or_string, 'read'):
|
||||
data = file_or_string.read()
|
||||
else:
|
||||
data = file_or_string
|
||||
personality.append({
|
||||
'path': filepath,
|
||||
'contents': data.encode('base64'),
|
||||
})
|
||||
|
||||
return self._create(resource_url, body, response_key,
|
||||
return_raw=return_raw)
|
||||
|
||||
|
||||
class Resource(object):
|
||||
"""
|
||||
A resource represents a particular instance of an object (server, flavor,
|
||||
@ -143,14 +217,3 @@ class Resource(object):
|
||||
if hasattr(self, 'id') and hasattr(other, 'id'):
|
||||
return self.id == other.id
|
||||
return self._info == other._info
|
||||
|
||||
|
||||
def getid(obj):
|
||||
"""
|
||||
Abstracts the common pattern of allowing both an object or an object's ID
|
||||
(integer) as a parameter when dealing with relationships.
|
||||
"""
|
||||
try:
|
||||
return obj.id
|
||||
except AttributeError:
|
||||
return int(obj)
|
||||
|
@ -175,7 +175,7 @@ class Server(base.Resource):
|
||||
return self.addresses['private'][0]
|
||||
|
||||
|
||||
class ServerManager(base.ManagerWithFind):
|
||||
class ServerManager(base.BootingManagerWithFind):
|
||||
resource_class = Server
|
||||
|
||||
def get(self, server):
|
||||
@ -195,7 +195,7 @@ class ServerManager(base.ManagerWithFind):
|
||||
return self._list("/servers/detail", "servers")
|
||||
|
||||
def create(self, name, image, flavor, ipgroup=None, meta=None, files=None,
|
||||
zone_blob=None):
|
||||
zone_blob=None, reservation_id=None):
|
||||
"""
|
||||
Create (boot) a new server.
|
||||
|
||||
@ -214,36 +214,11 @@ class ServerManager(base.ManagerWithFind):
|
||||
: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.
|
||||
"""
|
||||
body = {"server": {
|
||||
"name": name,
|
||||
"imageId": base.getid(image),
|
||||
"flavorId": base.getid(flavor),
|
||||
}}
|
||||
if ipgroup:
|
||||
body["server"]["sharedIpGroupId"] = base.getid(ipgroup)
|
||||
if meta:
|
||||
body["server"]["metadata"] = meta
|
||||
if zone_blob:
|
||||
body["server"]["zone_blob"] = zone_blob
|
||||
|
||||
# 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
|
||||
# base64-encoded contents of the file. We want to allow passing
|
||||
# either an open file *or* some contents as files here.
|
||||
if files:
|
||||
personality = body['server']['personality'] = []
|
||||
for filepath, file_or_string in files.items():
|
||||
if hasattr(file_or_string, 'read'):
|
||||
data = file_or_string.read()
|
||||
else:
|
||||
data = file_or_string
|
||||
personality.append({
|
||||
'path': filepath,
|
||||
'contents': data.encode('base64'),
|
||||
})
|
||||
|
||||
return self._create("/servers", body, "server")
|
||||
return self._boot("/servers", "server", name, image, flavor,
|
||||
ipgroup=ipgroup, meta=meta, files=files,
|
||||
zone_blob=zone_blob, reservation_id=reservation_id)
|
||||
|
||||
def update(self, server, name=None, password=None):
|
||||
"""
|
||||
|
@ -218,42 +218,7 @@ class OpenStackShell(object):
|
||||
server = self._find_server(args.server)
|
||||
server.backup_schedule.delete()
|
||||
|
||||
@arg('--flavor',
|
||||
default=None,
|
||||
metavar='<flavor>',
|
||||
help="Flavor ID (see 'novaclient flavors'). "\
|
||||
"Defaults to 256MB RAM instance.")
|
||||
@arg('--image',
|
||||
default=None,
|
||||
metavar='<image>',
|
||||
help="Image ID (see 'novaclient images'). "\
|
||||
"Defaults to Ubuntu 10.04 LTS.")
|
||||
@arg('--ipgroup',
|
||||
default=None,
|
||||
metavar='<group>',
|
||||
help="IP group name or ID (see 'novaclient ipgroup-list').")
|
||||
@arg('--meta',
|
||||
metavar="<key=value>",
|
||||
action='append',
|
||||
default=[],
|
||||
help="Record arbitrary key/value metadata. "\
|
||||
"May be give multiple times.")
|
||||
@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.")
|
||||
@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.")
|
||||
@arg('name', metavar='<name>', help='Name for the new server')
|
||||
def do_boot(self, args):
|
||||
def _boot(self, args, reservation_id=None):
|
||||
"""Boot a new server."""
|
||||
flavor = args.flavor or self.cs.flavors.find(ram=256)
|
||||
image = args.image or self.cs.images.find(name="Ubuntu 10.04 LTS "\
|
||||
@ -297,10 +262,109 @@ class OpenStackShell(object):
|
||||
except IOError, e:
|
||||
raise CommandError("Can't open '%s': %s" % (keyfile, e))
|
||||
|
||||
server = self.cs.servers.create(args.name, image, flavor, ipgroup,
|
||||
metadata, files)
|
||||
return (args.name, image, flavor, ipgroup, metadata, files,
|
||||
reservation_id)
|
||||
|
||||
@arg('--flavor',
|
||||
default=None,
|
||||
metavar='<flavor>',
|
||||
help="Flavor ID (see 'novaclient flavors'). "\
|
||||
"Defaults to 256MB RAM instance.")
|
||||
@arg('--image',
|
||||
default=None,
|
||||
metavar='<image>',
|
||||
help="Image ID (see 'novaclient images'). "\
|
||||
"Defaults to Ubuntu 10.04 LTS.")
|
||||
@arg('--ipgroup',
|
||||
default=None,
|
||||
metavar='<group>',
|
||||
help="IP group name or ID (see 'novaclient ipgroup-list').")
|
||||
@arg('--meta',
|
||||
metavar="<key=value>",
|
||||
action='append',
|
||||
default=[],
|
||||
help="Record arbitrary key/value metadata. "\
|
||||
"May be give multiple times.")
|
||||
@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.")
|
||||
@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.")
|
||||
@arg('name', metavar='<name>', help='Name for the new server')
|
||||
def do_boot(self, args):
|
||||
"""Boot a new server."""
|
||||
name, image, flavor, ipgroup, metadata, files, reservation_id = \
|
||||
self._boot(args)
|
||||
|
||||
server = self.cs.servers.create(args.name, image, flavor,
|
||||
ipgroup=ipgroup,
|
||||
meta=metadata,
|
||||
files=files)
|
||||
print_dict(server._info)
|
||||
|
||||
@arg('--flavor',
|
||||
default=None,
|
||||
metavar='<flavor>',
|
||||
help="Flavor ID (see 'novaclient flavors'). "\
|
||||
"Defaults to 256MB RAM instance.")
|
||||
@arg('--image',
|
||||
default=None,
|
||||
metavar='<image>',
|
||||
help="Image ID (see 'novaclient images'). "\
|
||||
"Defaults to Ubuntu 10.04 LTS.")
|
||||
@arg('--ipgroup',
|
||||
default=None,
|
||||
metavar='<group>',
|
||||
help="IP group name or ID (see 'novaclient ipgroup-list').")
|
||||
@arg('--meta',
|
||||
metavar="<key=value>",
|
||||
action='append',
|
||||
default=[],
|
||||
help="Record arbitrary key/value metadata. "\
|
||||
"May be give multiple times.")
|
||||
@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.")
|
||||
@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.")
|
||||
@arg('--reservation_id',
|
||||
default=None,
|
||||
metavar='<reservation_id>',
|
||||
help="Reservation ID (a UUID). "\
|
||||
"If unspecified will be generated by the server.")
|
||||
@arg('name', metavar='<name>', help='Name for the new server')
|
||||
def do_zone_boot(self, args):
|
||||
"""Boot a new server, potentially across Zones."""
|
||||
reservation_id = args.reservation_id
|
||||
name, image, flavor, ipgroup, metadata, files, reservation_id = \
|
||||
self._boot(args,
|
||||
reservation_id=reservation_id)
|
||||
|
||||
reservation_id = self.cs.zones.boot(args.name, image, flavor,
|
||||
ipgroup=ipgroup,
|
||||
meta=metadata,
|
||||
files=files,
|
||||
reservation_id=reservation_id)
|
||||
print "Reservation ID=", reservation_id
|
||||
|
||||
def _translate_flavor_keys(self, collection):
|
||||
convert = [('ram', 'memory_mb'), ('disk', 'local_gb')]
|
||||
for item in collection:
|
||||
|
@ -60,7 +60,7 @@ class Zone(base.Resource):
|
||||
self.manager.update(self, api_url, username, password)
|
||||
|
||||
|
||||
class ZoneManager(base.ManagerWithFind):
|
||||
class ZoneManager(base.BootingManagerWithFind):
|
||||
resource_class = Zone
|
||||
|
||||
def info(self):
|
||||
@ -103,6 +103,33 @@ class ZoneManager(base.ManagerWithFind):
|
||||
|
||||
return self._create("/zones", body, "zone")
|
||||
|
||||
def boot(self, name, image, flavor, ipgroup=None, meta=None, files=None,
|
||||
zone_blob=None, reservation_id=None):
|
||||
"""
|
||||
Create (boot) a new server while being aware of Zones.
|
||||
|
||||
:param name: Something to name the server.
|
||||
:param image: The :class:`Image` to boot with.
|
||||
:param flavor: The :class:`Flavor` to boot onto.
|
||||
:param ipgroup: An initial :class:`IPGroup` for this server.
|
||||
:param meta: A dict of arbitrary key/value metadata to store for this
|
||||
server. A maximum of five entries is allowed, and both
|
||||
keys and values must be 255 characters or less.
|
||||
:param files: A dict of files to overrwrite on the server upon boot.
|
||||
Keys are file names (i.e. ``/etc/passwd``) and values
|
||||
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.
|
||||
"""
|
||||
return self._boot("/zones/boot", "reservation_id", name, image, flavor,
|
||||
ipgroup=ipgroup, meta=meta, files=files,
|
||||
zone_blob=zone_blob, reservation_id=reservation_id,
|
||||
return_raw=True)
|
||||
|
||||
def select(self, *args, **kwargs):
|
||||
"""
|
||||
Given requirements for a new instance, select hosts
|
||||
|
Loading…
x
Reference in New Issue
Block a user