Add meta option for passing meta-data

Add a meta option for passing through metadata key/value pairs.  For
snapshot images, this ends up getting passed through to
novaclient.server.create_image(metadata=...).  For uploaded images, the
values are used as custom properties for glanceclient.images.create()

The 255 character limits are taken from the API documentation in [1].

This is initially intended to be used for setting xenapi and vm_mode
settings for disk-image-builder based Rackspace images, as described
in [2].

[1] http://docs.openstack.org/developer/python-novaclient/ref/v1_1/servers.html
[2] https://developer.rackspace.com/blog/custom-images-via-boot-dot-rackspace-dot-com-training-wheels-included/

Change-Id: Ic2f044f44f06830b5b84438d838013d409e642b4
This commit is contained in:
Ian Wienand 2014-09-24 15:20:53 +10:00
parent f206a1b722
commit 34335b5fbf
5 changed files with 45 additions and 10 deletions

View File

@ -220,6 +220,9 @@ same name. Example::
reset: reset_node.sh
username: jenkins
private-key: /var/lib/jenkins/.ssh/id_rsa
meta:
key: value
key2: value
- name: quantal
base-image: 'Quantal'
min-ram: 8192
@ -276,6 +279,11 @@ of availability zones being used. If you need more control of the
distribution you can use multiple logical providers each providing a
different list of availabiltiy zones.
The `meta` section is optional. It is a dict of arbitrary key/value
metadata to store for this server using the nova metadata service. A
maximum of five entries is allowed, and both keys and values must be
255 characters or less.
targets
-------

View File

@ -81,7 +81,8 @@ class FakeList(object):
t.start()
return s
def create_image(self, server, image_name):
def create_image(self, server, image_name, metadata):
# XXX : validate metadata?
x = self.api.images.create(name=image_name)
return x.id

View File

@ -876,7 +876,7 @@ class DiskImageUpdater(ImageUpdater):
stripped_filename = self.filename.replace(".qcow2", "")
image_path = os.path.join(self.imagesdir, stripped_filename)
image_id = self.manager.uploadImage(image_name, image_path,
'qcow2', 'bare')
'qcow2', 'bare', self.image.meta)
self.snap_image.external_id = image_id
session.commit()
self.log.debug("Image id: %s building image %s" %
@ -969,7 +969,8 @@ class SnapshotImageUpdater(ImageUpdater):
self.bootstrapServer(server, key, use_password=use_password)
image_id = self.manager.createImage(server_id, hostname)
image_id = self.manager.createImage(server_id, hostname,
self.image.meta)
self.snap_image.external_id = image_id
session.commit()
self.log.debug("Image id: %s building image %s" %
@ -1250,6 +1251,23 @@ class NodePool(threading.Thread):
i.private_key = image.get('private-key',
'/var/lib/jenkins/.ssh/id_rsa')
# note this does "double-duty" -- for
# SnapshotImageUpdater the meta-data dict is passed to
# nova when the snapshot image is created. For
# DiskImageUpdater, this dict is expanded and used as
# custom properties when the image is uploaded.
i.meta = image.get('meta', {})
# 5 elements, and no key or value can be > 255 chars
# per novaclient.servers.create() rules
if i.meta:
if len(i.meta) > 5 or \
any([len(k) > 255 or len(v) > 255
for k, v in i.meta.iteritems()]):
# soft-fail
self.log.error("Invalid metadata for %s; ignored"
% i.name)
i.meta = {}
for target in config['targets']:
t = Target()
t.name = target['name']
@ -1316,7 +1334,8 @@ class NodePool(threading.Thread):
new_images[k].setup != old_images[k].setup or
new_images[k].reset != old_images[k].reset or
new_images[k].username != old_images[k].username or
new_images[k].private_key != old_images[k].private_key):
new_images[k].private_key != old_images[k].private_key or
new_images[k].meta != old_images[k].meta):
return False
return True

View File

@ -207,12 +207,14 @@ class UploadImageTask(Task):
image = fakeprovider.FakeGlanceClient()
image.update(data='fake')
else:
# configure glance and upload image
# configure glance and upload image. Note the meta flags
# are provided as custom glance properties
glanceclient = self.get_glance_client(self.args['provider'])
image = glanceclient.images.create(
name=self.args['image_name'], is_public=False,
disk_format=self.args['disk_format'],
container_format=self.args['container_format'])
container_format=self.args['container_format'],
**self.args['meta'])
image.update(data=open(self.args['filename'], 'rb'))
glanceclient = None
@ -489,18 +491,20 @@ class ProviderManager(TaskManager):
if newip['instance_id'] == server_id:
return newip['ip']
def createImage(self, server_id, image_name):
def createImage(self, server_id, image_name, meta):
return self.submitTask(CreateImageTask(server=server_id,
image_name=image_name))
image_name=image_name,
metadata=meta))
def getImage(self, image_id):
return self.submitTask(GetImageTask(image=image_id))
def uploadImage(self, image_name, filename, disk_format, container_format):
def uploadImage(self, image_name, filename, disk_format, container_format,
meta):
return self.submitTask(UploadImageTask(
image_name=image_name, filename='%s.%s' % (filename, disk_format),
disk_format=disk_format, container_format=container_format,
provider=self.provider))
provider=self.provider, meta=meta))
def listExtensions(self):
return self.submitTask(ListExtensionsTask())

View File

@ -36,6 +36,9 @@ providers:
base-image: 'Fake Precise'
min-ram: 8192
name-filter: 'Fake'
meta:
key: value
key2: value
setup: prepare_node_devstack.sh
targets: