works properly with zone-boot

This commit is contained in:
Sandy Walsh 2011-05-30 18:04:13 -07:00
parent 6cb04897af
commit 7090a6c8e0
4 changed files with 211 additions and 82 deletions

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