From e601ab4a1068029b2f0b79789ed506fda1332404 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Thu, 16 Dec 2010 20:59:27 -0600 Subject: XenAPI Snapshots first cut --- nova/compute/manager.py | 30 ++++++++- nova/virt/libvirt_conn.py | 7 ++ nova/virt/xenapi/vm_utils.py | 91 ++++++++++++++++++++++++++ nova/virt/xenapi/vmops.py | 43 ++++++++++-- nova/virt/xenapi_conn.py | 7 ++ plugins/xenapi/etc/xapi.d/plugins/glance | 108 +++++++++++++++++++++++++++++++ 6 files changed, 278 insertions(+), 8 deletions(-) create mode 100644 plugins/xenapi/etc/xapi.d/plugins/glance diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 7eb60e262..8fc8d5e1a 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -134,8 +134,10 @@ class ComputeManager(manager.Manager): # TODO(ja): should we keep it in a terminated state for a bit? self.db.instance_destroy(context, instance_id) + #FIXME(sirp): Hacking reboot to snapshot @exception.wrap_exception - def reboot_instance(self, context, instance_id): + def XXreboot_instance(self, context, instance_id): + #def reboot_instance(self, context, instance_id): """Reboot an instance on this server.""" context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) @@ -156,6 +158,32 @@ class ComputeManager(manager.Manager): self.driver.reboot(instance_ref) self._update_state(context, instance_id) + #FIXME(sirp): Hacking reboot to snapshot + @exception.wrap_exception + def reboot_instance(self, context, instance_id): + #def snapshot_instance(self, context, instance_id): + """Snapshot an instance on this server.""" + context = context.elevated() + instance_ref = self.db.instance_get(context, instance_id) + + #FIXME(sirp): update_state currently only refreshes the state field + # if we add is_snapshotting, we will need this refreshed too, + # potentially? + self._update_state(context, instance_id) + + #TODO(sirp): check for is_snapshotting=True here? + if instance_ref['state'] != power_state.RUNNING: + logging.warn('trying to snapshot a non-running ' + 'instance: %s (state: %s excepted: %s)', + instance_ref['internal_id'], + instance_ref['state'], + power_state.RUNNING) + + logging.debug('instance %s: snapshotting', instance_ref['name']) + #TODO(sirp): set is_snapshotting=True here? + self.driver.snapshot(instance_ref) + #self._update_state(context, instance_id) + @exception.wrap_exception def rescue_instance(self, context, instance_id): """Rescue an instance on this server.""" diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 5a8c71850..112ac7d37 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -265,6 +265,13 @@ class LibvirtConnection(object): raise exception.NotFound("No disk at %s" % mount_device) virt_dom.detachDevice(xml) + @exception.wrap_exception + def snapshot(self, instance): + """ Create snapshot from a running VM instance """ + #TODO(sirp): only exists for XenAPI driver for now + raise NotImplementedError( + "Instance snapshotting is not supported for libvirt at this time") + @exception.wrap_exception def reboot(self, instance): self.destroy(instance, False) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 2f5d78e75..c3e8b30b7 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -19,6 +19,7 @@ Helper methods for operations related to the management of VM records and their attributes like VDIs, VIFs, as well as their lookup functions. """ +import time #FIXME(sirp): take this out, replace with greenthread.sleep import logging import urllib from xml.dom import minidom @@ -148,6 +149,63 @@ class VMHelper(): vm_ref, network_ref) return vif_ref + + @classmethod + def create_snapshot(cls, session, vm_ref, label): + logging.debug("Snapshotting VM %s with label '%s'...", vm_ref, label) + + #TODO(sirp): Add quiesce and VSS locking support when Windows support + # is added + + #TODO(sirp): Make safe_lookup_vdi for assert? + vdi_refs = VMHelper.lookup_vm_vdis(session, vm_ref) + if vdi_refs is None or len(vdi_refs) != 1: + raise Exception("Unexpected number of VDIs (%s) found for VM %s" + % (len(vdi_refs), vm_ref)) + vdi_ref = vdi_refs[0] + vdi_rec = session.get_xenapi().VDI.get_record(vdi_ref) + vdi_uuid = vdi_rec["uuid"] + + #NOTE(sirp): We may need to wait for our parent to coalese with his + # parent + original_parent_uuid = get_vhd_parent_uuid(session, vdi_ref) + + task = session.call_xenapi('Async.VM.snapshot', vm_ref, label) + template_vm_ref = session.wait_for_task(task) + logging.debug('Created snapshot %s from VM %s.', template_vm_ref, + vm_ref) + + #NOTE(sirp): wait for any coalescing + #NOTE(sirp): for some reason re-scan wasn't occuring automatically on + # XS5.6 + #TODO(sirp): clean this up, perhaps use LoopingCall + sr_ref = vdi_rec["SR"] + scan_sr(session, sr_ref) + parent_uuid = get_vhd_parent_uuid(session, vdi_ref) + time.sleep(5) + while original_parent_uuid and (parent_uuid != original_parent_uuid): + scan_sr(session, sr_ref) + logging.debug( + "Parent %s doesn't match original parent %s, " + "waiting for coalesce...", parent_uuid, original_parent_uuid) + #TODO(sirp): make this non-blocking + time.sleep(5) + parent_uuid = get_vhd_parent_uuid(session, vdi_ref) + + vdi_uuids = [vdi_uuid, parent_uuid] + return template_vm_ref, vdi_uuids + + + @classmethod + def upload_image(cls, session, vdi_uuids, glance_label): + logging.debug("Asking xapi to upload %s as '%s'", vdi_uuids, + glance_label) + kwargs = {'vdi_uuids': ','.join(vdi_uuids), + 'glance_label': glance_label} + task = session.async_call_plugin('glance', 'put_vdis', kwargs) + session.wait_for_task(task) + + @classmethod def fetch_image(cls, session, image, user, project, use_sr): """use_sr: True to put the image as a VDI in an SR, False to place @@ -258,3 +316,36 @@ def get_rrd(host, uuid): return xml.read() except IOError: return None + + +#TODO(sirp): This code comes from XS5.6 pluginlib.py, we should refactor to +# use that implmenetation +def get_vhd_parent(session, vdi_rec): + """ + Returns the VHD parent of the given VDI record, as a (ref, rec) pair. + Returns None if we're at the root of the tree. + """ + if 'vhd-parent' in vdi_rec['sm_config']: + parent_uuid = vdi_rec['sm_config']['vhd-parent'] + #NOTE(sirp): changed xenapi -> get_xenapi() + parent_ref = session.get_xenapi().VDI.get_by_uuid(parent_uuid) + parent_rec = session.get_xenapi().VDI.get_record(parent_ref) + #NOTE(sirp): changed log -> logging + logging.debug("VHD %s has parent %s", vdi_rec['uuid'], parent_ref) + return parent_ref, parent_rec + else: + return None + +def get_vhd_parent_uuid(session, vdi_ref): + vdi_rec = session.get_xenapi().VDI.get_record(vdi_ref) + ret = get_vhd_parent(session, vdi_rec) + if ret: + parent_ref, parent_rec = ret + return parent_rec["uuid"] + else: + return None + +def scan_sr(session, sr_ref): + logging.debug("Re-scanning SR %s", sr_ref) + task = session.call_xenapi('Async.SR.scan', sr_ref) + session.wait_for_task(task) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 3034df9e1..50b5ec3d6 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -78,6 +78,31 @@ class VMOps(object): self._session.call_xenapi('VM.start', vm_ref, False, False) logging.info('Spawning VM %s created %s.', instance.name, vm_ref) + + def snapshot(self, instance): + """ Create snapshot from a running VM instance """ + #TODO(sirp): Add quiesce and VSS locking support when Windows support + # is added + vm_ref = VMHelper.lookup(self._session, instance.name) + + #TODO(sirp): this is the label in Xen, we need to add a human friendly + # label that we store in paralalx + label = "%s-snapshot" % instance.name + glance_name = "MySnapshot" + + try: + template_vm_ref, template_vdi_uuids = VMHelper.create_snapshot( + self._session, vm_ref, label) + except XenAPI.Failure, exc: + logging.error("Unable to Snapshot %s: %s", vm_ref, exc) + return + + try: + # call plugin to ship snapshot off to glance + VMHelper.upload_image( + self._session, template_vdi_uuids, glance_name) + finally: + self._destroy(template_vm_ref, shutdown=False) def reboot(self, instance): """ Reboot VM instance """ @@ -89,20 +114,24 @@ class VMOps(object): self._session.wait_for_task(task) def destroy(self, instance): - """ Destroy VM instance """ vm = VMHelper.lookup(self._session, instance.name) + return self._destroy(vm, shutdown=True) + + def _destroy(self, vm, shutdown=True): + """ Destroy VM instance """ if vm is None: # Don't complain, just return. This lets us clean up instances # that have already disappeared from the underlying platform. return # Get the VDIs related to the VM vdis = VMHelper.lookup_vm_vdis(self._session, vm) - try: - task = self._session.call_xenapi('Async.VM.hard_shutdown', - vm) - self._session.wait_for_task(task) - except XenAPI.Failure, exc: - logging.warn(exc) + if shutdown: + try: + task = self._session.call_xenapi('Async.VM.hard_shutdown', + vm) + self._session.wait_for_task(task) + except XenAPI.Failure, exc: + logging.warn(exc) # Disk clean-up if vdis: for vdi in vdis: diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 6beb08f5e..8aacec507 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -115,6 +115,13 @@ class XenAPIConnection(object): """ Create VM instance """ self._vmops.spawn(instance) + + def snapshot(self, instance): + """ Create snapshot from a running VM instance """ + #TODO(sirp): Add quiesce and VSS locking support when Windows support + # is added + self._vmops.snapshot(instance) + def reboot(self, instance): """ Reboot VM instance """ self._vmops.reboot(instance) diff --git a/plugins/xenapi/etc/xapi.d/plugins/glance b/plugins/xenapi/etc/xapi.d/plugins/glance new file mode 100644 index 000000000..55d71b79e --- /dev/null +++ b/plugins/xenapi/etc/xapi.d/plugins/glance @@ -0,0 +1,108 @@ +#!/usr/bin/env python + +# Copyright (c) 2010 Citrix Systems, Inc. +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +# +# XenAPI plugin for putting images into glance +# + +import base64 +import errno +import hmac +import os +import os.path +import sha +import subprocess +import time +import urlparse + +import XenAPIPlugin + +#FIXME(sirp): should this use pluginlib from 5.6? +from pluginlib_nova import * +configure_logging('glance') + +#FIXME(sirp): Should this just be 64K like elsewhere +CHUNK_SIZE = 4096 + +FILE_SR_PATH = '/var/run/sr-mount' + +#TODO(sirp): use get_vhd_parent code from Citrix guys +#TODO(sirp): look for Citrix guys get VDI from VM_ref (does this exist in +#pluginlib? +# Need to add glance client to plugins (how do we get this onto XenServer +# machine?) +# WHen do we cleanup unused VDI's in SR + +def put_vdis(session, args): + vdi_uuids = exists(args, 'vdi_uuids').split(',') + glance_label = exists(args, 'glance_label') + + sr_path = get_sr_path(session) + tar_cmd = ['tar', '-zc', '--directory=%s' % sr_path] + paths = [ "%s.vhd" % vdi_uuid for vdi_uuid in vdi_uuids ] + tar_cmd.extend(paths) + logging.debug("Bundling image with cmd: %s", tar_cmd) + tar_proc = subprocess.Popen(tar_cmd, stdout=subprocess.PIPE) + + test_file = "%s.tar.gz" % os.path.join('/tmp', glance_label) + logging.debug("Writing to test file %s", test_file) + bundle = tar_proc.stdout + f = open(test_file, 'w') + try: + chunk = bundle.read(CHUNK_SIZE) + while chunk: + f.write(chunk) + chunk = bundle.read(CHUNK_SIZE) + finally: + f.close() + + return "" # FIXME(sirp): return anything useful here? + + +def get_sr_path(session): + sr_ref = find_sr(session) + + if sr_ref is None: + raise Exception('Cannot find SR to read VDI from') + + sr_rec = session.xenapi.SR.get_record(sr_ref) + sr_uuid = sr_rec["uuid"] + sr_path = os.path.join(FILE_SR_PATH, sr_uuid) + return sr_path + + +#TODO(sirp): both objectstore and glance need this, should this be refactored +#into common lib +def find_sr(session): + host = get_this_host(session) + srs = session.xenapi.SR.get_all() + for sr in srs: + sr_rec = session.xenapi.SR.get_record(sr) + if not ('i18n-key' in sr_rec['other_config'] and + sr_rec['other_config']['i18n-key'] == 'local-storage'): + continue + for pbd in sr_rec['PBDs']: + pbd_rec = session.xenapi.PBD.get_record(pbd) + if pbd_rec['host'] == host: + return sr + return None + + +if __name__ == '__main__': + XenAPIPlugin.dispatch({'put_vdis': put_vdis}) -- cgit From ae54d5bdf3e0615c5be9ebe4f03f7256f22484ee Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Fri, 17 Dec 2010 14:21:03 -0600 Subject: Add wait_for_vhd_coalesce --- nova/virt/xenapi/vm_utils.py | 52 +++++++++++++++++++++++++++++--------------- nova/virt/xenapi/vmops.py | 3 +++ 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index c3e8b30b7..1b18b0a01 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -175,26 +175,12 @@ class VMHelper(): logging.debug('Created snapshot %s from VM %s.', template_vm_ref, vm_ref) - #NOTE(sirp): wait for any coalescing - #NOTE(sirp): for some reason re-scan wasn't occuring automatically on - # XS5.6 - #TODO(sirp): clean this up, perhaps use LoopingCall sr_ref = vdi_rec["SR"] - scan_sr(session, sr_ref) - parent_uuid = get_vhd_parent_uuid(session, vdi_ref) - time.sleep(5) - while original_parent_uuid and (parent_uuid != original_parent_uuid): - scan_sr(session, sr_ref) - logging.debug( - "Parent %s doesn't match original parent %s, " - "waiting for coalesce...", parent_uuid, original_parent_uuid) - #TODO(sirp): make this non-blocking - time.sleep(5) - parent_uuid = get_vhd_parent_uuid(session, vdi_ref) - - vdi_uuids = [vdi_uuid, parent_uuid] - return template_vm_ref, vdi_uuids + parent_uuid = wait_for_vhd_coalesce( + session, sr_ref, vdi_ref, original_parent_uuid) + #TODO(sirp): we need to assert only one parent, not parents two deep + return template_vm_ref, [vdi_uuid, parent_uuid] @classmethod def upload_image(cls, session, vdi_uuids, glance_label): @@ -336,6 +322,7 @@ def get_vhd_parent(session, vdi_rec): else: return None + def get_vhd_parent_uuid(session, vdi_ref): vdi_rec = session.get_xenapi().VDI.get_record(vdi_ref) ret = get_vhd_parent(session, vdi_rec) @@ -345,7 +332,36 @@ def get_vhd_parent_uuid(session, vdi_ref): else: return None + def scan_sr(session, sr_ref): logging.debug("Re-scanning SR %s", sr_ref) task = session.call_xenapi('Async.SR.scan', sr_ref) session.wait_for_task(task) + + +def wait_for_vhd_coalesce(session, sr_ref, vdi_ref, original_parent_uuid): + """ TODO Explain why coalescing has to occur here """ + #TODO(sirp): we need to timeout this req after a while + #NOTE(sirp): for some reason re-scan wasn't occuring automatically on + # XS5.6 + #TODO(sirp): clean this up, perhaps use LoopingCall + def _get_vhd_parent_uuid_with_refresh(first_time): + if not first_time: + #TODO(sirp): should this interval be a gflag? + #TODO(sirp): make this non-blocking + time.sleep(5) + scan_sr(session, sr_ref) + return get_vhd_parent_uuid(session, vdi_ref) + + parent_uuid = _get_vhd_parent_uuid_with_refresh(first_time=True) + logging.debug( + "Parent %s doesn't match original parent %s, " + "waiting for coalesce...", parent_uuid, original_parent_uuid) + while original_parent_uuid and (parent_uuid != original_parent_uuid): + logging.debug( + "Parent %s doesn't match original parent %s, " + "waiting for coalesce...", parent_uuid, original_parent_uuid) + parent_uuid = _get_vhd_parent_uuid_with_refresh(first_time=False) + + return parent_uuid + diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 50b5ec3d6..988c54d6d 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -81,6 +81,7 @@ class VMOps(object): def snapshot(self, instance): """ Create snapshot from a running VM instance """ + logging.debug("Starting snapshot for VM %s", instance) #TODO(sirp): Add quiesce and VSS locking support when Windows support # is added vm_ref = VMHelper.lookup(self._session, instance.name) @@ -104,6 +105,8 @@ class VMOps(object): finally: self._destroy(template_vm_ref, shutdown=False) + logging.debug("Finished snapshot and upload for VM %s", instance) + def reboot(self, instance): """ Reboot VM instance """ instance_name = instance.name -- cgit From 650a0cdbc854d37fd62348ce34a14ef91ccbabad Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Fri, 17 Dec 2010 19:17:39 -0600 Subject: Cleaned up TODOs, using flags now --- nova/compute/manager.py | 3 +- nova/flags.py | 3 ++ nova/virt/xenapi/vm_utils.py | 50 +++++++++++---------- nova/virt/xenapi/vmops.py | 15 +++---- nova/virt/xenapi_conn.py | 9 ++-- plugins/xenapi/etc/xapi.d/plugins/glance | 76 ++++++++++++++++++++++---------- 6 files changed, 95 insertions(+), 61 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 8fc8d5e1a..b2584773a 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -181,7 +181,8 @@ class ComputeManager(manager.Manager): logging.debug('instance %s: snapshotting', instance_ref['name']) #TODO(sirp): set is_snapshotting=True here? - self.driver.snapshot(instance_ref) + glance_name = "MySnapshot3" + self.driver.snapshot(instance_ref, glance_name) #self._update_state(context, instance_id) @exception.wrap_exception diff --git a/nova/flags.py b/nova/flags.py index 87444565a..3318af058 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -212,6 +212,9 @@ DEFINE_string('aws_access_key_id', 'admin', 'AWS Access ID') DEFINE_string('aws_secret_access_key', 'admin', 'AWS Access Key') DEFINE_integer('s3_port', 3333, 's3 port') DEFINE_string('s3_host', '127.0.0.1', 's3 host') +DEFINE_integer('glance_port', 9292, 'glance port') +DEFINE_string('glance_host', '127.0.0.1', 'glance host') +DEFINE_string('glance_storage_location', 'swift://username:api_key@auth.api.rackspacecloud.com/v1.0/cloudservers', 'glance storage location') DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on') DEFINE_string('scheduler_topic', 'scheduler', 'the topic scheduler nodes listen on') diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 1b18b0a01..729f0daaf 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -19,11 +19,12 @@ Helper methods for operations related to the management of VM records and their attributes like VDIs, VIFs, as well as their lookup functions. """ -import time #FIXME(sirp): take this out, replace with greenthread.sleep import logging +import pickle import urllib from xml.dom import minidom +from eventlet import event from nova import flags from nova import utils from nova.auth.manager import AuthManager @@ -166,8 +167,6 @@ class VMHelper(): vdi_rec = session.get_xenapi().VDI.get_record(vdi_ref) vdi_uuid = vdi_rec["uuid"] - #NOTE(sirp): We may need to wait for our parent to coalese with his - # parent original_parent_uuid = get_vhd_parent_uuid(session, vdi_ref) task = session.call_xenapi('Async.VM.snapshot', vm_ref, label) @@ -186,8 +185,14 @@ class VMHelper(): def upload_image(cls, session, vdi_uuids, glance_label): logging.debug("Asking xapi to upload %s as '%s'", vdi_uuids, glance_label) - kwargs = {'vdi_uuids': ','.join(vdi_uuids), - 'glance_label': glance_label} + + params = {'vdi_uuids': vdi_uuids, + 'glance_label': glance_label, + 'glance_storage_location': FLAGS.glance_storage_location, + 'glance_host': FLAGS.glance_host, + 'glance_port': FLAGS.glance_port} + + kwargs = {'params': pickle.dumps(params)} task = session.async_call_plugin('glance', 'put_vdis', kwargs) session.wait_for_task(task) @@ -341,27 +346,24 @@ def scan_sr(session, sr_ref): def wait_for_vhd_coalesce(session, sr_ref, vdi_ref, original_parent_uuid): """ TODO Explain why coalescing has to occur here """ - #TODO(sirp): we need to timeout this req after a while #NOTE(sirp): for some reason re-scan wasn't occuring automatically on # XS5.6 - #TODO(sirp): clean this up, perhaps use LoopingCall - def _get_vhd_parent_uuid_with_refresh(first_time): - if not first_time: - #TODO(sirp): should this interval be a gflag? - #TODO(sirp): make this non-blocking - time.sleep(5) - scan_sr(session, sr_ref) - return get_vhd_parent_uuid(session, vdi_ref) - - parent_uuid = _get_vhd_parent_uuid_with_refresh(first_time=True) - logging.debug( - "Parent %s doesn't match original parent %s, " - "waiting for coalesce...", parent_uuid, original_parent_uuid) - while original_parent_uuid and (parent_uuid != original_parent_uuid): - logging.debug( - "Parent %s doesn't match original parent %s, " - "waiting for coalesce...", parent_uuid, original_parent_uuid) - parent_uuid = _get_vhd_parent_uuid_with_refresh(first_time=False) + #TODO(sirp): we need to timeout this req after a while + def _poll_vhds(): + scan_sr(session, sr_ref) + parent_uuid = get_vhd_parent_uuid(session, vdi_ref) + if original_parent_uuid and (parent_uuid != original_parent_uuid): + logging.debug( + "Parent %s doesn't match original parent %s, " + "waiting for coalesce...", parent_uuid, original_parent_uuid) + else: + done.send(parent_uuid) + + done = event.Event() + loop = utils.LoopingCall(_poll_vhds) + loop.start(FLAGS.xenapi_vhd_coalesce_poll_interval, now=True) + parent_uuid = done.wait() + loop.stop() return parent_uuid diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 988c54d6d..a44492d57 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -79,29 +79,26 @@ class VMOps(object): logging.info('Spawning VM %s created %s.', instance.name, vm_ref) - def snapshot(self, instance): + def snapshot(self, instance, name): """ Create snapshot from a running VM instance """ - logging.debug("Starting snapshot for VM %s", instance) + #TODO(sirp): Add quiesce and VSS locking support when Windows support # is added + + logging.debug("Starting snapshot for VM %s", instance) vm_ref = VMHelper.lookup(self._session, instance.name) - #TODO(sirp): this is the label in Xen, we need to add a human friendly - # label that we store in paralalx label = "%s-snapshot" % instance.name - glance_name = "MySnapshot" - try: template_vm_ref, template_vdi_uuids = VMHelper.create_snapshot( self._session, vm_ref, label) except XenAPI.Failure, exc: logging.error("Unable to Snapshot %s: %s", vm_ref, exc) return - + try: # call plugin to ship snapshot off to glance - VMHelper.upload_image( - self._session, template_vdi_uuids, glance_name) + VMHelper.upload_image(self._session, template_vdi_uuids, name) finally: self._destroy(template_vm_ref, shutdown=False) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 8aacec507..92e66d32d 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -77,7 +77,10 @@ flags.DEFINE_float('xenapi_task_poll_interval', 'The interval used for polling of remote tasks ' '(Async.VM.start, etc). Used only if ' 'connection_type=xenapi.') - +flags.DEFINE_float('xenapi_vhd_coalesce_poll_interval', + 5.0, + 'The interval used for polling of coalescing vhds.' + ' Used only if connection_type=xenapi.') XenAPI = None @@ -116,11 +119,11 @@ class XenAPIConnection(object): self._vmops.spawn(instance) - def snapshot(self, instance): + def snapshot(self, instance, name): """ Create snapshot from a running VM instance """ #TODO(sirp): Add quiesce and VSS locking support when Windows support # is added - self._vmops.snapshot(instance) + self._vmops.snapshot(instance, name) def reboot(self, instance): """ Reboot VM instance """ diff --git a/plugins/xenapi/etc/xapi.d/plugins/glance b/plugins/xenapi/etc/xapi.d/plugins/glance index 55d71b79e..13e79ff9f 100644 --- a/plugins/xenapi/etc/xapi.d/plugins/glance +++ b/plugins/xenapi/etc/xapi.d/plugins/glance @@ -24,8 +24,10 @@ import base64 import errno import hmac +import httplib import os import os.path +import pickle import sha import subprocess import time @@ -37,43 +39,69 @@ import XenAPIPlugin from pluginlib_nova import * configure_logging('glance') -#FIXME(sirp): Should this just be 64K like elsewhere -CHUNK_SIZE = 4096 - +CHUNK_SIZE = 8192 FILE_SR_PATH = '/var/run/sr-mount' -#TODO(sirp): use get_vhd_parent code from Citrix guys -#TODO(sirp): look for Citrix guys get VDI from VM_ref (does this exist in -#pluginlib? -# Need to add glance client to plugins (how do we get this onto XenServer -# machine?) -# WHen do we cleanup unused VDI's in SR - def put_vdis(session, args): - vdi_uuids = exists(args, 'vdi_uuids').split(',') - glance_label = exists(args, 'glance_label') + params = pickle.loads(exists(args, 'params')) + vdi_uuids = params["vdi_uuids"] + glance_label = params["glance_label"] + glance_host = params["glance_host"] + glance_port = params["glance_port"] + glance_storage_location = params["glance_storage_location"] sr_path = get_sr_path(session) - tar_cmd = ['tar', '-zc', '--directory=%s' % sr_path] + #FIXME(sirp): writing to a temp file until Glance supports chunked-PUTs + tmp_file = "%s.tar.gz" % os.path.join('/tmp', glance_label) + tar_cmd = ['tar', '-zcf', tmp_file, '--directory=%s' % sr_path] paths = [ "%s.vhd" % vdi_uuid for vdi_uuid in vdi_uuids ] tar_cmd.extend(paths) logging.debug("Bundling image with cmd: %s", tar_cmd) - tar_proc = subprocess.Popen(tar_cmd, stdout=subprocess.PIPE) - - test_file = "%s.tar.gz" % os.path.join('/tmp', glance_label) - logging.debug("Writing to test file %s", test_file) - bundle = tar_proc.stdout - f = open(test_file, 'w') + subprocess.call(tar_cmd) + logging.debug("Writing to test file %s", tmp_file) + put_bundle_in_glance(tmp_file, glance_label, glance_storage_location, + glance_host, glance_port) + return "" # FIXME(sirp): return anything useful here? + + +def put_bundle_in_glance(tmp_file, glance_label, glance_storage_location, + glance_host, glance_port): + + size = os.path.getsize(tmp_file) + + basename = os.path.basename(tmp_file) + location = os.path.join(glance_storage_location, basename) + + bundle = open(tmp_file, 'r') try: + headers = { + 'x-image-meta-is_public': 'True', + 'x-image-meta-name': glance_label, + 'x-image-meta-location': location, + 'x-image-meta-size': size, + 'content-length': size, + } + conn = httplib.HTTPConnection(glance_host, glance_port) + #NOTE(sirp): httplib under python2.4 won't accept a file-like object + # to request + conn.putrequest('POST', '/images') + + for header, value in headers.iteritems(): + conn.putheader(header, value) + conn.endheaders() + chunk = bundle.read(CHUNK_SIZE) while chunk: - f.write(chunk) + conn.send(chunk) chunk = bundle.read(CHUNK_SIZE) - finally: - f.close() - - return "" # FIXME(sirp): return anything useful here? + + res = conn.getresponse() + #FIXME(sirp): should this be 201 Created? + if res.status != httplib.OK: + raise Exception("Unexpected response from Glance %i" % res.status) + finally: + bundle.close() def get_sr_path(session): sr_ref = find_sr(session) -- cgit From 40c8a8a1a1e834c4e5bb61c853397a90475d83ff Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Mon, 20 Dec 2010 17:36:10 -0600 Subject: Typo fix, stubbing out to use admin project for now --- nova/api/openstack/__init__.py | 4 +++- nova/api/openstack/images.py | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index b9ecbd9b8..178838dfd 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -89,7 +89,9 @@ class AuthMiddleware(wsgi.Middleware): if not user: return faults.Fault(webob.exc.HTTPUnauthorized()) - req.environ['nova.context'] = context.RequestContext(user, user) + #FIXME(sirp): stubbing project to admin for now + project = manager.AuthManager().get_project('admin') + req.environ['nova.context'] = context.RequestContext(user, project) return self.application diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 4a0a8e6f1..78849223d 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -46,8 +46,9 @@ class Controller(wsgi.Controller): def detail(self, req): """Return all public images in detail.""" + ctxt = req.environ['nova.context'] try: - images = self._service.detail(req.environ['nova.context']) + images = self._service.detail(ctxt) images = nova.api.openstack.limited(images, req) except NotImplementedError: # Emulate detail() using repeated calls to show() -- cgit From af4d6e84c67b8f59f63ef0275778fa897dac9e95 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Wed, 22 Dec 2010 13:01:33 -0600 Subject: Getting Snapshots to work with cloudservers command-line tool --- nova/api/openstack/__init__.py | 3 +-- nova/api/openstack/images.py | 8 +++++--- nova/compute/api.py | 9 +++++++++ nova/compute/manager.py | 11 +++-------- nova/flags.py | 2 +- nova/image/glance.py | 5 ++++- nova/virt/xenapi/vm_utils.py | 8 +++----- plugins/xenapi/etc/xapi.d/plugins/glance | 20 ++++++++------------ 8 files changed, 34 insertions(+), 32 deletions(-) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 44f3aafec..dba008111 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -89,8 +89,7 @@ class AuthMiddleware(wsgi.Middleware): if not user: return faults.Fault(webob.exc.HTTPUnauthorized()) - #FIXME(sirp): stubbing project to admin for now - project = manager.AuthManager().get_project('admin') + project = manager.AuthManager().get_project(FLAGS.default_project) req.environ['nova.context'] = context.RequestContext(user, project) return self.application diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 78849223d..de072b28f 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -66,9 +66,11 @@ class Controller(wsgi.Controller): raise faults.Fault(exc.HTTPNotFound()) def create(self, req): - # Only public images are supported for now, so a request to - # make a backup of a server cannot be supproted. - raise faults.Fault(exc.HTTPNotFound()) + ctxt = req.environ['nova.context'] + env = self._deserialize(req.body, req) + data = {'instance_id': env["image"]["serverId"], + 'name': env["image"]["name"] } + return dict(image=self._service.create(ctxt, data)) def update(self, req, id): # Users may not modify public images, and that's all that diff --git a/nova/compute/api.py b/nova/compute/api.py index c740814da..6864e694e 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -271,6 +271,15 @@ class ComputeAPI(base.Base): def get_instance(self, context, instance_id): return self.db.instance_get_by_internal_id(context, instance_id) + def snapshot(self, context, instance_id, name): + """Snapshot the given instance.""" + instance = self.db.instance_get_by_internal_id(context, instance_id) + host = instance['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "snapshot_instance", + "args": {"instance_id": instance['id'], "name": name}}) + def reboot(self, context, instance_id): """Reboot the given instance.""" instance = self.db.instance_get_by_internal_id(context, instance_id) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 56b11c4ed..f74eacaf5 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -134,10 +134,8 @@ class ComputeManager(manager.Manager): # TODO(ja): should we keep it in a terminated state for a bit? self.db.instance_destroy(context, instance_id) - #FIXME(sirp): Hacking reboot to snapshot @exception.wrap_exception - def XXreboot_instance(self, context, instance_id): - #def reboot_instance(self, context, instance_id): + def reboot_instance(self, context, instance_id): """Reboot an instance on this server.""" context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) @@ -158,10 +156,8 @@ class ComputeManager(manager.Manager): self.driver.reboot(instance_ref) self._update_state(context, instance_id) - #FIXME(sirp): Hacking reboot to snapshot @exception.wrap_exception - def reboot_instance(self, context, instance_id): - #def snapshot_instance(self, context, instance_id): + def snapshot_instance(self, context, instance_id, name): """Snapshot an instance on this server.""" context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) @@ -181,8 +177,7 @@ class ComputeManager(manager.Manager): logging.debug('instance %s: snapshotting', instance_ref['name']) #TODO(sirp): set is_snapshotting=True here? - glance_name = "MySnapshot3" - self.driver.snapshot(instance_ref, glance_name) + self.driver.snapshot(instance_ref, name) #self._update_state(context, instance_id) @exception.wrap_exception diff --git a/nova/flags.py b/nova/flags.py index 51956d5cf..cf4614b8d 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -214,7 +214,6 @@ DEFINE_integer('s3_port', 3333, 's3 port') DEFINE_string('s3_host', '127.0.0.1', 's3 host') DEFINE_integer('glance_port', 9292, 'glance port') DEFINE_string('glance_host', '127.0.0.1', 'glance host') -DEFINE_string('glance_storage_location', 'swift://username:api_key@auth.api.rackspacecloud.com/v1.0/cloudservers', 'glance storage location') DEFINE_string('compute_topic', 'compute', 'the topic compute nodes listen on') DEFINE_string('scheduler_topic', 'scheduler', 'the topic scheduler nodes listen on') @@ -236,6 +235,7 @@ DEFINE_string('control_exchange', 'nova', 'the main exchange to connect to') DEFINE_string('ec2_url', 'http://127.0.0.1:8773/services/Cloud', 'Url to ec2 api server') +DEFINE_string('default_project', 'openstack', 'default project for openstack') DEFINE_string('default_image', 'ami-11111', 'default image to use, testing only') DEFINE_string('default_instance_type', 'm1.small', diff --git a/nova/image/glance.py b/nova/image/glance.py index 1ca6cf2eb..84e4eaa7d 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -24,6 +24,7 @@ import urlparse import webob.exc +from nova.compute import api as compute_api from nova import utils from nova import flags from nova import exception @@ -202,7 +203,9 @@ class GlanceImageService(nova.image.service.BaseImageService): :raises AlreadyExists if the image already exist. """ - return self.parallax.add_image_metadata(data) + instance_id = data["instance_id"] + name = data["name"] + compute_api.ComputeAPI().snapshot(context, instance_id, name) def update(self, context, image_id, data): """Replace the contents of the given image with the new data. diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index a4501cdbb..7a93c44c7 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -183,13 +183,11 @@ class VMHelper(): return template_vm_ref, [vdi_uuid, parent_uuid] @classmethod - def upload_image(cls, session, vdi_uuids, glance_label): - logging.debug("Asking xapi to upload %s as '%s'", vdi_uuids, - glance_label) + def upload_image(cls, session, vdi_uuids, image_name): + logging.debug("Asking xapi to upload %s as '%s'", vdi_uuids, image_name) params = {'vdi_uuids': vdi_uuids, - 'glance_label': glance_label, - 'glance_storage_location': FLAGS.glance_storage_location, + 'image_name': image_name, 'glance_host': FLAGS.glance_host, 'glance_port': FLAGS.glance_port} diff --git a/plugins/xenapi/etc/xapi.d/plugins/glance b/plugins/xenapi/etc/xapi.d/plugins/glance index 13e79ff9f..5e648b970 100644 --- a/plugins/xenapi/etc/xapi.d/plugins/glance +++ b/plugins/xenapi/etc/xapi.d/plugins/glance @@ -45,41 +45,37 @@ FILE_SR_PATH = '/var/run/sr-mount' def put_vdis(session, args): params = pickle.loads(exists(args, 'params')) vdi_uuids = params["vdi_uuids"] - glance_label = params["glance_label"] + image_name = params["image_name"] glance_host = params["glance_host"] glance_port = params["glance_port"] - glance_storage_location = params["glance_storage_location"] sr_path = get_sr_path(session) #FIXME(sirp): writing to a temp file until Glance supports chunked-PUTs - tmp_file = "%s.tar.gz" % os.path.join('/tmp', glance_label) + tmp_file = "%s.tar.gz" % os.path.join('/tmp', image_name) tar_cmd = ['tar', '-zcf', tmp_file, '--directory=%s' % sr_path] paths = [ "%s.vhd" % vdi_uuid for vdi_uuid in vdi_uuids ] tar_cmd.extend(paths) logging.debug("Bundling image with cmd: %s", tar_cmd) subprocess.call(tar_cmd) logging.debug("Writing to test file %s", tmp_file) - put_bundle_in_glance(tmp_file, glance_label, glance_storage_location, - glance_host, glance_port) + put_bundle_in_glance(tmp_file, image_name, glance_host, glance_port) return "" # FIXME(sirp): return anything useful here? -def put_bundle_in_glance(tmp_file, glance_label, glance_storage_location, - glance_host, glance_port): - +def put_bundle_in_glance(tmp_file, image_name, glance_host, glance_port): size = os.path.getsize(tmp_file) - basename = os.path.basename(tmp_file) - location = os.path.join(glance_storage_location, basename) bundle = open(tmp_file, 'r') try: headers = { + 'x-image-meta-store': 'file', 'x-image-meta-is_public': 'True', - 'x-image-meta-name': glance_label, - 'x-image-meta-location': location, + 'x-image-meta-type': 'raw', + 'x-image-meta-name': image_name, 'x-image-meta-size': size, 'content-length': size, + 'content-type': 'application/octet-stream', } conn = httplib.HTTPConnection(glance_host, glance_port) #NOTE(sirp): httplib under python2.4 won't accept a file-like object -- cgit From 7c03b9aa49b390e13cfbe8315a62c660778ef854 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Wed, 22 Dec 2010 14:00:31 -0600 Subject: i18n support for xs-snaps --- nova/compute/manager.py | 11 ++++------ nova/flags.py | 2 +- nova/virt/libvirt_conn.py | 4 ++-- nova/virt/xenapi/vm_utils.py | 50 +++++++++++++++++++++++++------------------- nova/virt/xenapi/vmops.py | 26 +++++++++++++---------- nova/virt/xenapi_conn.py | 2 -- 6 files changed, 51 insertions(+), 44 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index f9124aa6c..cf459d96a 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -162,23 +162,20 @@ class ComputeManager(manager.Manager): context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) - #FIXME(sirp): update_state currently only refreshes the state field + #NOTE(sirp): update_state currently only refreshes the state field # if we add is_snapshotting, we will need this refreshed too, # potentially? self._update_state(context, instance_id) - #TODO(sirp): check for is_snapshotting=True here? + logging.debug(_('instance %s: snapshotting'), instance_ref['name']) if instance_ref['state'] != power_state.RUNNING: - logging.warn('trying to snapshot a non-running ' - 'instance: %s (state: %s excepted: %s)', + logging.warn(_('trying to snapshot a non-running ' + 'instance: %s (state: %s excepted: %s)'), instance_ref['internal_id'], instance_ref['state'], power_state.RUNNING) - logging.debug('instance %s: snapshotting', instance_ref['name']) - #TODO(sirp): set is_snapshotting=True here? self.driver.snapshot(instance_ref, name) - #self._update_state(context, instance_id) @exception.wrap_exception def rescue_instance(self, context, instance_id): diff --git a/nova/flags.py b/nova/flags.py index b61447c76..d773a7e4c 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -213,7 +213,7 @@ DEFINE_string('connection_type', 'libvirt', 'libvirt, xenapi or fake') DEFINE_string('aws_access_key_id', 'admin', 'AWS Access ID') DEFINE_string('aws_secret_access_key', 'admin', 'AWS Access Key') DEFINE_integer('glance_port', 9292, 'glance port') -DEFINE_string('glance_host', utils.get_my_ip(),, 'glance host') +DEFINE_string('glance_host', utils.get_my_ip(), 'glance host') DEFINE_integer('s3_port', 3333, 's3 port') DEFINE_string('s3_host', utils.get_my_ip(), 's3 host (for infrastructure)') DEFINE_string('s3_dmz', utils.get_my_ip(), 's3 dmz ip (for instances)') diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 3b5954b3b..c8d0aa306 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -239,9 +239,9 @@ class LibvirtConnection(object): @exception.wrap_exception def snapshot(self, instance): """ Create snapshot from a running VM instance """ - #TODO(sirp): only exists for XenAPI driver for now raise NotImplementedError( - "Instance snapshotting is not supported for libvirt at this time") + _("Instance snapshotting is not supported for libvirt" + "at this time")) @exception.wrap_exception def reboot(self, instance): diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index f4e608e4d..5b1c36418 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -153,17 +153,22 @@ class VMHelper(): @classmethod - def create_snapshot(cls, session, vm_ref, label): - logging.debug("Snapshotting VM %s with label '%s'...", vm_ref, label) + def create_snapshot(cls, session, instance_id, vm_ref, label): + logging.debug(_("Snapshotting VM %s with label '%s'..."), vm_ref, label) #TODO(sirp): Add quiesce and VSS locking support when Windows support # is added #TODO(sirp): Make safe_lookup_vdi for assert? vdi_refs = VMHelper.lookup_vm_vdis(session, vm_ref) - if vdi_refs is None or len(vdi_refs) != 1: - raise Exception("Unexpected number of VDIs (%s) found for VM %s" - % (len(vdi_refs), vm_ref)) + if vdi_refs is None: + raise Exception(_("No VDIs found for VM %s") % vm_ref) + else: + num_vdis = len(vdi_refs) + if num_vdis != 1: + raise Exception(_("Unexpected number of VDIs (%s) found for " + "VM %s") % (num_vdis, vm_ref)) + vdi_ref = vdi_refs[0] vdi_rec = session.get_xenapi().VDI.get_record(vdi_ref) vdi_uuid = vdi_rec["uuid"] @@ -171,20 +176,21 @@ class VMHelper(): original_parent_uuid = get_vhd_parent_uuid(session, vdi_ref) task = session.call_xenapi('Async.VM.snapshot', vm_ref, label) - template_vm_ref = session.wait_for_task(task) - logging.debug('Created snapshot %s from VM %s.', template_vm_ref, + template_vm_ref = session.wait_for_task(instance_id, task) + logging.debug(_('Created snapshot %s from VM %s.'), template_vm_ref, vm_ref) sr_ref = vdi_rec["SR"] parent_uuid = wait_for_vhd_coalesce( - session, sr_ref, vdi_ref, original_parent_uuid) + session, instance_id, sr_ref, vdi_ref, original_parent_uuid) #TODO(sirp): we need to assert only one parent, not parents two deep return template_vm_ref, [vdi_uuid, parent_uuid] @classmethod - def upload_image(cls, session, vdi_uuids, image_name): - logging.debug("Asking xapi to upload %s as '%s'", vdi_uuids, image_name) + def upload_image(cls, session, instance_id, vdi_uuids, image_name): + logging.debug(_("Asking xapi to upload %s as '%s'"), + vdi_uuids, image_name) params = {'vdi_uuids': vdi_uuids, 'image_name': image_name, @@ -193,11 +199,11 @@ class VMHelper(): kwargs = {'params': pickle.dumps(params)} task = session.async_call_plugin('glance', 'put_vdis', kwargs) - session.wait_for_task(task) + session.wait_for_task(instance_id, task) @classmethod - def fetch_image(cls, session, image, user, project, use_sr): + def fetch_image(cls, session, instance_id, image, user, project, use_sr): """use_sr: True to put the image as a VDI in an SR, False to place it on dom0's filesystem. The former is for VM disks, the latter for its kernel and ramdisk (if external kernels are being used). @@ -214,7 +220,7 @@ class VMHelper(): if use_sr: args['add_partition'] = 'true' task = session.async_call_plugin('objectstore', fn, args) - uuid = session.wait_for_task(task) + uuid = session.wait_for_task(instance_id, task) return uuid @classmethod @@ -317,7 +323,7 @@ def get_vhd_parent(session, vdi_rec): parent_ref = session.get_xenapi().VDI.get_by_uuid(parent_uuid) parent_rec = session.get_xenapi().VDI.get_record(parent_ref) #NOTE(sirp): changed log -> logging - logging.debug("VHD %s has parent %s", vdi_rec['uuid'], parent_ref) + logging.debug(_("VHD %s has parent %s"), vdi_rec['uuid'], parent_ref) return parent_ref, parent_rec else: return None @@ -333,25 +339,27 @@ def get_vhd_parent_uuid(session, vdi_ref): return None -def scan_sr(session, sr_ref): - logging.debug("Re-scanning SR %s", sr_ref) +def scan_sr(session, instance_id, sr_ref): + logging.debug(_("Re-scanning SR %s"), sr_ref) task = session.call_xenapi('Async.SR.scan', sr_ref) - session.wait_for_task(task) + session.wait_for_task(instance_id, task) -def wait_for_vhd_coalesce(session, sr_ref, vdi_ref, original_parent_uuid): +def wait_for_vhd_coalesce(session, instance_id, sr_ref, vdi_ref, + original_parent_uuid): """ TODO Explain why coalescing has to occur here """ #NOTE(sirp): for some reason re-scan wasn't occuring automatically on # XS5.6 #TODO(sirp): we need to timeout this req after a while def _poll_vhds(): - scan_sr(session, sr_ref) + scan_sr(session, instance_id, sr_ref) parent_uuid = get_vhd_parent_uuid(session, vdi_ref) if original_parent_uuid and (parent_uuid != original_parent_uuid): logging.debug( - "Parent %s doesn't match original parent %s, " - "waiting for coalesce...", parent_uuid, original_parent_uuid) + _("Parent %s doesn't match original parent %s, " + "waiting for coalesce..."), + parent_uuid, original_parent_uuid) else: done.send(parent_uuid) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index d011f4489..787f959a5 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -67,11 +67,14 @@ class VMOps(object): user = AuthManager().get_user(instance.user_id) project = AuthManager().get_project(instance.project_id) vdi_uuid = VMHelper.fetch_image( - self._session, instance.image_id, user, project, True) + self._session, instance.id, instance.image_id, user, project, + True) kernel = VMHelper.fetch_image( - self._session, instance.kernel_id, user, project, False) + self._session, instance.id, instance.kernel_id, user, project, + False) ramdisk = VMHelper.fetch_image( - self._session, instance.ramdisk_id, user, project, False) + self._session, instance.id, instance.ramdisk_id, user, project, + False) vdi_ref = self._session.call_xenapi('VDI.get_by_uuid', vdi_uuid) vm_ref = VMHelper.create_vm( self._session, instance, kernel, ramdisk) @@ -90,24 +93,25 @@ class VMOps(object): #TODO(sirp): Add quiesce and VSS locking support when Windows support # is added - logging.debug("Starting snapshot for VM %s", instance) + logging.debug(_("Starting snapshot for VM %s"), instance) vm_ref = VMHelper.lookup(self._session, instance.name) label = "%s-snapshot" % instance.name try: template_vm_ref, template_vdi_uuids = VMHelper.create_snapshot( - self._session, vm_ref, label) + self._session, instance.id, vm_ref, label) except XenAPI.Failure, exc: - logging.error("Unable to Snapshot %s: %s", vm_ref, exc) + logging.error(_("Unable to Snapshot %s: %s"), vm_ref, exc) return try: # call plugin to ship snapshot off to glance - VMHelper.upload_image(self._session, template_vdi_uuids, name) + VMHelper.upload_image( + self._session, instance.id, template_vdi_uuids, name) finally: - self._destroy(template_vm_ref, shutdown=False) + self._destroy(instance, template_vm_ref, shutdown=False) - logging.debug("Finished snapshot and upload for VM %s", instance) + logging.debug(_("Finished snapshot and upload for VM %s"), instance) def reboot(self, instance): """Reboot VM instance""" @@ -121,9 +125,9 @@ class VMOps(object): def destroy(self, instance): """Destroy VM instance""" vm = VMHelper.lookup(self._session, instance.name) - return self._destroy(vm, shutdown=True) + return self._destroy(instance, vm, shutdown=True) - def _destroy(self, vm, shutdown=True): + def _destroy(self, instance, vm, shutdown=True): """ Destroy VM instance """ if vm is None: # Don't complain, just return. This lets us clean up instances diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index e6bc80d6e..58ad6ca6a 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -124,8 +124,6 @@ class XenAPIConnection(object): def snapshot(self, instance, name): """ Create snapshot from a running VM instance """ - #TODO(sirp): Add quiesce and VSS locking support when Windows support - # is added self._vmops.snapshot(instance, name) def reboot(self, instance): -- cgit From f31395c30c835201372802e9cdf9293dcbabdb5c Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Wed, 22 Dec 2010 14:54:02 -0600 Subject: Adding more comments regarding XS snapshots --- Authors | 1 + nova/tests/compute_unittest.py | 8 ++++++++ nova/virt/fake.py | 16 ++++++++++++++++ nova/virt/libvirt_conn.py | 2 +- nova/virt/xenapi/vm_utils.py | 20 +++++++++++++++++--- nova/virt/xenapi/vmops.py | 22 +++++++++++++++++++++- nova/virt/xenapi_conn.py | 1 - 7 files changed, 64 insertions(+), 6 deletions(-) diff --git a/Authors b/Authors index 0b048becb..d353b58ff 100644 --- a/Authors +++ b/Authors @@ -24,6 +24,7 @@ Michael Gundlach Monty Taylor Paul Voccio Rick Clark +Rick Harris Ryan Lucio Sandy Walsh Soren Hansen diff --git a/nova/tests/compute_unittest.py b/nova/tests/compute_unittest.py index 187ca31de..025291a92 100644 --- a/nova/tests/compute_unittest.py +++ b/nova/tests/compute_unittest.py @@ -142,6 +142,14 @@ class ComputeTestCase(test.TestCase): self.compute.reboot_instance(self.context, instance_id) self.compute.terminate_instance(self.context, instance_id) + def test_snapshot(self): + """Ensure instance can be snapshotted""" + instance_id = self._create_instance() + name = "myfakesnapshot" + self.compute.run_instance(self.context, instance_id) + self.compute.snapshot_instance(self.context, instance_id, name) + self.compute.terminate_instance(self.context, instance_id) + def test_console_output(self): """Make sure we can get console output from instance""" instance_id = self._create_instance() diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 73e273edd..c6db66aa6 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -106,6 +106,22 @@ class FakeConnection(object): self.instances[instance.name] = fake_instance fake_instance._state = power_state.RUNNING + + def snapshot(self, instance, name): + """ + Snapshots the specified instance. + + The given parameter is an instance of nova.compute.service.Instance, + and so the instance is being specified as instance.name. + + The second parameter is the name of the snapshot. + + The work will be done asynchronously. This function returns a + Deferred that allows the caller to detect when it is complete. + """ + pass + + def reboot(self, instance): """ Reboot the specified instance. diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index c8d0aa306..ad1a35643 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -237,7 +237,7 @@ class LibvirtConnection(object): virt_dom.detachDevice(xml) @exception.wrap_exception - def snapshot(self, instance): + def snapshot(self, instance, name): """ Create snapshot from a running VM instance """ raise NotImplementedError( _("Instance snapshotting is not supported for libvirt" diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 5b1c36418..b7e20121c 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -154,6 +154,9 @@ class VMHelper(): @classmethod def create_snapshot(cls, session, instance_id, vm_ref, label): + """ Creates Snapshot (Template) VM, Snapshot VBD, Snapshot VDI, + Snapshot VHD + """ logging.debug(_("Snapshotting VM %s with label '%s'..."), vm_ref, label) #TODO(sirp): Add quiesce and VSS locking support when Windows support @@ -189,6 +192,9 @@ class VMHelper(): @classmethod def upload_image(cls, session, instance_id, vdi_uuids, image_name): + """ Requests that the Glance plugin bundle the specified VDIs and + push them into Glance using the specified human-friendly name. + """ logging.debug(_("Asking xapi to upload %s as '%s'"), vdi_uuids, image_name) @@ -347,9 +353,17 @@ def scan_sr(session, instance_id, sr_ref): def wait_for_vhd_coalesce(session, instance_id, sr_ref, vdi_ref, original_parent_uuid): - """ TODO Explain why coalescing has to occur here """ - #NOTE(sirp): for some reason re-scan wasn't occuring automatically on - # XS5.6 + """ Spin until the parent VHD is coalesced into its parent VHD + + Before coalesce: + * original_parent_vhd + * parent_vhd + snapshot + + Atter coalesce: + * parent_vhd + snapshot + """ #TODO(sirp): we need to timeout this req after a while def _poll_vhds(): diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 787f959a5..dd6243dd5 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -88,7 +88,27 @@ class VMOps(object): vm_ref) def snapshot(self, instance, name): - """ Create snapshot from a running VM instance """ + """ Create snapshot from a running VM instance + + :param instance: instance to be snapshotted + :param name: name/label to be given to the snapshot + + Steps involved in a XenServer snapshot: + + 1. XAPI-Snapshot: Snapshotting the instance using XenAPI. This + creates: Snapshot (Template) VM, Snapshot VBD, Snapshot VDI, + Snapshot VHD + + 2. Wait-for-coalesce: The Snapshot VDI and Instance VDI both point to + a 'base-copy' VDI. The base_copy is immutable and may be chained + with other base_copies. If chained, the base_copies + coalesce together, so, we must wait for this coalescing to occur to + get a stable representation of the data on disk. + + 3. Push-to-glance: Once coalesced, we call a plugin on the XenServer + that will bundle the VHDs together and then push the bundle into + Glance. + """ #TODO(sirp): Add quiesce and VSS locking support when Windows support # is added diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 58ad6ca6a..816b92a0f 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -121,7 +121,6 @@ class XenAPIConnection(object): """Create VM instance""" self._vmops.spawn(instance) - def snapshot(self, instance, name): """ Create snapshot from a running VM instance """ self._vmops.snapshot(instance, name) -- cgit From 749af384c0b7ca36bdd8c511f02b819a65e5dae0 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Wed, 22 Dec 2010 17:09:46 -0600 Subject: Modified InstanceDiagnostics and truncate action --- nova/api/openstack/__init__.py | 1 + nova/api/openstack/servers.py | 4 ++++ nova/db/sqlalchemy/models.py | 10 ++-------- nova/virt/xenapi_conn.py | 2 +- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index de95ee548..d8cd86116 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -176,6 +176,7 @@ class APIRouter(wsgi.Router): logging.debug("Including admin operations in API.") server_members['pause'] = 'POST' server_members['unpause'] = 'POST' + server_members["diagnostics"] = "GET" mapper.resource("server", "servers", controller=servers.Controller(), collection={'detail': 'GET'}, diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 5c3322f7c..65e371a90 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -195,3 +195,7 @@ class Controller(wsgi.Controller): logging.error("Compute.api::unpause %s", readable) return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() + + def diagnostics(self, req, id): + """Permit Admins to retrieve server diagnostics.""" + return {} diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index eac6a304e..600943005 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -22,7 +22,7 @@ SQLAlchemy models for nova data. import datetime from sqlalchemy.orm import relationship, backref, object_mapper -from sqlalchemy import Column, Integer, Float, String, schema +from sqlalchemy import Column, Integer, String, schema from sqlalchemy import ForeignKey, DateTime, Boolean, Text from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.declarative import declarative_base @@ -232,13 +232,7 @@ class InstanceDiagnostics(BASE, NovaBase): id = Column(Integer, primary_key=True) instance_id = Column(Integer, ForeignKey('instances.id')) - memory_available = Column(Float) - memory_free = Column(Float) - cpu_load = Column(Float) - disk_read = Column(Float) - disk_write = Column(Float) - net_tx = Column(Float) - net_rx = Column(Float) + diagnostics = Column(Text) class InstanceActions(BASE, NovaBase): diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 146e2f153..7870a21ac 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -204,7 +204,7 @@ class XenAPISession(object): status = self._session.xenapi.task.get_status(task) action = dict( instance_id=int(instance_id), - action=name, + action=name[0:255], # Ensure action is never > 255 error=None) if status == "pending": return -- cgit From 358961f3cf259487a2ff9bbb225defdc7cd9e7a7 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 23 Dec 2010 11:22:51 -0600 Subject: Fixed trunk and PEP8 cleanup --- nova/virt/libvirt_conn.py | 16 +++++++++------- nova/virt/xenapi_conn.py | 4 ++++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 651b2af93..809a484dc 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -512,9 +512,11 @@ class LibvirtConnection(object): if FLAGS.allow_project_net_traffic: net, mask = _get_net_and_mask(network['cidr']) - extra_params = ("\n" - "\n" - ) % (net, mask) + extra_params = ( + "\n" + "\n") % ( + net, + mask) else: extra_params = "\n" @@ -800,8 +802,8 @@ class NWFilterFirewall(object): the base filter are all in place. """ - nwfilter_xml = ("\n" - ) % instance['name'] + nwfilter_xml = ("\n") % ( + instance['name']) if instance['image_id'] == FLAGS.vpn_image_id: nwfilter_xml += " \n" @@ -814,8 +816,8 @@ class NWFilterFirewall(object): for security_group in instance.security_groups: self.ensure_security_group_filter(security_group['id']) - nwfilter_xml += (" \n" - ) % security_group['id'] + nwfilter_xml += (" \n") % ( + security_group['id']) nwfilter_xml += "" self._define_filter(nwfilter_xml) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 7870a21ac..af16fcdf3 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -110,6 +110,10 @@ class XenAPIConnection(object): self._vmops = VMOps(session) self._volumeops = VolumeOps(session) + def init_host(self): + """Initialize anything that is necessary for the driver to function""" + return + def list_instances(self): """List VM instances""" return self._vmops.list_instances() -- cgit From 55a80811a5982cb9af5b80e7ac3e925334a1b22d Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 23 Dec 2010 12:23:28 -0600 Subject: Working diagnostics API; removed diagnostics DB model - not needed --- nova/api/openstack/servers.py | 3 ++- nova/compute/api.py | 9 +++++++++ nova/compute/manager.py | 11 +++++++++++ nova/db/sqlalchemy/models.py | 11 +---------- nova/virt/xenapi/vmops.py | 6 +++--- nova/virt/xenapi_conn.py | 4 ++-- 6 files changed, 28 insertions(+), 16 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 65e371a90..f758b252a 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -198,4 +198,5 @@ class Controller(wsgi.Controller): def diagnostics(self, req, id): """Permit Admins to retrieve server diagnostics.""" - return {} + ctxt = req.environ["nova.context"] + return self.compute_api.diagnostics(ctxt, id) diff --git a/nova/compute/api.py b/nova/compute/api.py index 4953fe559..1c24c6634 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -283,6 +283,15 @@ class ComputeAPI(base.Base): {"method": "unpause_instance", "args": {"instance_id": instance['id']}}) + def diagnostics(self, context, instance_id): + """Retrieve diagnostics for the given instance.""" + instance = self.db.instance_get_by_internal_id(context, instance_id) + host = instance["host"] + return rpc.call(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "get_diagnostics", + "args": {"instance_id": instance["id"]}}) + def rescue(self, context, instance_id): """Rescue the given instance.""" instance = self.db.instance_get_by_internal_id(context, instance_id) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index ff8202cca..3a5677bc5 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -296,6 +296,17 @@ class ComputeManager(manager.Manager): instance_id, result)) + @exception.wrap_exception + def get_diagnostics(self, context, instance_id): + """Retrieve diagnostics for an instance on this server.""" + context = context.elevated() + instance_ref = self.db.instance_get(context, instance_id) + + if instance_ref["state"] == power_state.RUNNING: + logging.debug(_("instance %s: retrieving diagnostics"), + instance_ref["internal_id"]) + return self.driver.get_diagnostics(instance_ref) + @exception.wrap_exception def get_console_output(self, context, instance_id): """Send the console output for an instance.""" diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 163d2470b..d93dd64c5 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -236,15 +236,6 @@ class Instance(BASE, NovaBase): # 'shutdown', 'shutoff', 'crashed']) -class InstanceDiagnostics(BASE, NovaBase): - """Represents a guest VM's diagnostics""" - __tablename__ = "instance_diagnostics" - id = Column(Integer, primary_key=True) - instance_id = Column(Integer, ForeignKey('instances.id')) - - diagnostics = Column(Text) - - class InstanceActions(BASE, NovaBase): """Represents a guest VM's actions and results""" __tablename__ = "instance_actions" @@ -555,7 +546,7 @@ def register_models(): it will never need to be called explicitly elsewhere. """ from sqlalchemy import create_engine - models = (Service, Instance, InstanceDiagnostics, InstanceActions, + models = (Service, Instance, InstanceActions, Volume, ExportDevice, IscsiTarget, FixedIp, FloatingIp, Network, SecurityGroup, SecurityGroupIngressRule, SecurityGroupInstanceAssociation, AuthToken, User, diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 4d897af35..50e20bff4 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -156,11 +156,11 @@ class VMOps(object): rec = self._session.get_xenapi().VM.get_record(vm) return VMHelper.compile_info(rec) - def get_diagnostics(self, instance_id): + def get_diagnostics(self, instance): """Return data about VM diagnostics""" - vm = VMHelper.lookup(self._session, instance_id) + vm = VMHelper.lookup(self._session, instance.name) if vm is None: - raise Exception("instance not present %s" % instance_id) + raise Exception("instance not present %s" % instance.name) rec = self._session.get_xenapi().VM.get_record(vm) return VMHelper.compile_diagnostics(self._session, rec) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index af16fcdf3..5c746a08f 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -142,9 +142,9 @@ class XenAPIConnection(object): """Return data about VM instance""" return self._vmops.get_info(instance_id) - def get_diagnostics(self, instance_id): + def get_diagnostics(self, instance): """Return data about VM diagnostics""" - return self._vmops.get_diagnostics(instance_id) + return self._vmops.get_diagnostics(instance) def get_console_output(self, instance): """Return snapshot of console""" -- cgit From 13e8c8d83b8fc44cff343ea751a98f66857d1865 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 23 Dec 2010 13:14:56 -0600 Subject: Resolved merge conflict --- nova/virt/xenapi_conn.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 4ea8a6737..2bf29b295 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -225,13 +225,8 @@ class XenAPISession(object): name = self._session.xenapi.task.get_name_label(task) status = self._session.xenapi.task.get_status(task) action = dict( -<<<<<<< TREE - instance_id=int(instance_id), + instance_id=int(id), action=name[0:255], # Ensure action is never > 255 -======= - id=int(id), - action=name, ->>>>>>> MERGE-SOURCE error=None) if status == "pending": return -- cgit From bd46ab4a721da856da5743c9f55ab5e50ec9b60f Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 23 Dec 2010 13:36:01 -0600 Subject: PEP8 fix --- nova/virt/xenapi/vmops.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 5039913ba..7571fe2e4 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -186,7 +186,8 @@ class VMOps(object): """Return data about VM diagnostics""" vm = VMHelper.lookup(self._session, instance.name) if vm is None: - raise exception.NotFound(_("Instance not found %s") % instance.name) + raise exception.NotFound(_("Instance not found %s") % + instance.name) rec = self._session.get_xenapi().VM.get_record(vm) return VMHelper.compile_diagnostics(self._session, rec) -- cgit From 748aa8089eabfd15425199c2318079e9bf84578f Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Mon, 27 Dec 2010 14:23:53 -0600 Subject: Fixing bad merge --- nova/virt/xenapi/vmops.py | 48 +++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index a0497b3e2..23531348e 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -96,7 +96,30 @@ class VMOps(object): self._session.call_xenapi('VM.start', vm_ref, False, False) logging.info(_('Spawning VM %s created %s.'), instance.name, vm_ref) - + + # NOTE(armando): Do we really need to do this in virt? + timer = utils.LoopingCall(f=None) + + def _wait_for_boot(): + try: + state = self.get_info(instance['name'])['state'] + db.instance_set_state(context.get_admin_context(), + instance['id'], state) + if state == power_state.RUNNING: + logging.debug(_('Instance %s: booted'), instance['name']) + timer.stop() + except Exception, exc: + logging.warn(exc) + logging.exception(_('instance %s: failed to boot'), + instance['name']) + db.instance_set_state(context.get_admin_context(), + instance['id'], + power_state.SHUTDOWN) + timer.stop() + + timer.f = _wait_for_boot + return timer.start(interval=0.5, now=True) + def snapshot(self, instance, name): """ Create snapshot from a running VM instance @@ -143,29 +166,6 @@ class VMOps(object): logging.debug(_("Finished snapshot and upload for VM %s"), instance) - # NOTE(armando): Do we really need to do this in virt? - timer = utils.LoopingCall(f=None) - - def _wait_for_boot(): - try: - state = self.get_info(instance['name'])['state'] - db.instance_set_state(context.get_admin_context(), - instance['id'], state) - if state == power_state.RUNNING: - logging.debug(_('Instance %s: booted'), instance['name']) - timer.stop() - except Exception, exc: - logging.warn(exc) - logging.exception(_('instance %s: failed to boot'), - instance['name']) - db.instance_set_state(context.get_admin_context(), - instance['id'], - power_state.SHUTDOWN) - timer.stop() - - timer.f = _wait_for_boot - return timer.start(interval=0.5, now=True) - def reboot(self, instance): """Reboot VM instance""" instance_name = instance.name -- cgit From 8d522838ace090a7325d930df08c37f1e9d9803e Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Mon, 27 Dec 2010 15:42:30 -0600 Subject: Fixing snapshots, pep8 fixes --- nova/api/openstack/images.py | 2 +- nova/tests/api/openstack/fakes.py | 1 + nova/virt/fake.py | 2 -- nova/virt/xenapi/vm_utils.py | 58 +++++++++++++++++++++------------------ nova/virt/xenapi/vmops.py | 16 +++++------ nova/virt/xenapi_conn.py | 2 +- 6 files changed, 43 insertions(+), 38 deletions(-) diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 8a6772fa5..3a95e3961 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -71,7 +71,7 @@ class Controller(wsgi.Controller): ctxt = req.environ['nova.context'] env = self._deserialize(req.body, req) data = {'instance_id': env["image"]["serverId"], - 'name': env["image"]["name"] } + 'name': env["image"]["name"]} return dict(image=self._service.create(ctxt, data)) def update(self, req, id): diff --git a/nova/tests/api/openstack/fakes.py b/nova/tests/api/openstack/fakes.py index e1ed6a6a8..961431154 100644 --- a/nova/tests/api/openstack/fakes.py +++ b/nova/tests/api/openstack/fakes.py @@ -115,6 +115,7 @@ def stub_out_compute_api_snapshot(stubs): return 123 stubs.Set(nova.compute.api.ComputeAPI, 'snapshot', snapshot) + def stub_out_glance(stubs, initial_fixtures=[]): class FakeParallaxClient: diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 6d3cef268..0f456aef6 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -112,7 +112,6 @@ class FakeConnection(object): self.instances[instance.name] = fake_instance fake_instance._state = power_state.RUNNING - def snapshot(self, instance, name): """ Snapshots the specified instance. @@ -127,7 +126,6 @@ class FakeConnection(object): """ pass - def reboot(self, instance): """ Reboot the specified instance. diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 2b76afc62..f4195cb1b 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -27,6 +27,7 @@ from xml.dom import minidom from eventlet import event from nova import exception from nova import flags +from nova import utils from nova.auth.manager import AuthManager from nova.compute import instance_types from nova.compute import power_state @@ -205,44 +206,34 @@ class VMHelper(HelperBase): vm_ref, network_ref) return vif_ref - @classmethod def create_snapshot(cls, session, instance_id, vm_ref, label): """ Creates Snapshot (Template) VM, Snapshot VBD, Snapshot VDI, Snapshot VHD """ - logging.debug(_("Snapshotting VM %s with label '%s'..."), vm_ref, label) - + logging.debug(_("Snapshotting VM %s with label '%s'..."), + vm_ref, label) #TODO(sirp): Add quiesce and VSS locking support when Windows support # is added - - #TODO(sirp): Make safe_lookup_vdi for assert? - vdi_refs = VMHelper.lookup_vm_vdis(session, vm_ref) - if vdi_refs is None: - raise Exception(_("No VDIs found for VM %s") % vm_ref) - else: - num_vdis = len(vdi_refs) - if num_vdis != 1: - raise Exception(_("Unexpected number of VDIs (%s) found for " - "VM %s") % (num_vdis, vm_ref)) - - vdi_ref = vdi_refs[0] - vdi_rec = session.get_xenapi().VDI.get_record(vdi_ref) - vdi_uuid = vdi_rec["uuid"] - - original_parent_uuid = get_vhd_parent_uuid(session, vdi_ref) + vm_vdi_ref, vm_vdi_rec = get_vdi_for_vm_safely(session, vm_ref) + vm_vdi_uuid = vm_vdi_rec["uuid"] + original_parent_uuid = get_vhd_parent_uuid(session, vm_vdi_ref) task = session.call_xenapi('Async.VM.snapshot', vm_ref, label) template_vm_ref = session.wait_for_task(instance_id, task) + template_vdi_ref, template_vdi_rec = get_vdi_for_vm_safely( + session, template_vm_ref) + + template_vdi_uuid = template_vdi_rec["uuid"] logging.debug(_('Created snapshot %s from VM %s.'), template_vm_ref, vm_ref) - sr_ref = vdi_rec["SR"] + sr_ref = vm_vdi_rec["SR"] parent_uuid = wait_for_vhd_coalesce( - session, instance_id, sr_ref, vdi_ref, original_parent_uuid) + session, instance_id, sr_ref, vm_vdi_ref, original_parent_uuid) #TODO(sirp): we need to assert only one parent, not parents two deep - return template_vm_ref, [vdi_uuid, parent_uuid] + return template_vm_ref, [template_vdi_uuid, parent_uuid] @classmethod def upload_image(cls, session, instance_id, vdi_uuids, image_name): @@ -252,7 +243,7 @@ class VMHelper(HelperBase): logging.debug(_("Asking xapi to upload %s as '%s'"), vdi_uuids, image_name) - params = {'vdi_uuids': vdi_uuids, + params = {'vdi_uuids': vdi_uuids, 'image_name': image_name, 'glance_host': FLAGS.glance_host, 'glance_port': FLAGS.glance_port} @@ -261,7 +252,6 @@ class VMHelper(HelperBase): task = session.async_call_plugin('glance', 'put_vdis', kwargs) session.wait_for_task(instance_id, task) - @classmethod def fetch_image(cls, session, instance_id, image, user, project, type): """ @@ -418,7 +408,7 @@ def scan_sr(session, instance_id, sr_ref): def wait_for_vhd_coalesce(session, instance_id, sr_ref, vdi_ref, original_parent_uuid): """ Spin until the parent VHD is coalesced into its parent VHD - + Before coalesce: * original_parent_vhd * parent_vhd @@ -440,7 +430,7 @@ def wait_for_vhd_coalesce(session, instance_id, sr_ref, vdi_ref, parent_uuid, original_parent_uuid) else: done.send(parent_uuid) - + done = event.Event() loop = utils.LoopingCall(_poll_vhds) loop.start(FLAGS.xenapi_vhd_coalesce_poll_interval, now=True) @@ -448,3 +438,19 @@ def wait_for_vhd_coalesce(session, instance_id, sr_ref, vdi_ref, loop.stop() return parent_uuid + +def get_vdi_for_vm_safely(session, vm_ref): + """Returns (vdi_ref, vdi_uuid)""" + #TODO(sirp): Make safe_lookup_vdi for assert? + vdi_refs = VMHelper.lookup_vm_vdis(session, vm_ref) + if vdi_refs is None: + raise Exception(_("No VDIs found for VM %s") % vm_ref) + else: + num_vdis = len(vdi_refs) + if num_vdis != 1: + raise Exception(_("Unexpected number of VDIs (%s) found for " + "VM %s") % (num_vdis, vm_ref)) + + vdi_ref = vdi_refs[0] + vdi_rec = session.get_xenapi().VDI.get_record(vdi_ref) + return vdi_ref, vdi_rec diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 23531348e..2cc36ad54 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -122,7 +122,7 @@ class VMOps(object): def snapshot(self, instance, name): """ Create snapshot from a running VM instance - + :param instance: instance to be snapshotted :param name: name/label to be given to the snapshot @@ -131,7 +131,7 @@ class VMOps(object): 1. XAPI-Snapshot: Snapshotting the instance using XenAPI. This creates: Snapshot (Template) VM, Snapshot VBD, Snapshot VDI, Snapshot VHD - + 2. Wait-for-coalesce: The Snapshot VDI and Instance VDI both point to a 'base-copy' VDI. The base_copy is immutable and may be chained with other base_copies. If chained, the base_copies @@ -153,14 +153,14 @@ class VMOps(object): try: template_vm_ref, template_vdi_uuids = VMHelper.create_snapshot( self._session, instance.id, vm_ref, label) - except XenAPI.Failure, exc: + except self.XenAPI.Failure, exc: logging.error(_("Unable to Snapshot %s: %s"), vm_ref, exc) return - + try: # call plugin to ship snapshot off to glance VMHelper.upload_image( - self._session, instance.id, template_vdi_uuids, name) + self._session, instance.id, template_vdi_uuids, name) finally: self._destroy(instance, template_vm_ref, shutdown=False) @@ -193,7 +193,7 @@ class VMOps(object): try: task = self._session.call_xenapi('Async.VM.hard_shutdown', vm) self._session.wait_for_task(instance.id, task) - except XenAPI.Failure, exc: + except self.XenAPI.Failure, exc: logging.warn(exc) # Disk clean-up @@ -202,13 +202,13 @@ class VMOps(object): try: task = self._session.call_xenapi('Async.VDI.destroy', vdi) self._session.wait_for_task(instance.id, task) - except XenAPI.Failure, exc: + except self.XenAPI.Failure, exc: logging.warn(exc) # VM Destroy try: task = self._session.call_xenapi('Async.VM.destroy', vm) self._session.wait_for_task(instance.id, task) - except XenAPI.Failure, exc: + except self.XenAPI.Failure, exc: logging.warn(exc) def _wait_with_callback(self, instance_id, task, callback): diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 1a3dc9f16..abd2a02ee 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -233,7 +233,7 @@ class XenAPISession(object): name = self._session.xenapi.task.get_name_label(task) status = self._session.xenapi.task.get_status(task) action = dict( - id=int(id), + instance_id=int(id), action=name, error=None) if status == "pending": -- cgit From 7e469690f04b06f2371650fdf419ba028388c5e9 Mon Sep 17 00:00:00 2001 From: termie Date: Tue, 28 Dec 2010 01:40:24 -0800 Subject: Output of run_tests.sh to be closer to trial --- run_tests.py | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ run_tests.sh | 10 +++++---- 2 files changed, 74 insertions(+), 4 deletions(-) create mode 100644 run_tests.py diff --git a/run_tests.py b/run_tests.py new file mode 100644 index 000000000..56a8bffe7 --- /dev/null +++ b/run_tests.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2010 United States Government as represented by the +# Administrator of the National Aeronautics and Space Administration. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + + +import os +import unittest +import sys + +from nose import config +from nose import result +from nose import core + + +class NovaTestResult(result.TextTestResult): + def __init__(self, *args, **kw): + result.TextTestResult.__init__(self, *args, **kw) + self._last_case = None + + def getDescription(self, test): + return str(test) + + def startTest(self, test): + unittest.TestResult.startTest(self, test) + current_case = test.test.__class__.__name__ + + if self.showAll: + if current_case != self._last_case: + self.stream.writeln(current_case) + self._last_case = current_case + + self.stream.write( + ' %s' % str(test.test._testMethodName).ljust(60)) + self.stream.flush() + + +class NovaTestRunner(core.TextTestRunner): + def _makeResult(self): + return NovaTestResult(self.stream, + self.descriptions, + self.verbosity, + self.config) + + +if __name__ == '__main__': + c = config.Config(stream=sys.stdout, + env=os.environ, + verbosity=3) + + runner = NovaTestRunner(stream=c.stream, + verbosity=c.verbosity, + config=c) + sys.exit(core.run(config=c, testRunner=runner)) diff --git a/run_tests.sh b/run_tests.sh index 67214996d..2e536a636 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -30,6 +30,7 @@ always_venv=0 never_venv=0 force=0 + for arg in "$@"; do process_option $arg done @@ -37,7 +38,7 @@ done if [ $never_venv -eq 1 ]; then # Just run the test suites in current environment rm -f nova.sqlite - nosetests -v + python run_tests.py $@ 2> run_tests.err.log exit fi @@ -49,7 +50,7 @@ fi if [ -e ${venv} ]; then ${with_venv} rm -f nova.sqlite - ${with_venv} nosetests -v $@ + ${with_venv} python run_tests.py $@ 2> run_tests.err.log else if [ $always_venv -eq 1 ]; then # Automatically install the virtualenv @@ -62,10 +63,11 @@ else python tools/install_venv.py else rm -f nova.sqlite - nosetests -v + #nosetests -v + python run_tests.py 2> run_tests.err.log exit fi fi ${with_venv} rm -f nova.sqlite - ${with_venv} nosetests -v $@ + ${with_venv} python run_tests.py $@ 2> run_tests.err.log fi -- cgit From 0fb37c5a08db4b2631ff687cb0fc6af43ba20190 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 28 Dec 2010 11:49:07 -0600 Subject: Added InstanceAction DB functions --- nova/db/api.py | 5 +++++ nova/db/sqlalchemy/api.py | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/nova/db/api.py b/nova/db/api.py index fde3f0852..127f15478 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -383,6 +383,11 @@ def instance_action_create(context, values): return IMPL.instance_action_create(context, values) +def instance_get_actions(context, instance_id): + """Get instance actions by instance id.""" + return IMPL.instance_get_actions(context, instance_id) + + ################### diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 7e945e4cb..d2127989c 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -856,6 +856,15 @@ def instance_action_create(context, values): return action_ref +@require_context +def instance_get_actions(context, instance_id): + """Return the actions associated to the given instance id""" + session = get_session() + return session.query(models.InstanceActions).\ + filter_by(instance_id=instance_id).\ + all() + + ################### -- cgit From 7ddd833bc61252061b3dfd2449765a93f750bffa Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 28 Dec 2010 12:19:25 -0600 Subject: Added get_diagnostics placeholders to libvirt and fake --- nova/virt/fake.py | 3 +++ nova/virt/libvirt_conn.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 706888b0d..880772b22 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -202,6 +202,9 @@ class FakeConnection(object): 'num_cpu': 2, 'cpu_time': 0} + def get_diagnostics(self, instance_name): + pass + def list_disks(self, instance_name): """ Return the IDs of all the virtual disks attached to the specified diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 65cf65098..06ace7457 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -567,6 +567,9 @@ class LibvirtConnection(object): 'num_cpu': num_cpu, 'cpu_time': cpu_time} + def get_diagnostics(self, instance_name): + raise exception.APIError("diagnostics not supported for libvirt") + def get_disks(self, instance_name): """ Note that this function takes an instance name, not an Instance, so -- cgit From 96384b689953e381f2210d4a78f1b5239a78e507 Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Tue, 28 Dec 2010 12:53:32 -0600 Subject: Add some basic snapshot tests --- nova/tests/test_xenapi.py | 106 +++++++++++++++++++++++++++++-------------- nova/tests/xenapi/stubs.py | 66 +++++++++++++++++++++++++++ nova/virt/xenapi/fake.py | 64 ++++++++++++++++++++++++-- nova/virt/xenapi/vm_utils.py | 15 +++--- 4 files changed, 206 insertions(+), 45 deletions(-) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index ed2e4ffde..f0d84e9aa 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -29,9 +29,9 @@ from nova.auth import manager from nova.compute import instance_types from nova.compute import power_state from nova.virt import xenapi_conn -from nova.virt.xenapi import fake +from nova.virt.xenapi import fake as xenapi_fake from nova.virt.xenapi import volume_utils -from nova.tests.db import fakes +from nova.tests.db import fakes as db_fakes from nova.tests.xenapi import stubs FLAGS = flags.FLAGS @@ -47,9 +47,9 @@ class XenAPIVolumeTestCase(test.TestCase): FLAGS.target_host = '127.0.0.1' FLAGS.xenapi_connection_url = 'test_url' FLAGS.xenapi_connection_password = 'test_pass' - fakes.stub_out_db_instance_api(self.stubs) + db_fakes.stub_out_db_instance_api(self.stubs) stubs.stub_out_get_target(self.stubs) - fake.reset() + xenapi_fake.reset() self.values = {'name': 1, 'id': 1, 'project_id': 'fake', 'user_id': 'fake', @@ -83,7 +83,7 @@ class XenAPIVolumeTestCase(test.TestCase): label = 'SR-%s' % vol['ec2_id'] description = 'Test-SR' sr_ref = helper.create_iscsi_storage(session, info, label, description) - srs = fake.get_all('SR') + srs = xenapi_fake.get_all('SR') self.assertEqual(sr_ref, srs[0]) db.volume_destroy(context.get_admin_context(), vol['id']) @@ -107,17 +107,17 @@ class XenAPIVolumeTestCase(test.TestCase): conn = xenapi_conn.get_connection(False) volume = self._create_volume() instance = db.instance_create(self.values) - fake.create_vm(instance.name, 'Running') + xenapi_fake.create_vm(instance.name, 'Running') result = conn.attach_volume(instance.name, volume['ec2_id'], '/dev/sdc') def check(): # check that the VM has a VBD attached to it # Get XenAPI reference for the VM - vms = fake.get_all('VM') + vms = xenapi_fake.get_all('VM') # Get XenAPI record for VBD - vbds = fake.get_all('VBD') - vbd = fake.get_record('VBD', vbds[0]) + vbds = xenapi_fake.get_all('VBD') + vbd = xenapi_fake.get_record('VBD', vbds[0]) vm_ref = vbd['VM'] self.assertEqual(vm_ref, vms[0]) @@ -130,7 +130,7 @@ class XenAPIVolumeTestCase(test.TestCase): conn = xenapi_conn.get_connection(False) volume = self._create_volume() instance = db.instance_create(self.values) - fake.create_vm(instance.name, 'Running') + xenapi_fake.create_vm(instance.name, 'Running') self.assertRaises(Exception, conn.attach_volume, instance.name, @@ -156,41 +156,66 @@ class XenAPIVMTestCase(test.TestCase): self.stubs = stubout.StubOutForTesting() FLAGS.xenapi_connection_url = 'test_url' FLAGS.xenapi_connection_password = 'test_pass' - fake.reset() - fakes.stub_out_db_instance_api(self.stubs) - fake.create_network('fake', FLAGS.flat_network_bridge) + xenapi_fake.reset() + db_fakes.stub_out_db_instance_api(self.stubs) + xenapi_fake.create_network('fake', FLAGS.flat_network_bridge) + stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) + self.conn = xenapi_conn.get_connection(False) def test_list_instances_0(self): - stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) - conn = xenapi_conn.get_connection(False) - instances = conn.list_instances() + instances = self.conn.list_instances() self.assertEquals(instances, []) + def test_instance_snapshot(self): + stubs.stubout_instance_snapshot(self.stubs) + instance = self._create_instance() + + name = "MySnapshot" + template_vm_ref = self.conn.snapshot(instance, name) + + def ensure_vm_was_torn_down(): + vm_labels = [] + for vm_ref in xenapi_fake.get_all('VM'): + vm_rec = xenapi_fake.get_record('VM', vm_ref) + if not vm_rec["is_control_domain"]: + vm_labels.append(vm_rec["name_label"]) + + self.assertEquals(vm_labels, [1]) + + def ensure_vbd_was_torn_down(): + vbd_labels = [] + for vbd_ref in xenapi_fake.get_all('VBD'): + vbd_rec = xenapi_fake.get_record('VBD', vbd_ref) + vbd_labels.append(vbd_rec["vm_name_label"]) + + self.assertEquals(vbd_labels, [1]) + + def ensure_vdi_was_torn_down(): + for vdi_ref in xenapi_fake.get_all('VDI'): + vdi_rec = xenapi_fake.get_record('VDI', vdi_ref) + name_label = vdi_rec["name_label"] + self.assert_(not name_label.endswith('snapshot')) + + def check(): + ensure_vm_was_torn_down() + ensure_vbd_was_torn_down() + ensure_vdi_was_torn_down() + + check() + def test_spawn(self): - stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) - values = {'name': 1, 'id': 1, - 'project_id': self.project.id, - 'user_id': self.user.id, - 'image_id': 1, - 'kernel_id': 2, - 'ramdisk_id': 3, - 'instance_type': 'm1.large', - 'mac_address': 'aa:bb:cc:dd:ee:ff', - } - conn = xenapi_conn.get_connection(False) - instance = db.instance_create(values) - conn.spawn(instance) + instance = self._create_instance() def check(): - instances = conn.list_instances() + instances = self.conn.list_instances() self.assertEquals(instances, [1]) # Get Nova record for VM - vm_info = conn.get_info(1) + vm_info = self.conn.get_info(1) # Get XenAPI record for VM - vms = fake.get_all('VM') - vm = fake.get_record('VM', vms[0]) + vms = xenapi_fake.get_all('VM') + vm = xenapi_fake.get_record('VM', vms[0]) # Check that m1.large above turned into the right thing. instance_type = instance_types.INSTANCE_TYPES['m1.large'] @@ -218,3 +243,18 @@ class XenAPIVMTestCase(test.TestCase): self.manager.delete_project(self.project) self.manager.delete_user(self.user) self.stubs.UnsetAll() + + def _create_instance(self): + """Creates and spawns a test instance""" + values = {'name': 1, 'id': 1, + 'project_id': self.project.id, + 'user_id': self.user.id, + 'image_id': 1, + 'kernel_id': 2, + 'ramdisk_id': 3, + 'instance_type': 'm1.large', + 'mac_address': 'aa:bb:cc:dd:ee:ff', + } + instance = db.instance_create(values) + self.conn.spawn(instance) + return instance diff --git a/nova/tests/xenapi/stubs.py b/nova/tests/xenapi/stubs.py index a7e592fee..55f751f11 100644 --- a/nova/tests/xenapi/stubs.py +++ b/nova/tests/xenapi/stubs.py @@ -19,6 +19,54 @@ from nova.virt import xenapi_conn from nova.virt.xenapi import fake from nova.virt.xenapi import volume_utils +from nova.virt.xenapi import vm_utils + + +def stubout_instance_snapshot(stubs): + @classmethod + def fake_fetch_image(cls, session, instance_id, image, user, project, + type): + # Stubout wait_for_task + def fake_wait_for_task(self, id, task): + class FakeEvent: + + def send(self, value): + self.rv = value + + def wait(self): + return self.rv + + done = FakeEvent() + self._poll_task(id, task, done) + rv = done.wait() + return rv + + stubs.Set(xenapi_conn.XenAPISession, 'wait_for_task', + fake_wait_for_task) + + from nova.virt.xenapi.fake import create_vdi + name_label = "instance-%s" % instance_id + #TODO: create fake SR record + sr_ref = "fakesr" + vdi_ref = create_vdi(name_label=name_label, read_only=False, + sr_ref=sr_ref, sharable=False) + vdi_rec = session.get_xenapi().VDI.get_record(vdi_ref) + vdi_uuid = vdi_rec['uuid'] + return vdi_uuid + + stubs.Set(vm_utils.VMHelper, 'fetch_image', fake_fetch_image) + + def fake_parse_xmlrpc_value(val): + return val + + stubs.Set(xenapi_conn, '_parse_xmlrpc_value', fake_parse_xmlrpc_value) + + def fake_wait_for_vhd_coalesce(session, instance_id, sr_ref, vdi_ref, + original_parent_uuid): + #TODO(sirp): Should we actually fake out the data here + return "fakeparent" + + stubs.Set(vm_utils, 'wait_for_vhd_coalesce', fake_wait_for_vhd_coalesce) def stubout_session(stubs, cls): @@ -63,6 +111,24 @@ class FakeSessionForVMTests(fake.SessionBase): vm['is_a_template'] = False vm['is_control_domain'] = False + def VM_snapshot(self, session_ref, vm_ref, label): + status = "Running" + template_vm_ref = fake.create_vm(label, status, is_a_template=True, + is_control_domain=False) + + sr_ref = "fakesr" + template_vdi_ref = fake.create_vdi(label, read_only=True, + sr_ref=sr_ref, sharable=False) + + template_vbd_ref = fake.create_vbd(template_vm_ref, template_vdi_ref) + return template_vm_ref + + def VDI_destroy(self, session_ref, vdi_ref): + fake.destroy_vdi(vdi_ref) + + def VM_destroy(self, session_ref, vm_ref): + fake.destroy_vm(vm_ref) + class FakeSessionForVolumeTests(fake.SessionBase): """ Stubs out a XenAPISession for Volume tests """ diff --git a/nova/virt/xenapi/fake.py b/nova/virt/xenapi/fake.py index 1eaf31c25..aa4026f97 100644 --- a/nova/virt/xenapi/fake.py +++ b/nova/virt/xenapi/fake.py @@ -55,6 +55,8 @@ import datetime import logging import uuid +from pprint import pformat + from nova import exception @@ -64,6 +66,10 @@ _CLASSES = ['host', 'network', 'session', 'SR', 'VBD',\ _db_content = {} +def log_db_contents(msg=None): + logging.debug(_("%s: _db_content => %s"), msg or "", pformat(_db_content)) + + def reset(): for c in _CLASSES: _db_content[c] = {} @@ -93,6 +99,24 @@ def create_vm(name_label, status, }) +def destroy_vm(vm_ref): + vm_rec = _db_content['VM'][vm_ref] + + vbd_refs = vm_rec['VBDs'] + for vbd_ref in vbd_refs: + destroy_vbd(vbd_ref) + + del _db_content['VM'][vm_ref] + + +def destroy_vbd(vbd_ref): + del _db_content['VBD'][vbd_ref] + + +def destroy_vdi(vdi_ref): + del _db_content['VDI'][vdi_ref] + + def create_vdi(name_label, read_only, sr_ref, sharable): return _create_object('VDI', { 'name_label': name_label, @@ -109,6 +133,23 @@ def create_vdi(name_label, read_only, sr_ref, sharable): }) +def create_vbd(vm_ref, vdi_ref): + vbd_rec = {'VM': vm_ref, 'VDI': vdi_ref} + vbd_ref = _create_object('VBD', vbd_rec) + after_VBD_create(vbd_ref, vbd_rec) + return vbd_ref + + +def after_VBD_create(vbd_ref, vbd_rec): + """Create backref from VM to VBD when VBD is created""" + vm_ref = vbd_rec['VM'] + vm_rec = _db_content['VM'][vm_ref] + vm_rec['VBDs'] = [vbd_ref] + + vm_name_label = _db_content['VM'][vm_ref]['name_label'] + vbd_rec['vm_name_label'] = vm_name_label + + def create_pbd(config, sr_ref, attached): return _create_object('PBD', { 'device-config': config, @@ -277,11 +318,12 @@ class SessionBase(object): self._check_arg_count(params, 2) return get_record(cls, params[1]) - if (func == 'get_by_name_label' or - func == 'get_by_uuid'): + if func in ('get_by_name_label', 'get_by_uuid'): self._check_arg_count(params, 2) + return_singleton = (func == 'get_by_uuid') return self._get_by_field( - _db_content[cls], func[len('get_by_'):], params[1]) + _db_content[cls], func[len('get_by_'):], params[1], + return_singleton=return_singleton) if len(params) == 2: field = func[len('get_'):] @@ -324,6 +366,13 @@ class SessionBase(object): (cls, _) = name.split('.') ref = is_sr_create and \ _create_sr(cls, params) or _create_object(cls, params[1]) + + # Call hook to provide any fixups needed (ex. creating backrefs) + try: + globals()["after_%s_create" % cls](ref, params[1]) + except KeyError: + pass + obj = get_record(cls, ref) # Add RO fields @@ -359,11 +408,18 @@ class SessionBase(object): raise Failure(['MESSAGE_PARAMETER_COUNT_MISMATCH', expected, actual]) - def _get_by_field(self, recs, k, v): + def _get_by_field(self, recs, k, v, return_singleton): result = [] for ref, rec in recs.iteritems(): if rec.get(k) == v: result.append(ref) + + if return_singleton: + try: + return result[0] + except IndexError: + return None + return result diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index f4195cb1b..68da807fc 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -211,24 +211,25 @@ class VMHelper(HelperBase): """ Creates Snapshot (Template) VM, Snapshot VBD, Snapshot VDI, Snapshot VHD """ - logging.debug(_("Snapshotting VM %s with label '%s'..."), - vm_ref, label) #TODO(sirp): Add quiesce and VSS locking support when Windows support # is added + logging.debug(_("Snapshotting VM %s with label '%s'..."), + vm_ref, label) + vm_vdi_ref, vm_vdi_rec = get_vdi_for_vm_safely(session, vm_ref) vm_vdi_uuid = vm_vdi_rec["uuid"] + sr_ref = vm_vdi_rec["SR"] + original_parent_uuid = get_vhd_parent_uuid(session, vm_vdi_ref) task = session.call_xenapi('Async.VM.snapshot', vm_ref, label) template_vm_ref = session.wait_for_task(instance_id, task) - template_vdi_ref, template_vdi_rec = get_vdi_for_vm_safely( - session, template_vm_ref) - + template_vdi_rec = get_vdi_for_vm_safely(session, template_vm_ref)[1] template_vdi_uuid = template_vdi_rec["uuid"] + logging.debug(_('Created snapshot %s from VM %s.'), template_vm_ref, vm_ref) - sr_ref = vm_vdi_rec["SR"] parent_uuid = wait_for_vhd_coalesce( session, instance_id, sr_ref, vm_vdi_ref, original_parent_uuid) @@ -440,8 +441,6 @@ def wait_for_vhd_coalesce(session, instance_id, sr_ref, vdi_ref, def get_vdi_for_vm_safely(session, vm_ref): - """Returns (vdi_ref, vdi_uuid)""" - #TODO(sirp): Make safe_lookup_vdi for assert? vdi_refs = VMHelper.lookup_vm_vdis(session, vm_ref) if vdi_refs is None: raise Exception(_("No VDIs found for VM %s") % vm_ref) -- cgit From 7811a77753943ee87f3c3b10f37d22e61c5119d0 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 28 Dec 2010 14:32:45 -0600 Subject: Make action log available through Admin API --- nova/api/openstack/__init__.py | 1 + nova/api/openstack/servers.py | 5 +++++ nova/compute/api.py | 9 +++++++++ nova/compute/manager.py | 10 ++++++++++ nova/db/sqlalchemy/api.py | 9 ++++++--- nova/virt/fake.py | 3 +++ nova/virt/libvirt_conn.py | 5 ++++- nova/virt/xenapi/vmops.py | 8 ++++++++ nova/virt/xenapi_conn.py | 4 ++++ 9 files changed, 50 insertions(+), 4 deletions(-) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index be342b670..412e16315 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -94,6 +94,7 @@ class APIRouter(wsgi.Router): server_members['pause'] = 'POST' server_members['unpause'] = 'POST' server_members["diagnostics"] = "GET" + server_members["actions"] = "GET" server_members['suspend'] = 'POST' server_members['resume'] = 'POST' diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index c00aa26ce..0091a9bd2 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -224,3 +224,8 @@ class Controller(wsgi.Controller): """Permit Admins to retrieve server diagnostics.""" ctxt = req.environ["nova.context"] return self.compute_api.diagnostics(ctxt, id) + + def actions(self, req, id): + """Permit Admins to retrieve server actions.""" + ctxt = req.environ["nova.context"] + return self.compute_api.actions(ctxt, id) diff --git a/nova/compute/api.py b/nova/compute/api.py index 68919442a..3d9728c77 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -293,6 +293,15 @@ class ComputeAPI(base.Base): {"method": "get_diagnostics", "args": {"instance_id": instance["id"]}}) + def actions(self, context, instance_id): + """Retrieve actions for the given instance.""" + instance = self.db.instance_get_by_internal_id(context, instance_id) + host = instance["host"] + return rpc.call(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "get_actions", + "args": {"instance_id": instance["id"]}}) + def suspend(self, context, instance_id): """suspend the instance with instance_id""" instance = self.db.instance_get_by_internal_id(context, instance_id) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 7dea04132..924b72004 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -307,6 +307,16 @@ class ComputeManager(manager.Manager): instance_ref["internal_id"]) return self.driver.get_diagnostics(instance_ref) + @exception.wrap_exception + def get_actions(self, context, instance_id): + """Retrieve actions for an instance on this server.""" + context = context.elevated() + instance_ref = self.db.instance_get(context, instance_id) + + logging.debug(_("instance %s: retrieving actions"), + instance_ref["internal_id"]) + return self.driver.get_actions(instance_ref) + def suspend_instance(self, context, instance_id): """suspend the instance with instance_id""" context = context.elevated() diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index d2127989c..5fd928a79 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -856,13 +856,16 @@ def instance_action_create(context, values): return action_ref -@require_context +@require_admin_context def instance_get_actions(context, instance_id): """Return the actions associated to the given instance id""" session = get_session() - return session.query(models.InstanceActions).\ + actions = {} + for action in session.query(models.InstanceActions).\ filter_by(instance_id=instance_id).\ - all() + all(): + actions[action.action] = action.error + return actions ################### diff --git a/nova/virt/fake.py b/nova/virt/fake.py index 880772b22..e9d1e4bd7 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -205,6 +205,9 @@ class FakeConnection(object): def get_diagnostics(self, instance_name): pass + def get_actions(self, instance_name): + pass + def list_disks(self, instance_name): """ Return the IDs of all the virtual disks attached to the specified diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 06ace7457..9c8aa1fae 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -568,7 +568,10 @@ class LibvirtConnection(object): 'cpu_time': cpu_time} def get_diagnostics(self, instance_name): - raise exception.APIError("diagnostics not supported for libvirt") + raise exception.APIError("diagnostics are not supported for libvirt") + + def get_actions(self, instance_name): + raise exception.APIError("actions are not supported for libvirt") def get_disks(self, instance_name): """ diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 332e603db..93f7f7695 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -226,6 +226,14 @@ class VMOps(object): rec = self._session.get_xenapi().VM.get_record(vm) return VMHelper.compile_diagnostics(self._session, rec) + def get_actions(self, instance): + """Return VM action history""" + vm = VMHelper.lookup(self._session, instance.name) + if vm is None: + raise exception.NotFound(_("Instance not found %s") % + instance.name) + return db.instance_get_actions(context.get_admin_context(), instance.id) + def get_console_output(self, instance): """Return snapshot of console""" # TODO: implement this to fix pylint! diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index a7ec22012..92274daf9 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -163,6 +163,10 @@ class XenAPIConnection(object): """Return data about VM diagnostics""" return self._vmops.get_diagnostics(instance) + def get_actions(self, instance): + """Return VM action history""" + return self._vmops.get_actions(instance) + def get_console_output(self, instance): """Return snapshot of console""" return self._vmops.get_console_output(instance) -- cgit From cc906e48c13012da552cc346146d9586afc6092e Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Wed, 29 Dec 2010 10:35:29 -0600 Subject: Updating Authors --- .mailmap | 1 + Authors | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.mailmap b/.mailmap index 9ab7db743..9ac8f2efd 100644 --- a/.mailmap +++ b/.mailmap @@ -27,3 +27,4 @@ + diff --git a/Authors b/Authors index 299114dad..407b407c8 100644 --- a/Authors +++ b/Authors @@ -25,7 +25,7 @@ Michael Gundlach Monty Taylor Paul Voccio Rick Clark -Rick Harris +Rick Harris Ryan Lane Ryan Lucio Salvatore Orlando -- cgit From 823c5fc1ff3c37acbfe9b30d7057f53b050b93c6 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Wed, 29 Dec 2010 14:02:57 -0600 Subject: Added tests --- nova/tests/api/openstack/test_servers.py | 16 ++++++++++++++++ nova/tests/test_compute.py | 16 ++++++++++++++-- nova/tests/test_xenapi.py | 30 ++++++++++++++++++++---------- nova/virt/xenapi/vm_utils.py | 4 ++++ 4 files changed, 54 insertions(+), 12 deletions(-) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 5d23db588..321ddceee 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -95,6 +95,10 @@ class ServersTest(unittest.TestCase): fake_compute_api) self.stubs.Set(nova.compute.api.ComputeAPI, 'resume', fake_compute_api) + self.stubs.Set(nova.compute.api.ComputeAPI, "diagnostics", + fake_compute_api) + self.stubs.Set(nova.compute.api.ComputeAPI, "actions", + fake_compute_api) self.allow_admin = FLAGS.allow_admin_api def tearDown(self): @@ -274,6 +278,18 @@ class ServersTest(unittest.TestCase): res = req.get_response(nova.api.API('os')) self.assertEqual(res.status_int, 202) + def test_server_diagnostics(self): + req = webob.Request.blank("/v1.0/servers/1/diagnostics") + req.method = "GET" + res = req.get_response(nova.api.API("os")) + self.assertEqual(res.status_int, 404) + + def test_server_actions(self): + req = webob.Request.blank("/v1.0/servers/1/actions") + req.method = "GET" + res = req.get_response(nova.api.API("os")) + self.assertEqual(res.status_int, 404) + def test_server_reboot(self): body = dict(server=dict( name='server_test', imageId=2, flavorId=2, metadata={}, diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index bcb8a1526..757b1f462 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -102,13 +102,15 @@ class ComputeTestCase(test.TestCase): instances = db.instance_get_all(context.get_admin_context()) logging.info(_("Running instances: %s"), instances) - self.assertEqual(len(instances), 1) + + instance_count = len(instances) + self.assertNotEqual(instance_count, 0) self.compute.terminate_instance(self.context, instance_id) instances = db.instance_get_all(context.get_admin_context()) logging.info(_("After terminating instances: %s"), instances) - self.assertEqual(len(instances), 0) + self.assertEqual(instance_count, len(instances) + 1) def test_run_terminate_timestamps(self): """Make sure timestamps are set for launched and destroyed""" @@ -151,6 +153,16 @@ class ComputeTestCase(test.TestCase): self.compute.reboot_instance(self.context, instance_id) self.compute.terminate_instance(self.context, instance_id) + def test_diagnostics(self): + """Ensure instance diagnostics are available""" + instance_id = self._create_instance() + self.compute.get_diagnostics(self.context, instance_id) + + def test_actions(self): + """Ensure instance actions are available""" + instance_id = self._create_instance() + self.compute.get_actions(self.context, instance_id) + def test_console_output(self): """Make sure we can get console output from instance""" instance_id = self._create_instance() diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index ed2e4ffde..11baead8b 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -159,6 +159,16 @@ class XenAPIVMTestCase(test.TestCase): fake.reset() fakes.stub_out_db_instance_api(self.stubs) fake.create_network('fake', FLAGS.flat_network_bridge) + self.values = { + "name": 1, + "id": 1, + "project_id": self.project.id, + "user_id": self.user.id, + "image_id": 1, + "kernel_id": 2, + "ramdisk_id": 3, + "instance_type": "m1.large", + "mac_address": "aa:bb:cc:dd:ee:ff"} def test_list_instances_0(self): stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) @@ -166,19 +176,19 @@ class XenAPIVMTestCase(test.TestCase): instances = conn.list_instances() self.assertEquals(instances, []) + def test_get_diagnostics(self): + stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) + conn = xenapi_conn.get_connection(False) + + instance = db.instance_create(self.values) + conn.spawn(instance) + + conn.get_diagnostics(instance) + def test_spawn(self): stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) - values = {'name': 1, 'id': 1, - 'project_id': self.project.id, - 'user_id': self.user.id, - 'image_id': 1, - 'kernel_id': 2, - 'ramdisk_id': 3, - 'instance_type': 'm1.large', - 'mac_address': 'aa:bb:cc:dd:ee:ff', - } conn = xenapi_conn.get_connection(False) - instance = db.instance_create(values) + instance = db.instance_create(self.values) conn.spawn(instance) def check(): diff --git a/nova/virt/xenapi/vm_utils.py b/nova/virt/xenapi/vm_utils.py index 47fb6db53..cca7c1476 100644 --- a/nova/virt/xenapi/vm_utils.py +++ b/nova/virt/xenapi/vm_utils.py @@ -299,6 +299,10 @@ class VMHelper(HelperBase): try: host = session.get_xenapi_host() host_ip = session.get_xenapi().host.get_record(host)["address"] + except (cls.XenAPI.Failure, KeyError) as e: + return {"Unable to retrieve diagnostics": e} + + try: diags = {} xml = get_rrd(host_ip, record["uuid"]) if xml: -- cgit From 5b8137b2f50a4ed3eb105e38cefa280927f1c2ea Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Wed, 29 Dec 2010 14:15:04 -0600 Subject: PEP8 cleanup --- nova/db/sqlalchemy/api.py | 2 +- nova/virt/xenapi/vmops.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 5fd928a79..8e68d12a4 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -865,7 +865,7 @@ def instance_get_actions(context, instance_id): filter_by(instance_id=instance_id).\ all(): actions[action.action] = action.error - return actions + return actions ################### diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 93f7f7695..1dfb9005c 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -232,7 +232,9 @@ class VMOps(object): if vm is None: raise exception.NotFound(_("Instance not found %s") % instance.name) - return db.instance_get_actions(context.get_admin_context(), instance.id) + return db.instance_get_actions( + context.get_admin_context(), + instance.id) def get_console_output(self, instance): """Return snapshot of console""" -- cgit From e7be0b485e2d6c7cd95d1f5b7e6a401032f437e6 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Wed, 29 Dec 2010 17:01:34 -0500 Subject: Fix scheduler testcase so it knows all flags and can run in isolation. --- nova/tests/test_scheduler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/tests/test_scheduler.py b/nova/tests/test_scheduler.py index 91517cc5d..78e4a1c77 100644 --- a/nova/tests/test_scheduler.py +++ b/nova/tests/test_scheduler.py @@ -33,6 +33,7 @@ from nova.scheduler import driver FLAGS = flags.FLAGS flags.DECLARE('max_cores', 'nova.scheduler.simple') +flags.DECLARE('stub_network', 'nova.compute.manager') class TestDriver(driver.Scheduler): -- cgit From f5611d9fdeaed5e2c16cf4b31a85db2ba4f5b30d Mon Sep 17 00:00:00 2001 From: Cory Wright Date: Thu, 30 Dec 2010 14:23:52 -0500 Subject: Several documentation corrections --- doc/source/adminguide/multi.node.install.rst | 23 +++++++------- doc/source/devref/addmethod.openstackapi.rst | 8 ++--- doc/source/devref/rabbit.rst | 4 +-- doc/source/nova.concepts.rst | 14 ++++----- doc/source/quickstart.rst | 47 +++++++++------------------- 5 files changed, 39 insertions(+), 57 deletions(-) diff --git a/doc/source/adminguide/multi.node.install.rst b/doc/source/adminguide/multi.node.install.rst index fcb76c5e5..40490e71e 100644 --- a/doc/source/adminguide/multi.node.install.rst +++ b/doc/source/adminguide/multi.node.install.rst @@ -77,21 +77,20 @@ Nova development has consolidated all .conf files to nova.conf as of November 20 #. These need to be defined in the nova.conf configuration file:: - --sql_connection=mysql://root:nova@$CC_ADDR/nova # location of nova sql db - --s3_host=$CC_ADDR # This is where nova is hosting the objectstore service, which - # will contain the VM images and buckets - --rabbit_host=$CC_ADDR # This is where the rabbit AMQP messaging service is hosted - --cc_host=$CC_ADDR # This is where the the nova-api service lives - --verbose # Optional but very helpful during initial setup - --ec2_url=http://$CC_ADDR:8773/services/Cloud - --network_manager=nova.network.manager.FlatManager # simple, no-vlan networking type - - --fixed_range= # ip network to use for VM guests, ex 192.168.2.64/26 - --network_size=<# of addrs> # number of ip addrs to use for VM guests, ex 64 + --sql_connection=mysql://root:nova@$CC_ADDR/nova # location of nova sql db + --s3_host=$CC_ADDR # This is where Nova is hosting the objectstore service, which + # will contain the VM images and buckets + --rabbit_host=$CC_ADDR # This is where the rabbit AMQP messaging service is hosted + --cc_host=$CC_ADDR # This is where the the nova-api service lives + --verbose # Optional but very helpful during initial setup + --ec2_url=http://$CC_ADDR:8773/services/Cloud + --network_manager=nova.network.manager.FlatManager # simple, no-vlan networking type + --fixed_range= # ip network to use for VM guests, ex 192.168.2.64/26 + --network_size=<# of addrs> # number of ip addrs to use for VM guests, ex 64 #. Create a nova group:: - sudo addgroup nova + sudo addgroup nova The Nova config file should have its owner set to root:nova, and mode set to 0640, since they contain your MySQL server's root password. diff --git a/doc/source/devref/addmethod.openstackapi.rst b/doc/source/devref/addmethod.openstackapi.rst index 6484613df..4baa46e20 100644 --- a/doc/source/devref/addmethod.openstackapi.rst +++ b/doc/source/devref/addmethod.openstackapi.rst @@ -24,7 +24,7 @@ Routing To map URLs to controllers+actions, OpenStack uses the Routes package, a clone of Rails routes for Python implementations. See http://routes.groovie.org/ fore more information. -URLs are mapped to "action" methods on "controller" classes in nova/api/openstack/__init__/ApiRouter.__init__ . +URLs are mapped to "action" methods on "controller" classes in `nova/api/openstack/__init__/ApiRouter.__init__` . See http://routes.groovie.org/manual.html for all syntax, but you'll probably just need these two: - mapper.connect() lets you map a single URL to a single action on a controller. @@ -33,9 +33,9 @@ See http://routes.groovie.org/manual.html for all syntax, but you'll probably ju Controllers and actions ----------------------- -Controllers live in nova/api/openstack, and inherit from nova.wsgi.Controller. +Controllers live in `nova/api/openstack`, and inherit from nova.wsgi.Controller. -See nova/api/openstack/servers.py for an example. +See `nova/api/openstack/servers.py` for an example. Action methods take parameters that are sucked out of the URL by mapper.connect() or .resource(). The first two parameters are self and the WebOb request, from which you can get the req.environ, req.body, req.headers, etc. @@ -46,7 +46,7 @@ Actions return a dictionary, and wsgi.Controller serializes that to JSON or XML If you define a new controller, you'll need to define a _serialization_metadata attribute on the class, to tell wsgi.Controller how to convert your dictionary to XML. It needs to know the singular form of any list tag (e.g. list contains tags) and which dictionary keys are to be XML attributes as opposed to subtags (e.g. instead of 4). -See nova/api/openstack/servers.py for an example. +See `nova/api/openstack/servers.py` for an example. Faults ------ diff --git a/doc/source/devref/rabbit.rst b/doc/source/devref/rabbit.rst index 423284a55..ae0bac49d 100644 --- a/doc/source/devref/rabbit.rst +++ b/doc/source/devref/rabbit.rst @@ -71,8 +71,8 @@ RPC Casts The diagram below the message flow during an rp.cast operation: - 1. a Topic Publisher is instantiated to send the message request to the queuing system. - 2. once the message is dispatched by the exchange, it is fetched by the Topic Consumer dictated by the routing key (such as 'topic') and passed to the Worker in charge of the task. + 1. A Topic Publisher is instantiated to send the message request to the queuing system. + 2. Once the message is dispatched by the exchange, it is fetched by the Topic Consumer dictated by the routing key (such as 'topic') and passed to the Worker in charge of the task. .. image:: /images/rabbit/flow2.png :width: 60% diff --git a/doc/source/nova.concepts.rst b/doc/source/nova.concepts.rst index 18368546b..fb3969a43 100644 --- a/doc/source/nova.concepts.rst +++ b/doc/source/nova.concepts.rst @@ -75,7 +75,7 @@ Nova is built on a shared-nothing, messaging-based architecture. All of the majo To achieve the shared-nothing property with multiple copies of the same component, Nova keeps all the cloud system state in a distributed data store. Updates to system state are written into this store, using atomic transactions when required. Requests for system state are read out of this store. In limited cases, the read results are cached within controllers for short periods of time (for example, the current list of system users.) - .. note:: The database schema is available on the `OpenStack Wiki _`. + .. note:: The database schema is available on the `OpenStack Wiki `_. Concept: Storage ---------------- @@ -129,12 +129,12 @@ The simplest networking mode. Each instance receives a fixed ip from the pool. Flat DHCP Mode ~~~~~~~~~~~~~~ -This is similar to the flat mode, in that all instances are attached to the same bridge. In this mode nova does a bit more configuration, it will attempt to bridge into an ethernet device (eth0 by default). It will also run dnsmasq as a dhcpserver listening on this bridge. Instances receive their fixed IPs by doing a dhcpdiscover. +This is similar to the flat mode, in that all instances are attached to the same bridge. In this mode Nova does a bit more configuration, it will attempt to bridge into an ethernet device (eth0 by default). It will also run dnsmasq as a dhcpserver listening on this bridge. Instances receive their fixed IPs by doing a dhcpdiscover. VLAN DHCP Mode ~~~~~~~~~~~~~~ -This is the default networking mode and supports the most features. For multiple machine installation, it requires a switch that supports host-managed vlan tagging. In this mode, nova will create a vlan and bridge for each project. The project gets a range of private ips that are only accessible from inside the vlan. In order for a user to access the instances in their project, a special vpn instance (code named :ref:`cloudpipe `) needs to be created. Nova generates a certificate and key for the user to access the vpn and starts the vpn automatically. More information on cloudpipe can be found :ref:`here `. +This is the default networking mode and supports the most features. For multiple machine installation, it requires a switch that supports host-managed vlan tagging. In this mode, Nova will create a vlan and bridge for each project. The project gets a range of private ips that are only accessible from inside the vlan. In order for a user to access the instances in their project, a special vpn instance (code named :ref:`cloudpipe `) needs to be created. Nova generates a certificate and key for the user to access the vpn and starts the vpn automatically. More information on cloudpipe can be found :ref:`here `. The following diagram illustrates how the communication that occurs between the vlan (the dashed box) and the public internet (represented by the two clouds) @@ -154,16 +154,16 @@ Concept: nova-manage -------------------- The nova-manage command is used to perform many essential functions for -administration and ongoing maintenance of nova, such as user creation, +administration and ongoing maintenance of Nova, such as user creation, vpn management, and much more. -See doc:`nova.manage` in the Administration Guide for more details. +See :doc:`nova.manage` in the Administration Guide for more details. Concept: Flags -------------- -Nova uses python-gflags for a distributed command line system, and the flags can either be set when running a command at the command line or within flag files. When you install Nova packages, each nova service gets its own flag file. For example, nova-network.conf is used for configuring the nova-network service, and so forth. +Nova uses python-gflags for a distributed command line system, and the flags can either be set when running a command at the command line or within flag files. When you install Nova packages, each Nova service gets its own flag file. For example, nova-network.conf is used for configuring the nova-network service, and so forth. Concept: Plugins @@ -181,7 +181,7 @@ Concept: Plugins Concept: IPC/RPC ---------------- -Nova utilizes the RabbitMQ implementation of the AMQP messaging standard for performing communication between the various nova services. This message queuing service is used for both local and remote communication because Nova is designed so that there is no requirement that any of the services exist on the same physical machine. RabbitMQ in particular is very robust and provides the efficiency and reliability that Nova needs. More information about RabbitMQ can be found at http://www.rabbitmq.com/. +Nova utilizes the RabbitMQ implementation of the AMQP messaging standard for performing communication between the various Nova services. This message queuing service is used for both local and remote communication because Nova is designed so that there is no requirement that any of the services exist on the same physical machine. RabbitMQ in particular is very robust and provides the efficiency and reliability that Nova needs. More information about RabbitMQ can be found at http://www.rabbitmq.com/. Concept: Fakes -------------- diff --git a/doc/source/quickstart.rst b/doc/source/quickstart.rst index ae2b64d8a..fa5d96738 100644 --- a/doc/source/quickstart.rst +++ b/doc/source/quickstart.rst @@ -59,38 +59,21 @@ different configurations (though for more complex setups you should see * HOST_IP * Default: address of first interface from the ifconfig command * Values: 127.0.0.1, or any other valid address - -TEST -~~~~ - -**Default**: 0 -**Values**: 1, run tests after checkout and initial setup - -USE_MYSQL -~~~~~~~~~ - -**Default**: 0, use sqlite3 -**Values**: 1, use mysql instead of sqlite3 - -MYSQL_PASS -~~~~~~~~~~ - -Only useful if $USE_MYSQL=1. - -**Default**: nova -**Values**: value of root password for mysql - -USE_LDAP -~~~~~~~~ - -**Default**: 0, use :mod:`nova.auth.dbdriver` -**Values**: 1, use :mod:`nova.auth.ldapdriver` - -LIBVIRT_TYPE -~~~~~~~~~~~~ - -**Default**: qemu -**Values**: uml, kvm +* TEST + * Default: 0 + * Values: 1, run tests after checkout and initial setup +* USE_MYSQL + * Default: 0, use sqlite3 + * Values: 1, use mysql instead of sqlite3 +* MYSQL_PASS (Only useful if $USE_MYSQL=1) + * Default: nova + * Values: value of root password for mysql +* USE_LDAP + * Default: 0, use :mod:`nova.auth.dbdriver` + * Values: 1, use :mod:`nova.auth.ldapdriver` +* LIBVIRT_TYPE + * Default: qemu + * Values: uml, kvm Usage ----- -- cgit From 601b19291a7cf1bcda7bcd4ebf27e4eefe3e28fd Mon Sep 17 00:00:00 2001 From: Rick Harris Date: Thu, 30 Dec 2010 14:09:16 -0600 Subject: Calling compute api directly from OpenStack image create --- nova/api/openstack/images.py | 10 +++++----- nova/image/glance.py | 4 ---- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/nova/api/openstack/images.py b/nova/api/openstack/images.py index 2ee73480f..867ee5a7e 100644 --- a/nova/api/openstack/images.py +++ b/nova/api/openstack/images.py @@ -25,7 +25,7 @@ import nova.image.service from nova.api.openstack import common from nova.api.openstack import faults - +from nova.compute import api as compute_api FLAGS = flags.FLAGS @@ -127,11 +127,11 @@ class Controller(wsgi.Controller): raise faults.Fault(exc.HTTPNotFound()) def create(self, req): - ctxt = req.environ['nova.context'] + context = req.environ['nova.context'] env = self._deserialize(req.body, req) - data = {'instance_id': env["image"]["serverId"], - 'name': env["image"]["name"]} - return dict(image=self._service.create(ctxt, data)) + instance_id = env["image"]["serverId"] + name = env["image"]["name"] + return compute_api.ComputeAPI().snapshot(context, instance_id, name) def update(self, req, id): # Users may not modify public images, and that's all that diff --git a/nova/image/glance.py b/nova/image/glance.py index b61cba10e..cc3192e7c 100644 --- a/nova/image/glance.py +++ b/nova/image/glance.py @@ -203,10 +203,6 @@ class GlanceImageService(nova.image.service.BaseImageService): :raises AlreadyExists if the image already exist. """ - instance_id = data["instance_id"] - name = data["name"] - compute_api.ComputeAPI().snapshot(context, instance_id, name) - # FIXME(sirp): This needs to be reworked for new-style glance return self.parallax.add_image_metadata(data) def update(self, context, image_id, data): -- cgit From 42f6a993bcc4d0bc8823e4d039b1f59a6d6758a8 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 30 Dec 2010 15:13:32 -0600 Subject: Implemented review feedback --- nova/compute/manager.py | 6 +++--- nova/virt/fake.py | 3 --- nova/virt/libvirt_conn.py | 3 --- nova/virt/xenapi/vmops.py | 10 ---------- nova/virt/xenapi_conn.py | 4 ---- 5 files changed, 3 insertions(+), 23 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 924b72004..d2ade8f52 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -299,7 +299,6 @@ class ComputeManager(manager.Manager): @exception.wrap_exception def get_diagnostics(self, context, instance_id): """Retrieve diagnostics for an instance on this server.""" - context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) if instance_ref["state"] == power_state.RUNNING: @@ -310,12 +309,13 @@ class ComputeManager(manager.Manager): @exception.wrap_exception def get_actions(self, context, instance_id): """Retrieve actions for an instance on this server.""" - context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) logging.debug(_("instance %s: retrieving actions"), instance_ref["internal_id"]) - return self.driver.get_actions(instance_ref) + return self.db.instance_get_actions( + context, + instance_id) def suspend_instance(self, context, instance_id): """suspend the instance with instance_id""" diff --git a/nova/virt/fake.py b/nova/virt/fake.py index e9d1e4bd7..880772b22 100644 --- a/nova/virt/fake.py +++ b/nova/virt/fake.py @@ -205,9 +205,6 @@ class FakeConnection(object): def get_diagnostics(self, instance_name): pass - def get_actions(self, instance_name): - pass - def list_disks(self, instance_name): """ Return the IDs of all the virtual disks attached to the specified diff --git a/nova/virt/libvirt_conn.py b/nova/virt/libvirt_conn.py index 8407938c6..cf45ac13b 100644 --- a/nova/virt/libvirt_conn.py +++ b/nova/virt/libvirt_conn.py @@ -583,9 +583,6 @@ class LibvirtConnection(object): def get_diagnostics(self, instance_name): raise exception.APIError("diagnostics are not supported for libvirt") - def get_actions(self, instance_name): - raise exception.APIError("actions are not supported for libvirt") - def get_disks(self, instance_name): """ Note that this function takes an instance name, not an Instance, so diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 1dfb9005c..332e603db 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -226,16 +226,6 @@ class VMOps(object): rec = self._session.get_xenapi().VM.get_record(vm) return VMHelper.compile_diagnostics(self._session, rec) - def get_actions(self, instance): - """Return VM action history""" - vm = VMHelper.lookup(self._session, instance.name) - if vm is None: - raise exception.NotFound(_("Instance not found %s") % - instance.name) - return db.instance_get_actions( - context.get_admin_context(), - instance.id) - def get_console_output(self, instance): """Return snapshot of console""" # TODO: implement this to fix pylint! diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index 92274daf9..a7ec22012 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -163,10 +163,6 @@ class XenAPIConnection(object): """Return data about VM diagnostics""" return self._vmops.get_diagnostics(instance) - def get_actions(self, instance): - """Return VM action history""" - return self._vmops.get_actions(instance) - def get_console_output(self, instance): """Return snapshot of console""" return self._vmops.get_console_output(instance) -- cgit From 384da35986ff174e63272b41d3ba383378abf1ab Mon Sep 17 00:00:00 2001 From: termie Date: Thu, 30 Dec 2010 13:31:56 -0800 Subject: change exit code --- run_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/run_tests.py b/run_tests.py index 56a8bffe7..5b8617f63 100644 --- a/run_tests.py +++ b/run_tests.py @@ -65,4 +65,4 @@ if __name__ == '__main__': runner = NovaTestRunner(stream=c.stream, verbosity=c.verbosity, config=c) - sys.exit(core.run(config=c, testRunner=runner)) + sys.exit(not core.run(config=c, testRunner=runner)) -- cgit From 00abbb4401c87cca9f1540d7be1c0119fc7aee44 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 30 Dec 2010 16:06:48 -0600 Subject: Review feedback --- nova/compute/api.py | 6 +----- nova/compute/manager.py | 11 ----------- nova/tests/test_compute.py | 11 ++--------- nova/tests/test_xenapi.py | 31 ++++++------------------------- 4 files changed, 9 insertions(+), 50 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index 4fcefb384..3f700349a 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -305,11 +305,7 @@ class ComputeAPI(base.Base): def actions(self, context, instance_id): """Retrieve actions for the given instance.""" instance = self.db.instance_get_by_internal_id(context, instance_id) - host = instance["host"] - return rpc.call(context, - self.db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "get_actions", - "args": {"instance_id": instance["id"]}}) + return self.db.instance_get_actions(context, instance["id"]) def suspend(self, context, instance_id): """suspend the instance with instance_id""" diff --git a/nova/compute/manager.py b/nova/compute/manager.py index c4768d266..c9aff75ac 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -327,17 +327,6 @@ class ComputeManager(manager.Manager): instance_ref["internal_id"]) return self.driver.get_diagnostics(instance_ref) - @exception.wrap_exception - def get_actions(self, context, instance_id): - """Retrieve actions for an instance on this server.""" - instance_ref = self.db.instance_get(context, instance_id) - - logging.debug(_("instance %s: retrieving actions"), - instance_ref["internal_id"]) - return self.db.instance_get_actions( - context, - instance_id) - def suspend_instance(self, context, instance_id): """suspend the instance with instance_id""" context = context.elevated() diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index e3679b6b6..2664adc0d 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -102,15 +102,13 @@ class ComputeTestCase(test.TestCase): instances = db.instance_get_all(context.get_admin_context()) logging.info(_("Running instances: %s"), instances) - - instance_count = len(instances) - self.assertNotEqual(instance_count, 0) + self.assertEqual(len(instances), 1) self.compute.terminate_instance(self.context, instance_id) instances = db.instance_get_all(context.get_admin_context()) logging.info(_("After terminating instances: %s"), instances) - self.assertEqual(instance_count, len(instances) + 1) + self.assertEqual(len(instances), 0) def test_run_terminate_timestamps(self): """Make sure timestamps are set for launched and destroyed""" @@ -158,11 +156,6 @@ class ComputeTestCase(test.TestCase): instance_id = self._create_instance() self.compute.get_diagnostics(self.context, instance_id) - def test_actions(self): - """Ensure instance actions are available""" - instance_id = self._create_instance() - self.compute.get_actions(self.context, instance_id) - def test_snapshot(self): """Ensure instance can be snapshotted""" instance_id = self._create_instance() diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 3528d7bfe..a09750672 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -156,10 +156,11 @@ class XenAPIVMTestCase(test.TestCase): self.stubs = stubout.StubOutForTesting() FLAGS.xenapi_connection_url = 'test_url' FLAGS.xenapi_connection_password = 'test_pass' - - fake.reset() - fakes.stub_out_db_instance_api(self.stubs) - fake.create_network('fake', FLAGS.flat_network_bridge) + xenapi_fake.reset() + db_fakes.stub_out_db_instance_api(self.stubs) + xenapi_fake.create_network('fake', FLAGS.flat_network_bridge) + stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) + self.conn = xenapi_conn.get_connection(False) self.values = { "name": 1, "id": 1, @@ -171,12 +172,6 @@ class XenAPIVMTestCase(test.TestCase): "instance_type": "m1.large", "mac_address": "aa:bb:cc:dd:ee:ff"} - xenapi_fake.reset() - db_fakes.stub_out_db_instance_api(self.stubs) - xenapi_fake.create_network('fake', FLAGS.flat_network_bridge) - stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) - self.conn = xenapi_conn.get_connection(False) - def test_list_instances_0(self): instances = self.conn.list_instances() self.assertEquals(instances, []) @@ -228,11 +223,6 @@ class XenAPIVMTestCase(test.TestCase): check() def test_spawn(self): - stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) - conn = xenapi_conn.get_connection(False) - instance = db.instance_create(self.values) - conn.spawn(instance) - instance = self._create_instance() def check(): @@ -275,15 +265,6 @@ class XenAPIVMTestCase(test.TestCase): def _create_instance(self): """Creates and spawns a test instance""" - values = {'name': 1, 'id': 1, - 'project_id': self.project.id, - 'user_id': self.user.id, - 'image_id': 1, - 'kernel_id': 2, - 'ramdisk_id': 3, - 'instance_type': 'm1.large', - 'mac_address': 'aa:bb:cc:dd:ee:ff', - } - instance = db.instance_create(values) + instance = db.instance_create(self.values) self.conn.spawn(instance) return instance -- cgit From 5c34edb3fc215a519f1a00e8c3ff6223ee050041 Mon Sep 17 00:00:00 2001 From: Cerberus Date: Thu, 30 Dec 2010 16:13:42 -0600 Subject: Fail --- nova/db/sqlalchemy/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 693db8d23..baa743765 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -452,7 +452,7 @@ class AuthToken(BASE, NovaBase): """ __tablename__ = 'auth_tokens' token_hash = Column(String(255), primary_key=True) - user_id = Column(Integer) + user_id = Column(String(255)) server_manageent_url = Column(String(255)) storage_url = Column(String(255)) cdn_management_url = Column(String(255)) -- cgit From d89b3a4b5c1f6bfe1f59da6c33cb469da589e866 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 30 Dec 2010 16:27:31 -0600 Subject: Make compute.api methods verbs --- nova/api/openstack/servers.py | 4 ++-- nova/compute/api.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index e1c2d36ea..c5cbe21ef 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -222,9 +222,9 @@ class Controller(wsgi.Controller): def diagnostics(self, req, id): """Permit Admins to retrieve server diagnostics.""" ctxt = req.environ["nova.context"] - return self.compute_api.diagnostics(ctxt, id) + return self.compute_api.get_diagnostics(ctxt, id) def actions(self, req, id): """Permit Admins to retrieve server actions.""" ctxt = req.environ["nova.context"] - return self.compute_api.actions(ctxt, id) + return self.compute_api.get_actions(ctxt, id) diff --git a/nova/compute/api.py b/nova/compute/api.py index 3f700349a..28189e4a9 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -293,7 +293,7 @@ class ComputeAPI(base.Base): {"method": "unpause_instance", "args": {"instance_id": instance['id']}}) - def diagnostics(self, context, instance_id): + def get_diagnostics(self, context, instance_id): """Retrieve diagnostics for the given instance.""" instance = self.db.instance_get_by_internal_id(context, instance_id) host = instance["host"] @@ -302,7 +302,7 @@ class ComputeAPI(base.Base): {"method": "get_diagnostics", "args": {"instance_id": instance["id"]}}) - def actions(self, context, instance_id): + def get_actions(self, context, instance_id): """Retrieve actions for the given instance.""" instance = self.db.instance_get_by_internal_id(context, instance_id) return self.db.instance_get_actions(context, instance["id"]) -- cgit From d1129fa4b14d9edba8d6c4c3cb2d13e7c66c1391 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 30 Dec 2010 17:02:16 -0600 Subject: Oopsies --- nova/tests/api/openstack/test_servers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 321ddceee..70ff714e6 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -95,9 +95,9 @@ class ServersTest(unittest.TestCase): fake_compute_api) self.stubs.Set(nova.compute.api.ComputeAPI, 'resume', fake_compute_api) - self.stubs.Set(nova.compute.api.ComputeAPI, "diagnostics", + self.stubs.Set(nova.compute.api.ComputeAPI, "get_diagnostics", fake_compute_api) - self.stubs.Set(nova.compute.api.ComputeAPI, "actions", + self.stubs.Set(nova.compute.api.ComputeAPI, "get_actions", fake_compute_api) self.allow_admin = FLAGS.allow_admin_api -- cgit From e453e2761daee6e96da9575a860e694a065c68c0 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 30 Dec 2010 18:12:10 -0600 Subject: Removed problematic test --- nova/tests/test_compute.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 2664adc0d..1fb9143f1 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -151,11 +151,6 @@ class ComputeTestCase(test.TestCase): self.compute.reboot_instance(self.context, instance_id) self.compute.terminate_instance(self.context, instance_id) - def test_diagnostics(self): - """Ensure instance diagnostics are available""" - instance_id = self._create_instance() - self.compute.get_diagnostics(self.context, instance_id) - def test_snapshot(self): """Ensure instance can be snapshotted""" instance_id = self._create_instance() -- cgit From f55991c01421350434893200db8f01ca3911d957 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 30 Dec 2010 18:56:12 -0600 Subject: Improved test --- nova/tests/test_xenapi.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index a09750672..539d132bd 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -180,9 +180,7 @@ class XenAPIVMTestCase(test.TestCase): stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) conn = xenapi_conn.get_connection(False) - instance = db.instance_create(self.values) - conn.spawn(instance) - + instance = self._create_instance() conn.get_diagnostics(instance) def test_instance_snapshot(self): -- cgit From ffaf32b9ac9a3e71ac25f68eb593bbf16a11946a Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 30 Dec 2010 19:07:20 -0600 Subject: Cleanup --- nova/tests/test_xenapi.py | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index 539d132bd..c95a53af3 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -161,27 +161,14 @@ class XenAPIVMTestCase(test.TestCase): xenapi_fake.create_network('fake', FLAGS.flat_network_bridge) stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) self.conn = xenapi_conn.get_connection(False) - self.values = { - "name": 1, - "id": 1, - "project_id": self.project.id, - "user_id": self.user.id, - "image_id": 1, - "kernel_id": 2, - "ramdisk_id": 3, - "instance_type": "m1.large", - "mac_address": "aa:bb:cc:dd:ee:ff"} def test_list_instances_0(self): instances = self.conn.list_instances() self.assertEquals(instances, []) def test_get_diagnostics(self): - stubs.stubout_session(self.stubs, stubs.FakeSessionForVMTests) - conn = xenapi_conn.get_connection(False) - instance = self._create_instance() - conn.get_diagnostics(instance) + self.conn.get_diagnostics(instance) def test_instance_snapshot(self): stubs.stubout_instance_snapshot(self.stubs) @@ -263,6 +250,15 @@ class XenAPIVMTestCase(test.TestCase): def _create_instance(self): """Creates and spawns a test instance""" - instance = db.instance_create(self.values) + values = {'name': 1, 'id': 1, + 'project_id': self.project.id, + 'user_id': self.user.id, + 'image_id': 1, + 'kernel_id': 2, + 'ramdisk_id': 3, + 'instance_type': 'm1.large', + 'mac_address': 'aa:bb:cc:dd:ee:ff' + } + instance = db.instance_create(values) self.conn.spawn(instance) return instance -- cgit From 3d6c193fb7a44f62e3cb1dc18670fdca10f028cf Mon Sep 17 00:00:00 2001 From: Eric Day Date: Thu, 30 Dec 2010 20:03:21 -0800 Subject: Ignore CA/crl.pem --- .bzrignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.bzrignore b/.bzrignore index 82db46fa2..d81a7d829 100644 --- a/.bzrignore +++ b/.bzrignore @@ -6,6 +6,7 @@ keys networks nova.sqlite CA/cacert.pem +CA/crl.pem CA/index.txt* CA/openssl.cnf CA/serial* -- cgit