From 8e28dd8331f99223696ab6656cd555be12c28e85 Mon Sep 17 00:00:00 2001 From: Chiradeep Vittal Date: Fri, 8 Oct 2010 17:57:13 -0700 Subject: [PATCH 01/86] Twisted pidfile and other flag parameters simply do not function on Windows. --- nova/twistd.py | 31 ++++++++++++++++--------------- 1 file changed, 16 insertions(+), 15 deletions(-) diff --git a/nova/twistd.py b/nova/twistd.py index 9511c231c71e..29d75328320d 100644 --- a/nova/twistd.py +++ b/nova/twistd.py @@ -224,21 +224,22 @@ def serve(filename): logging.getLogger('amqplib').setLevel(logging.WARN) FLAGS.python = filename FLAGS.no_save = True - if not FLAGS.pidfile: - FLAGS.pidfile = '%s.pid' % name - elif FLAGS.pidfile.endswith('twistd.pid'): - FLAGS.pidfile = FLAGS.pidfile.replace('twistd.pid', '%s.pid' % name) - # NOTE(vish): if we're running nodaemon, redirect the log to stdout - if FLAGS.nodaemon and not FLAGS.logfile: - FLAGS.logfile = "-" - if not FLAGS.logfile: - FLAGS.logfile = '%s.log' % name - elif FLAGS.logfile.endswith('twistd.log'): - FLAGS.logfile = FLAGS.logfile.replace('twistd.log', '%s.log' % name) - if not FLAGS.prefix: - FLAGS.prefix = name - elif FLAGS.prefix.endswith('twisted'): - FLAGS.prefix = FLAGS.prefix.replace('twisted', name) + if sys.platform != 'win32': + if not FLAGS.pidfile: + FLAGS.pidfile = '%s.pid' % name + elif FLAGS.pidfile.endswith('twistd.pid'): + FLAGS.pidfile = FLAGS.pidfile.replace('twistd.pid', '%s.pid' % name) + # NOTE(vish): if we're running nodaemon, redirect the log to stdout + if FLAGS.nodaemon and not FLAGS.logfile: + FLAGS.logfile = "-" + if not FLAGS.logfile: + FLAGS.logfile = '%s.log' % name + elif FLAGS.logfile.endswith('twistd.log'): + FLAGS.logfile = FLAGS.logfile.replace('twistd.log', '%s.log' % name) + if not FLAGS.prefix: + FLAGS.prefix = name + elif FLAGS.prefix.endswith('twisted'): + FLAGS.prefix = FLAGS.prefix.replace('twisted', name) action = 'start' if len(argv) > 1: From b578047ec26ac7d0ad26ccaab8b596ba5373b278 Mon Sep 17 00:00:00 2001 From: Chiradeep Vittal Date: Fri, 8 Oct 2010 17:57:49 -0700 Subject: [PATCH 02/86] hyper-v driver created --- nova/virt/hyperv.py | 387 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 387 insertions(+) create mode 100644 nova/virt/hyperv.py diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py new file mode 100644 index 000000000000..91b86e2658df --- /dev/null +++ b/nova/virt/hyperv.py @@ -0,0 +1,387 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2010 Cloud.com, Inc +# +# 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. + +""" +A connection to Hyper-V . + +""" + +import os +import logging +import wmi +import time + +from twisted.internet import defer + +from nova import flags +from nova.auth.manager import AuthManager +from nova.compute import power_state +from nova.virt import images + + +FLAGS = flags.FLAGS + + +HYPERV_POWER_STATE = { + 3 : power_state.SHUTDOWN, + 2 : power_state.RUNNING, + 32768 : power_state.PAUSED, + 32768: power_state.PAUSED, # TODO + 3 : power_state.CRASHED +} + +REQ_POWER_STATE = { + 'Enabled' : 2, + 'Disabled': 3, + 'Reboot' : 10, + 'Reset' : 11, + 'Paused' : 32768, + 'Suspended': 32769 +} + + +def get_connection(_): + return HyperVConnection() + + +class HyperVConnection(object): + def __init__(self): + self._conn = wmi.WMI(moniker = '//./root/virtualization') + self._cim_conn = wmi.WMI(moniker = '//./root/cimv2') + + def list_instances(self): + vms = [v.ElementName \ + for v in self._conn.Msvm_ComputerSystem(['ElementName'])] + return vms + + @defer.inlineCallbacks + def spawn(self, instance): + vm = yield self._lookup(instance.name) + if vm is not None: + raise Exception('Attempted to create non-unique name %s' % + instance.name) + + user = AuthManager().get_user(instance['user_id']) + project = AuthManager().get_project(instance['project_id']) + vhdfile = os.path.join(FLAGS.instances_path, instance['str_id'])+".vhd" + yield images.fetch(instance['image_id'], vhdfile, user, project) + + try: + yield self._create_vm(instance) + + yield self._create_disk(instance['name'], vhdfile) + yield self._create_nic(instance['name'], instance['mac_address']) + + logging.debug ('Starting VM %s ', instance.name) + yield self._set_vm_state(instance['name'], 'Enabled') + logging.info('Started VM %s ', instance.name) + except Exception as exn: + logging.error('spawn vm failed: %s', exn) + self.destroy(instance) + + def _create_vm(self, instance): + """Create a VM record. """ + vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0] + + vs_gs_data = self._conn.Msvm_VirtualSystemGlobalSettingData.new() + vs_gs_data.ElementName = instance['name'] + (job, ret_val) = vs_man_svc.DefineVirtualSystem( + [], None, vs_gs_data.GetText_(1))[1:] + if (ret_val == 4096 ): #WMI job started + success = self._check_job_status(job) + else: + success = (ret_val == 0) + + if not success: + raise Exception('Failed to create VM %s', instance.name) + + logging.debug('Created VM %s...', instance.name) + vm = self._conn.Msvm_ComputerSystem (ElementName=instance.name)[0] + + vmsettings = vm.associators(wmi_result_class= + 'Msvm_VirtualSystemSettingData') + vmsetting = [s for s in vmsettings + if s.SettingType == 3][0] #avoid snapshots + memsetting = vmsetting.associators(wmi_result_class= + 'Msvm_MemorySettingData')[0] + #No Dynamic Memory + mem = long(str(instance['memory_mb'])) + memsetting.VirtualQuantity = mem + memsetting.Reservation = mem + memsetting.Limit = mem + + (job, ret_val) = vs_man_svc.ModifyVirtualSystemResources( + vm.path_(), [memsetting.GetText_(1)]) + + logging.debug('Set memory for vm %s...', instance.name) + procsetting = vmsetting.associators(wmi_result_class= + 'Msvm_ProcessorSettingData')[0] + vcpus = long(str(instance['vcpus'])) + #vcpus = 1 + procsetting.VirtualQuantity = vcpus + procsetting.Reservation = vcpus + procsetting.Limit = vcpus + + (job, ret_val) = vs_man_svc.ModifyVirtualSystemResources( + vm.path_(), [procsetting.GetText_(1)]) + + logging.debug('Set vcpus for vm %s...', instance.name) + + + def _create_disk(self, vm_name, vhdfile): + """Create a disk and attach it to the vm""" + logging.debug("Creating disk for %s by attaching disk file %s", \ + vm_name, vhdfile) + vms = self._conn.MSVM_ComputerSystem (ElementName=vm_name) + vm = vms[0] + vmsettings = vm.associators( + wmi_result_class='Msvm_VirtualSystemSettingData') + rasds = vmsettings[0].associators( + wmi_result_class='MSVM_ResourceAllocationSettingData') + ctrller = [r for r in rasds + if r.ResourceSubType == 'Microsoft Emulated IDE Controller'\ + and r.Address == "0" ] + diskdflt = self._conn.query( + "SELECT * FROM Msvm_ResourceAllocationSettingData \ + WHERE ResourceSubType LIKE 'Microsoft Synthetic Disk Drive'\ + AND InstanceID LIKE '%Default%'")[0] + diskdrive = self._clone_wmi_obj( + 'Msvm_ResourceAllocationSettingData', diskdflt) + diskdrive.Parent = ctrller[0].path_() + diskdrive.Address = 0 + new_resources = self._add_virt_resource(diskdrive, vm) + + if new_resources is None: + raise Exception('Failed to add diskdrive to VM %s', vm_name) + + diskdrive_path = new_resources[0] + logging.debug("New disk drive path is " + diskdrive_path) + vhddefault = self._conn.query( + "SELECT * FROM Msvm_ResourceAllocationSettingData \ + WHERE ResourceSubType LIKE 'Microsoft Virtual Hard Disk' AND \ + InstanceID LIKE '%Default%' ")[0] + + vhddisk = self._clone_wmi_obj( + 'Msvm_ResourceAllocationSettingData', vhddefault) + vhddisk.Parent = diskdrive_path + vhddisk.Connection = [vhdfile] + + new_resources = self._add_virt_resource(vhddisk, vm) + if new_resources is None: + raise Exception('Failed to add vhd file to VM %s', vm_name) + logging.info("Created disk for %s ", vm_name) + + + def _create_nic(self, vm_name, mac): + """Create a (emulated) nic and attach it to the vm""" + logging.debug("Creating nic for %s ", vm_name) + vms = self._conn.Msvm_ComputerSystem (ElementName=vm_name) + extswitch = self._find_external_network() + vm = vms[0] + switch_svc = self._conn.Msvm_VirtualSwitchManagementService ()[0] + #use Msvm_SyntheticEthernetPortSettingData for Windows VMs or Linux with + #Linux Integration Components installed + emulatednics_data = self._conn.Msvm_EmulatedEthernetPortSettingData() + default_nic_data = [n for n in emulatednics_data + if n.InstanceID.rfind('Default') >0 ] + new_nic_data = self._clone_wmi_obj( + 'Msvm_EmulatedEthernetPortSettingData', + default_nic_data[0]) + + (created_sw, ret_val) = switch_svc.CreateSwitchPort(vm_name, vm_name, + "", extswitch.path_()) + if (ret_val != 0): + logging.debug("Failed to create a new port on the external network") + return + logging.debug("Created switch port %s on switch %s", + vm_name, extswitch.path_()) + new_nic_data.Connection = [created_sw] + new_nic_data.ElementName = vm_name + ' nic' + new_nic_data.Address = ''.join(mac.split(':')) + new_nic_data.StaticMacAddress = 'TRUE' + new_resources = self._add_virt_resource(new_nic_data, vm) + if new_resources is None: + raise Exception('Failed to add nic to VM %s', vm_name) + logging.info("Created nic for %s ", vm_name) + + + def _add_virt_resource(self, res_setting_data, target_vm): + vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0] + (job, new_resources, return_val) = vs_man_svc.\ + AddVirtualSystemResources([res_setting_data.GetText_(1)], + target_vm.path_()) + success = True + if (return_val == 4096 ): #WMI job started + success = self._check_job_status(job) + else: + success = (return_val == 0) + if success: + return new_resources + else: + return None + + #TODO: use the reactor to poll instead of sleep + def _check_job_status(self, jobpath): + inst_id = jobpath.split(':')[1].split('=')[1].strip('\"') + jobs = self._conn.Msvm_ConcreteJob(InstanceID=inst_id) + if (len(jobs) == 0): + return False + job = jobs[0] + while job.JobState == 4: #job started + time.sleep(0.1) + job = self._conn.Msvm_ConcreteJob(InstanceID=inst_id)[0] + + if (job.JobState != 7): #job success + logging.debug("WMI job failed: " + job.ErrorSummaryDescription) + return False + + logging.debug("WMI job succeeded: " + job.Description + ",Elapsed = " \ + + job.ElapsedTime) + + return True + + + + def _find_external_network(self): + bound = self._conn.Msvm_ExternalEthernetPort(IsBound='TRUE') + if (len(bound) == 0): + return None + + return self._conn.Msvm_ExternalEthernetPort(IsBound='TRUE')[0]\ + .associators(wmi_result_class='Msvm_SwitchLANEndpoint')[0]\ + .associators(wmi_result_class='Msvm_SwitchPort')[0]\ + .associators(wmi_result_class='Msvm_VirtualSwitch')[0] + + def _clone_wmi_obj(self, wmi_class, wmi_obj): + cl = self._conn.__getattr__(wmi_class) + newinst = cl.new() + for prop in wmi_obj._properties: + newinst.Properties_.Item(prop).Value =\ + wmi_obj.Properties_.Item(prop).Value + return newinst + + + @defer.inlineCallbacks + def reboot(self, instance): + vm = yield self._lookup(instance.name) + if vm is None: + raise Exception('instance not present %s' % instance.name) + self._set_vm_state(instance.name, 'Reboot') + + + @defer.inlineCallbacks + def destroy(self, instance): + logging.debug("Got request to destroy vm %s", instance.name) + vm = yield self._lookup(instance.name) + if vm is None: + defer.returnValue(None) + vm = self._conn.Msvm_ComputerSystem (ElementName=instance.name)[0] + vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0] + self._set_vm_state(instance.name, 'Disabled') + vmsettings = vm.associators(wmi_result_class= + 'Msvm_VirtualSystemSettingData') + rasds = vmsettings[0].associators(wmi_result_class= + 'MSVM_ResourceAllocationSettingData') + disks = [r for r in rasds \ + if r.ResourceSubType == 'Microsoft Virtual Hard Disk' ] + diskfiles = [] + for disk in disks: + diskfiles.extend([c for c in disk.Connection]) + + (job, ret_val) = vs_man_svc.DestroyVirtualSystem(vm.path_()) + if (ret_val == 4096 ): #WMI job started + success = self._check_job_status(job) + elif (ret_val == 0): + success = True + if not success: + raise Exception('Failed to destroy vm %s' % instance.name) + for disk in diskfiles: + vhdfile = self._cim_conn.CIM_DataFile(Name=disk) + for vf in vhdfile: + vf.Delete() + logging.debug("Deleted disk %s vm %s", vhdfile, instance.name) + + + + def get_info(self, instance_id): + vm = self._lookup(instance_id) + if vm is None: + raise Exception('instance not present %s' % instance_id) + vm = self._conn.Msvm_ComputerSystem(ElementName=instance_id)[0] + vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0] + vmsettings = vm.associators(wmi_result_class= + 'Msvm_VirtualSystemSettingData') + settings_paths = [ v.path_() for v in vmsettings] + summary_info = vs_man_svc.GetSummaryInformation( + [4,100,103,105], settings_paths)[1] + info = summary_info[0] + logging.debug("Got Info for vm %s: state=%s, mem=%s, num_cpu=%s, \ + cpu_time=%s", instance_id, + str(HYPERV_POWER_STATE[info.EnabledState]), + str(info.MemoryUsage), + str(info.NumberOfProcessors), + str(info.UpTime)) + + return {'state': HYPERV_POWER_STATE[info.EnabledState], + 'max_mem': info.MemoryUsage, + 'mem': info.MemoryUsage, + 'num_cpu': info.NumberOfProcessors, + 'cpu_time': info.UpTime} + + + def _lookup(self, i): + vms = self._conn.Msvm_ComputerSystem (ElementName=i) + n = len(vms) + if n == 0: + return None + elif n > 1: + raise Exception('duplicate name found: %s' % i) + else: + return vms[0].ElementName + + def _set_vm_state(self, vm_name, req_state): + vms = self._conn.Msvm_ComputerSystem (ElementName=vm_name) + if len(vms) == 0: + return False + status = vms[0].RequestStateChange(REQ_POWER_STATE[req_state]) + job = status[0] + return_val = status[1] + if (return_val == 4096 ): #WMI job started + success = self._check_job_status(job) + elif (return_val == 0): + success = True + if success: + logging.info("Successfully changed vm state of %s to %s", + vm_name, req_state) + return True + else: + logging.debug("Failed to change vm state of %s to %s", + vm_name, req_state) + return False + + + def attach_volume(self, instance_name, device_path, mountpoint): + vm = self._lookup(instance_name) + if vm is None: + raise Exception('Attempted to attach volume to nonexistent %s vm' % + instance_name) + + def detach_volume(self, instance_name, mountpoint): + vm = self._lookup(instance_name) + if vm is None: + raise Exception('Attempted to detach volume from nonexistent %s ' % + instance_name) + From 85c890e91f493f254801edd5e5aed115d8d9c4a6 Mon Sep 17 00:00:00 2001 From: Chiradeep Vittal Date: Fri, 8 Oct 2010 17:58:01 -0700 Subject: [PATCH 03/86] Register the Hyper-V module into the list of virt modules --- nova/virt/connection.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nova/virt/connection.py b/nova/virt/connection.py index 34e37adf7587..0f736ce39e7f 100644 --- a/nova/virt/connection.py +++ b/nova/virt/connection.py @@ -26,6 +26,7 @@ from nova import flags from nova.virt import fake from nova.virt import libvirt_conn from nova.virt import xenapi +from nova.virt import hyperv FLAGS = flags.FLAGS @@ -49,6 +50,8 @@ def get_connection(read_only=False): conn = libvirt_conn.get_connection(read_only) elif t == 'xenapi': conn = xenapi.get_connection(read_only) + elif t == 'hyperv': + conn = hyperv.get_connection(read_only) else: raise Exception('Unknown connection type "%s"' % t) From 6669b46ca91f462c96b033c6e04618c06fecb31f Mon Sep 17 00:00:00 2001 From: Chiradeep Vittal Date: Fri, 8 Oct 2010 17:58:53 -0700 Subject: [PATCH 04/86] curl not available on Windows for s3 download. also os-agnostic local copy --- nova/virt/images.py | 36 +++++++++++++++++++++++++++--------- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/nova/virt/images.py b/nova/virt/images.py index dc50764d9120..90071107b38d 100644 --- a/nova/virt/images.py +++ b/nova/virt/images.py @@ -24,13 +24,15 @@ Handling of VM disk images. import os.path import time import urlparse +import shutil from nova import flags -from nova import process from nova.auth import manager from nova.auth import signer -from nova.objectstore import image +import logging +import urllib2 +import os FLAGS = flags.FLAGS flags.DEFINE_bool('use_s3', True, @@ -47,6 +49,7 @@ def fetch(image, path, user, project): def _fetch_s3_image(image, path, user, project): url = image_url(image) + logging.debug("About to retrieve %s and place it in %s", url, path) # This should probably move somewhere else, like e.g. a download_as # method on User objects and at the same time get rewritten to use @@ -61,17 +64,32 @@ def _fetch_s3_image(image, path, user, project): url_path) headers['Authorization'] = 'AWS %s:%s' % (access, signature) - cmd = ['/usr/bin/curl', '--fail', '--silent', url] - for (k,v) in headers.iteritems(): - cmd += ['-H', '%s: %s' % (k,v)] + def urlretrieve(urlfile, fpath): + chunk = 1*1024*1024 + f = open(fpath, "wb") + while 1: + data = urlfile.read(chunk) + if not data: + break + f.write(data) - cmd += ['-o', path] - return process.SharedPool().execute(executable=cmd[0], args=cmd[1:]) + request = urllib2.Request(url) + for (k, v) in headers.iteritems(): + request.add_header(k, v) + + urlopened = urllib2.urlopen(request) + + urlretrieve(urlopened, path) + + logging.debug("Finished retreving %s -- placed in %s", url, path) + + return def _fetch_local_image(image, path, user, project): - source = _image_path('%s/image' % image) - return process.simple_execute('cp %s %s' % (source, path)) + source = _image_path(os.path.join(image,'image')) + logging.debug("About to copy %s to %s", source, path) + return shutil.copy(source, path) def _image_path(path): From 202da619d383db9e0968a1fc67acdf48101235c0 Mon Sep 17 00:00:00 2001 From: Chiradeep Vittal Date: Fri, 8 Oct 2010 17:59:17 -0700 Subject: [PATCH 05/86] if using local copy (use_s3=false) we need to know where to find the image --- nova/compute/manager.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 131fac406abc..8d2705da2628 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -39,6 +39,8 @@ flags.DEFINE_string('instances_path', utils.abspath('../instances'), 'where instances are stored on disk') flags.DEFINE_string('compute_driver', 'nova.virt.connection.get_connection', 'Driver to use for volume creation') +flags.DEFINE_string('images_path', utils.abspath('../images'), + 'path to decrypted local images if not using s3') class ComputeManager(manager.Manager): From 20aab4195baac543d638cf9c3a1484f8f9fb3d80 Mon Sep 17 00:00:00 2001 From: Chiradeep Vittal Date: Tue, 12 Oct 2010 15:04:39 -0700 Subject: [PATCH 06/86] Add design doc, docstrings, document hyper-v wmi, python wmi usage. Adhere to pep-8 more closely --- nova/virt/hyperv.py | 148 ++++++++++++++++++++++++++++++++------------ 1 file changed, 107 insertions(+), 41 deletions(-) diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index 91b86e2658df..388e833b24f8 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -16,16 +16,58 @@ """ A connection to Hyper-V . +Uses Windows Management Instrumentation (WMI) calls to interact with Hyper-V +Hyper-V WMI usage: + http://msdn.microsoft.com/en-us/library/cc723875%28v=VS.85%29.aspx +The Hyper-V object model briefly: + The physical computer and its hosted virtual machines are each represented + by the Msvm_ComputerSystem class. + + Each virtual machine is associated with a + Msvm_VirtualSystemGlobalSettingData (vs_gs_data) instance and one or more + Msvm_VirtualSystemSettingData (vmsetting) instances. For each vmsetting + there is a series of Msvm_ResourceAllocationSettingData (rasd) objects. + The rasd objects describe the settings for each device in a virtual machine. + Together, the vs_gs_data, vmsettings and rasds describe the configuration + of the virtual machine. + + Creating new resources such as disks and nics involves cloning a default + rasd object and appropriately modifying the clone and calling the + AddVirtualSystemResources WMI method + Changing resources such as memory uses the ModifyVirtualSystemResources + WMI method + +Using the Python WMI library: + Tutorial: + http://timgolden.me.uk/python/wmi/tutorial.html + Hyper-V WMI objects can be retrieved simply by using the class name + of the WMI object and optionally specifying a column to filter the + result set. More complex filters can be formed using WQL (sql-like) + queries. + The parameters and return tuples of WMI method calls can gleaned by + examining the doc string. For example: + >>> vs_man_svc.ModifyVirtualSystemResources.__doc__ + ModifyVirtualSystemResources (ComputerSystem, ResourceSettingData[]) + => (Job, ReturnValue)' + When passing setting data (ResourceSettingData) to the WMI method, + an XML representation of the data is passed in using the GetText_(1) method. + Available methods on a service can be determined using method.keys(): + >>> vs_man_svc.methods.keys() + vmsettings and rasds for a vm can be retrieved using the 'associators' + method with the appropriate return class. + Long running WMI commands generally return a Job (an instance of + Msvm_ConcreteJob) whose state can be polled to determine when it finishes """ import os import logging -import wmi import time from twisted.internet import defer +import wmi +from nova import exception from nova import flags from nova.auth.manager import AuthManager from nova.compute import power_state @@ -39,10 +81,9 @@ HYPERV_POWER_STATE = { 3 : power_state.SHUTDOWN, 2 : power_state.RUNNING, 32768 : power_state.PAUSED, - 32768: power_state.PAUSED, # TODO - 3 : power_state.CRASHED } + REQ_POWER_STATE = { 'Enabled' : 2, 'Disabled': 3, @@ -53,6 +94,11 @@ REQ_POWER_STATE = { } +WMI_JOB_STATUS_STARTED = 4096 +WMI_JOB_STATE_RUNNING = 4 +WMI_JOB_STATE_COMPLETED = 7 + + def get_connection(_): return HyperVConnection() @@ -63,19 +109,22 @@ class HyperVConnection(object): self._cim_conn = wmi.WMI(moniker = '//./root/cimv2') def list_instances(self): + """ Return the names of all the instances known to Hyper-V. """ vms = [v.ElementName \ for v in self._conn.Msvm_ComputerSystem(['ElementName'])] return vms @defer.inlineCallbacks def spawn(self, instance): + """ Create a new VM and start it.""" vm = yield self._lookup(instance.name) if vm is not None: - raise Exception('Attempted to create non-unique name %s' % + raise exception.Duplicate('Attempted to create duplicate name %s' % instance.name) user = AuthManager().get_user(instance['user_id']) project = AuthManager().get_project(instance['project_id']) + #Fetch the file, assume it is a VHD file. vhdfile = os.path.join(FLAGS.instances_path, instance['str_id'])+".vhd" yield images.fetch(instance['image_id'], vhdfile, user, project) @@ -93,14 +142,14 @@ class HyperVConnection(object): self.destroy(instance) def _create_vm(self, instance): - """Create a VM record. """ + """Create a VM but don't start it. """ vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0] vs_gs_data = self._conn.Msvm_VirtualSystemGlobalSettingData.new() vs_gs_data.ElementName = instance['name'] (job, ret_val) = vs_man_svc.DefineVirtualSystem( [], None, vs_gs_data.GetText_(1))[1:] - if (ret_val == 4096 ): #WMI job started + if ret_val == WMI_JOB_STATUS_STARTED: success = self._check_job_status(job) else: success = (ret_val == 0) @@ -117,7 +166,7 @@ class HyperVConnection(object): if s.SettingType == 3][0] #avoid snapshots memsetting = vmsetting.associators(wmi_result_class= 'Msvm_MemorySettingData')[0] - #No Dynamic Memory + #No Dynamic Memory, so reservation, limit and quantity are identical. mem = long(str(instance['memory_mb'])) memsetting.VirtualQuantity = mem memsetting.Reservation = mem @@ -129,8 +178,7 @@ class HyperVConnection(object): logging.debug('Set memory for vm %s...', instance.name) procsetting = vmsetting.associators(wmi_result_class= 'Msvm_ProcessorSettingData')[0] - vcpus = long(str(instance['vcpus'])) - #vcpus = 1 + vcpus = long(instance['vcpus']) procsetting.VirtualQuantity = vcpus procsetting.Reservation = vcpus procsetting.Limit = vcpus @@ -140,11 +188,11 @@ class HyperVConnection(object): logging.debug('Set vcpus for vm %s...', instance.name) - def _create_disk(self, vm_name, vhdfile): """Create a disk and attach it to the vm""" logging.debug("Creating disk for %s by attaching disk file %s", \ vm_name, vhdfile) + #Find the IDE controller for the vm. vms = self._conn.MSVM_ComputerSystem (ElementName=vm_name) vm = vms[0] vmsettings = vm.associators( @@ -154,14 +202,17 @@ class HyperVConnection(object): ctrller = [r for r in rasds if r.ResourceSubType == 'Microsoft Emulated IDE Controller'\ and r.Address == "0" ] + #Find the default disk drive object for the vm and clone it. diskdflt = self._conn.query( "SELECT * FROM Msvm_ResourceAllocationSettingData \ WHERE ResourceSubType LIKE 'Microsoft Synthetic Disk Drive'\ AND InstanceID LIKE '%Default%'")[0] diskdrive = self._clone_wmi_obj( 'Msvm_ResourceAllocationSettingData', diskdflt) - diskdrive.Parent = ctrller[0].path_() + #Set the IDE ctrller as parent. + diskdrive.Parent = ctrller[0].path_() diskdrive.Address = 0 + #Add the cloned disk drive object to the vm. new_resources = self._add_virt_resource(diskdrive, vm) if new_resources is None: @@ -169,31 +220,36 @@ class HyperVConnection(object): diskdrive_path = new_resources[0] logging.debug("New disk drive path is " + diskdrive_path) + #Find the default VHD disk object. vhddefault = self._conn.query( "SELECT * FROM Msvm_ResourceAllocationSettingData \ WHERE ResourceSubType LIKE 'Microsoft Virtual Hard Disk' AND \ InstanceID LIKE '%Default%' ")[0] + #Clone the default and point it to the image file. vhddisk = self._clone_wmi_obj( 'Msvm_ResourceAllocationSettingData', vhddefault) - vhddisk.Parent = diskdrive_path + #Set the new drive as the parent. + vhddisk.Parent = diskdrive_path vhddisk.Connection = [vhdfile] + #Add the new vhd object as a virtual hard disk to the vm. new_resources = self._add_virt_resource(vhddisk, vm) if new_resources is None: raise Exception('Failed to add vhd file to VM %s', vm_name) logging.info("Created disk for %s ", vm_name) - def _create_nic(self, vm_name, mac): """Create a (emulated) nic and attach it to the vm""" logging.debug("Creating nic for %s ", vm_name) + #Find the vswitch that is connected to the physical nic. vms = self._conn.Msvm_ComputerSystem (ElementName=vm_name) extswitch = self._find_external_network() vm = vms[0] switch_svc = self._conn.Msvm_VirtualSwitchManagementService ()[0] - #use Msvm_SyntheticEthernetPortSettingData for Windows VMs or Linux with - #Linux Integration Components installed + #Find the default nic and clone it to create a new nic for the vm. + #Use Msvm_SyntheticEthernetPortSettingData for Windows VMs or Linux with + #Linux Integration Components installed. emulatednics_data = self._conn.Msvm_EmulatedEthernetPortSettingData() default_nic_data = [n for n in emulatednics_data if n.InstanceID.rfind('Default') >0 ] @@ -201,30 +257,33 @@ class HyperVConnection(object): 'Msvm_EmulatedEthernetPortSettingData', default_nic_data[0]) + #Create a port on the vswitch. (created_sw, ret_val) = switch_svc.CreateSwitchPort(vm_name, vm_name, "", extswitch.path_()) - if (ret_val != 0): + if ret_val != 0: logging.debug("Failed to create a new port on the external network") return logging.debug("Created switch port %s on switch %s", vm_name, extswitch.path_()) + #Connect the new nic to the new port. new_nic_data.Connection = [created_sw] new_nic_data.ElementName = vm_name + ' nic' new_nic_data.Address = ''.join(mac.split(':')) new_nic_data.StaticMacAddress = 'TRUE' + #Add the new nic to the vm. new_resources = self._add_virt_resource(new_nic_data, vm) if new_resources is None: raise Exception('Failed to add nic to VM %s', vm_name) logging.info("Created nic for %s ", vm_name) - def _add_virt_resource(self, res_setting_data, target_vm): + """Add a new resource (disk/nic) to the VM""" vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0] (job, new_resources, return_val) = vs_man_svc.\ AddVirtualSystemResources([res_setting_data.GetText_(1)], target_vm.path_()) success = True - if (return_val == 4096 ): #WMI job started + if return_val == WMI_JOB_STATUS_STARTED: success = self._check_job_status(job) else: success = (return_val == 0) @@ -235,16 +294,17 @@ class HyperVConnection(object): #TODO: use the reactor to poll instead of sleep def _check_job_status(self, jobpath): + """Poll WMI job state for completion""" inst_id = jobpath.split(':')[1].split('=')[1].strip('\"') jobs = self._conn.Msvm_ConcreteJob(InstanceID=inst_id) - if (len(jobs) == 0): + if len(jobs) == 0: return False job = jobs[0] - while job.JobState == 4: #job started + while job.JobState == WMI_JOB_STATE_RUNNING: time.sleep(0.1) job = self._conn.Msvm_ConcreteJob(InstanceID=inst_id)[0] - if (job.JobState != 7): #job success + if job.JobState != WMI_JOB_STATE_COMPLETED: logging.debug("WMI job failed: " + job.ErrorSummaryDescription) return False @@ -253,11 +313,13 @@ class HyperVConnection(object): return True - - def _find_external_network(self): + """Find the vswitch that is connected to the physical nic. + Assumes only one physical nic on the host + """ + #If there are no physical nics connected to networks, return. bound = self._conn.Msvm_ExternalEthernetPort(IsBound='TRUE') - if (len(bound) == 0): + if len(bound) == 0: return None return self._conn.Msvm_ExternalEthernetPort(IsBound='TRUE')[0]\ @@ -266,30 +328,33 @@ class HyperVConnection(object): .associators(wmi_result_class='Msvm_VirtualSwitch')[0] def _clone_wmi_obj(self, wmi_class, wmi_obj): - cl = self._conn.__getattr__(wmi_class) - newinst = cl.new() + """Clone a WMI object""" + cl = self._conn.__getattr__(wmi_class) #get the class + newinst = cl.new() + #Copy the properties from the original. for prop in wmi_obj._properties: newinst.Properties_.Item(prop).Value =\ wmi_obj.Properties_.Item(prop).Value return newinst - @defer.inlineCallbacks def reboot(self, instance): + """Reboot the specified instance.""" vm = yield self._lookup(instance.name) if vm is None: - raise Exception('instance not present %s' % instance.name) + raise exception.NotFound('instance not present %s' % instance.name) self._set_vm_state(instance.name, 'Reboot') - @defer.inlineCallbacks def destroy(self, instance): + """Destroy the VM. Also destroy the associated VHD disk files""" logging.debug("Got request to destroy vm %s", instance.name) vm = yield self._lookup(instance.name) if vm is None: defer.returnValue(None) vm = self._conn.Msvm_ComputerSystem (ElementName=instance.name)[0] vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0] + #Stop the VM first. self._set_vm_state(instance.name, 'Disabled') vmsettings = vm.associators(wmi_result_class= 'Msvm_VirtualSystemSettingData') @@ -298,33 +363,35 @@ class HyperVConnection(object): disks = [r for r in rasds \ if r.ResourceSubType == 'Microsoft Virtual Hard Disk' ] diskfiles = [] + #Collect disk file information before destroying the VM. for disk in disks: diskfiles.extend([c for c in disk.Connection]) - + #Nuke the VM. Does not destroy disks. (job, ret_val) = vs_man_svc.DestroyVirtualSystem(vm.path_()) - if (ret_val == 4096 ): #WMI job started + if ret_val == WMI_JOB_STATUS_STARTED: success = self._check_job_status(job) - elif (ret_val == 0): + elif ret_val == 0: success = True if not success: raise Exception('Failed to destroy vm %s' % instance.name) + #Delete associated vhd disk files. for disk in diskfiles: vhdfile = self._cim_conn.CIM_DataFile(Name=disk) for vf in vhdfile: vf.Delete() logging.debug("Deleted disk %s vm %s", vhdfile, instance.name) - - def get_info(self, instance_id): + """Get information about the VM""" vm = self._lookup(instance_id) if vm is None: - raise Exception('instance not present %s' % instance_id) + raise exception.NotFound('instance not present %s' % instance_id) vm = self._conn.Msvm_ComputerSystem(ElementName=instance_id)[0] vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0] vmsettings = vm.associators(wmi_result_class= 'Msvm_VirtualSystemSettingData') settings_paths = [ v.path_() for v in vmsettings] + #See http://msdn.microsoft.com/en-us/library/cc160706%28VS.85%29.aspx summary_info = vs_man_svc.GetSummaryInformation( [4,100,103,105], settings_paths)[1] info = summary_info[0] @@ -341,7 +408,6 @@ class HyperVConnection(object): 'num_cpu': info.NumberOfProcessors, 'cpu_time': info.UpTime} - def _lookup(self, i): vms = self._conn.Msvm_ComputerSystem (ElementName=i) n = len(vms) @@ -353,15 +419,16 @@ class HyperVConnection(object): return vms[0].ElementName def _set_vm_state(self, vm_name, req_state): + """Set the desired state of the VM""" vms = self._conn.Msvm_ComputerSystem (ElementName=vm_name) if len(vms) == 0: return False status = vms[0].RequestStateChange(REQ_POWER_STATE[req_state]) job = status[0] return_val = status[1] - if (return_val == 4096 ): #WMI job started + if return_val == WMI_JOB_STATUS_STARTED: success = self._check_job_status(job) - elif (return_val == 0): + elif return_val == 0: success = True if success: logging.info("Successfully changed vm state of %s to %s", @@ -372,16 +439,15 @@ class HyperVConnection(object): vm_name, req_state) return False - def attach_volume(self, instance_name, device_path, mountpoint): vm = self._lookup(instance_name) if vm is None: - raise Exception('Attempted to attach volume to nonexistent %s vm' % + raise exception.NotFound('Cannot attach volume to missing %s vm' % instance_name) def detach_volume(self, instance_name, mountpoint): vm = self._lookup(instance_name) if vm is None: - raise Exception('Attempted to detach volume from nonexistent %s ' % + raise exception.NotFound('Cannot detach volume from missing %s ' % instance_name) From f224c0ed419f885aa85065d1a27623b22721d34c Mon Sep 17 00:00:00 2001 From: Chiradeep Vittal Date: Tue, 12 Oct 2010 23:45:30 -0700 Subject: [PATCH 07/86] Fix typo, fix import --- nova/virt/images.py | 54 +++++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/nova/virt/images.py b/nova/virt/images.py index 90071107b38d..dad285fe0be0 100644 --- a/nova/virt/images.py +++ b/nova/virt/images.py @@ -27,12 +27,14 @@ import urlparse import shutil from nova import flags +from nova import process from nova.auth import manager from nova.auth import signer import logging import urllib2 import os +import sys FLAGS = flags.FLAGS flags.DEFINE_bool('use_s3', True, @@ -47,6 +49,24 @@ def fetch(image, path, user, project): return f(image, path, user, project) +def _fetch_image_no_curl(url, path, headers): + request = urllib2.Request(url) + for (k, v) in headers.iteritems(): + request.add_header(k, v) + + def urlretrieve(urlfile, fpath): + chunk = 1*1024*1024 + f = open(fpath, "wb") + while 1: + data = urlfile.read(chunk) + if not data: + break + f.write(data) + + urlopened = urllib2.urlopen(request) + urlretrieve(urlopened, path) + logging.debug("Finished retreving %s -- placed in %s", url, path) + def _fetch_s3_image(image, path, user, project): url = image_url(image) logging.debug("About to retrieve %s and place it in %s", url, path) @@ -64,32 +84,22 @@ def _fetch_s3_image(image, path, user, project): url_path) headers['Authorization'] = 'AWS %s:%s' % (access, signature) - def urlretrieve(urlfile, fpath): - chunk = 1*1024*1024 - f = open(fpath, "wb") - while 1: - data = urlfile.read(chunk) - if not data: - break - f.write(data) - - request = urllib2.Request(url) - for (k, v) in headers.iteritems(): - request.add_header(k, v) - - urlopened = urllib2.urlopen(request) - - urlretrieve(urlopened, path) - - logging.debug("Finished retreving %s -- placed in %s", url, path) - - return - + if sys.platform.startswith('win'): + return _fetch_image_no_curl(url, path, headers) + else: + cmd = ['/usr/bin/curl', '--fail', '--silent', url] + for (k,v) in headers.iteritems(): + cmd += ['-H', '%s: %s' % (k,v)] + cmd += ['-o', path] + return process.SharedPool().execute(executable=cmd[0], args=cmd[1:]) def _fetch_local_image(image, path, user, project): source = _image_path(os.path.join(image,'image')) logging.debug("About to copy %s to %s", source, path) - return shutil.copy(source, path) + if sys.platform.startswith('win'): + return shutil.copy(source, path) + else: + return process.simple_execute('cp %s %s' % (source, path)) def _image_path(path): From 01ad0a05c4f93bb5e95a1c781d492374739dce2c Mon Sep 17 00:00:00 2001 From: Chiradeep Vittal Date: Tue, 12 Oct 2010 23:53:31 -0700 Subject: [PATCH 08/86] Remove extraneous newlines --- nova/virt/images.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/nova/virt/images.py b/nova/virt/images.py index dad285fe0be0..75e6f783ec76 100644 --- a/nova/virt/images.py +++ b/nova/virt/images.py @@ -48,7 +48,6 @@ def fetch(image, path, user, project): f = _fetch_local_image return f(image, path, user, project) - def _fetch_image_no_curl(url, path, headers): request = urllib2.Request(url) for (k, v) in headers.iteritems(): @@ -101,11 +100,9 @@ def _fetch_local_image(image, path, user, project): else: return process.simple_execute('cp %s %s' % (source, path)) - def _image_path(path): return os.path.join(FLAGS.images_path, path) - def image_url(image): return "http://%s:%s/_images/%s/image" % (FLAGS.s3_host, FLAGS.s3_port, image) From b28c43c1f66cc111e34e9bbc45a78ff7aa60fd29 Mon Sep 17 00:00:00 2001 From: Chiradeep Vittal Date: Wed, 13 Oct 2010 00:06:29 -0700 Subject: [PATCH 09/86] Newlines again, reorder imports --- nova/virt/images.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/nova/virt/images.py b/nova/virt/images.py index 75e6f783ec76..a68d856a1936 100644 --- a/nova/virt/images.py +++ b/nova/virt/images.py @@ -21,20 +21,20 @@ Handling of VM disk images. """ +import logging +import os import os.path -import time -import urlparse import shutil +import sys +import time +import urllib2 +import urlparse from nova import flags from nova import process from nova.auth import manager from nova.auth import signer -import logging -import urllib2 -import os -import sys FLAGS = flags.FLAGS flags.DEFINE_bool('use_s3', True, @@ -48,6 +48,7 @@ def fetch(image, path, user, project): f = _fetch_local_image return f(image, path, user, project) + def _fetch_image_no_curl(url, path, headers): request = urllib2.Request(url) for (k, v) in headers.iteritems(): @@ -103,6 +104,7 @@ def _fetch_local_image(image, path, user, project): def _image_path(path): return os.path.join(FLAGS.images_path, path) + def image_url(image): return "http://%s:%s/_images/%s/image" % (FLAGS.s3_host, FLAGS.s3_port, image) From 273f5c1c5a3f2ae1f540ba2432cc8a2d0a9c1826 Mon Sep 17 00:00:00 2001 From: Chiradeep Vittal Date: Wed, 13 Oct 2010 23:19:25 -0700 Subject: [PATCH 10/86] Added a unit test but not integrated it --- nova/tests/hyperv_unittest.py | 67 +++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 nova/tests/hyperv_unittest.py diff --git a/nova/tests/hyperv_unittest.py b/nova/tests/hyperv_unittest.py new file mode 100644 index 000000000000..e5c6d719ed86 --- /dev/null +++ b/nova/tests/hyperv_unittest.py @@ -0,0 +1,67 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2010 Cloud.com, Inc +# +# 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. +""" +Tests For Hyper-V driver +""" + +import random + +from nova import db +from nova import flags +from nova import test + +from nova.virt import hyperv + +FLAGS = flags.FLAGS +FLAGS.connection_type = 'hyperv' +# Redis is probably not running on Hyper-V host. +# Change this to the actual Redis host +FLAGS.redis_host = '127.0.0.1' + + +class HyperVTestCase(test.TrialTestCase): + """Test cases for the Hyper-V driver""" + def setUp(self): # pylint: disable-msg=C0103 + pass + + def test_create_destroy(self): + """Create a VM and destroy it""" + instance = {'internal_id' : random.randint(1, 1000000), + 'memory_mb' : '1024', + 'mac_address' : '02:12:34:46:56:67', + 'vcpus' : 2, + 'project_id' : 'fake', + 'instance_type' : 'm1.small'} + + instance_ref = db.instance_create(None, instance) + + conn = hyperv.get_connection(False) + conn._create_vm(instance_ref) # pylint: disable-msg=W0212 + found = [n for n in conn.list_instances() + if n == instance_ref['name']] + self.assertTrue(len(found) == 1) + info = conn.get_info(instance_ref['name']) + #Unfortunately since the vm is not running at this point, + #we cannot obtain memory information from get_info + self.assertEquals(info['num_cpu'], instance_ref['vcpus']) + + conn.destroy(instance_ref) + found = [n for n in conn.list_instances() + if n == instance_ref['name']] + self.assertTrue(len(found) == 0) + + def tearDown(self): # pylint: disable-msg=C0103 + pass From 9caed7b34d9b953bb8ecd306509443d076d1e4fe Mon Sep 17 00:00:00 2001 From: Chiradeep Vittal Date: Wed, 13 Oct 2010 23:21:22 -0700 Subject: [PATCH 11/86] review comments --- nova/compute/manager.py | 2 - nova/virt/hyperv.py | 250 ++++++++++++++++++++++------------------ nova/virt/images.py | 11 +- 3 files changed, 147 insertions(+), 116 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 8d2705da2628..131fac406abc 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -39,8 +39,6 @@ flags.DEFINE_string('instances_path', utils.abspath('../instances'), 'where instances are stored on disk') flags.DEFINE_string('compute_driver', 'nova.virt.connection.get_connection', 'Driver to use for volume creation') -flags.DEFINE_string('images_path', utils.abspath('../images'), - 'path to decrypted local images if not using s3') class ComputeManager(manager.Manager): diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index 388e833b24f8..7451cac97224 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -20,21 +20,21 @@ Uses Windows Management Instrumentation (WMI) calls to interact with Hyper-V Hyper-V WMI usage: http://msdn.microsoft.com/en-us/library/cc723875%28v=VS.85%29.aspx The Hyper-V object model briefly: - The physical computer and its hosted virtual machines are each represented - by the Msvm_ComputerSystem class. + The physical computer and its hosted virtual machines are each represented + by the Msvm_ComputerSystem class. - Each virtual machine is associated with a - Msvm_VirtualSystemGlobalSettingData (vs_gs_data) instance and one or more - Msvm_VirtualSystemSettingData (vmsetting) instances. For each vmsetting - there is a series of Msvm_ResourceAllocationSettingData (rasd) objects. - The rasd objects describe the settings for each device in a virtual machine. - Together, the vs_gs_data, vmsettings and rasds describe the configuration + Each virtual machine is associated with a + Msvm_VirtualSystemGlobalSettingData (vs_gs_data) instance and one or more + Msvm_VirtualSystemSettingData (vmsetting) instances. For each vmsetting + there is a series of Msvm_ResourceAllocationSettingData (rasd) objects. + The rasd objects describe the settings for each device in a VM. + Together, the vs_gs_data, vmsettings and rasds describe the configuration of the virtual machine. - Creating new resources such as disks and nics involves cloning a default - rasd object and appropriately modifying the clone and calling the + Creating new resources such as disks and nics involves cloning a default + rasd object and appropriately modifying the clone and calling the AddVirtualSystemResources WMI method - Changing resources such as memory uses the ModifyVirtualSystemResources + Changing resources such as memory uses the ModifyVirtualSystemResources WMI method Using the Python WMI library: @@ -47,10 +47,10 @@ Using the Python WMI library: The parameters and return tuples of WMI method calls can gleaned by examining the doc string. For example: >>> vs_man_svc.ModifyVirtualSystemResources.__doc__ - ModifyVirtualSystemResources (ComputerSystem, ResourceSettingData[]) + ModifyVirtualSystemResources (ComputerSystem, ResourceSettingData[]) => (Job, ReturnValue)' - When passing setting data (ResourceSettingData) to the WMI method, - an XML representation of the data is passed in using the GetText_(1) method. + When passing setting data (ResourceSettingData) to the WMI method, + an XML representation of the data is passed in using GetText_(1). Available methods on a service can be determined using method.keys(): >>> vs_man_svc.methods.keys() vmsettings and rasds for a vm can be retrieved using the 'associators' @@ -65,22 +65,23 @@ import logging import time from twisted.internet import defer -import wmi from nova import exception from nova import flags -from nova.auth.manager import AuthManager +from nova.auth import manager from nova.compute import power_state from nova.virt import images +wmi = None + FLAGS = flags.FLAGS HYPERV_POWER_STATE = { - 3 : power_state.SHUTDOWN, - 2 : power_state.RUNNING, - 32768 : power_state.PAUSED, + 3 : power_state.SHUTDOWN, + 2 : power_state.RUNNING, + 32768 : power_state.PAUSED, } @@ -98,15 +99,42 @@ WMI_JOB_STATUS_STARTED = 4096 WMI_JOB_STATE_RUNNING = 4 WMI_JOB_STATE_COMPLETED = 7 +##### Exceptions + + +class HyperVError(Exception): + """Base Exception class for all hyper-v errors.""" + def __init__(self, *args): + Exception.__init__(self, *args) + + +class VmResourceAllocationError(HyperVError): + """Raised when Hyper-V is unable to create or add a resource to + a VM + """ + def __init__(self, *args): + HyperVError.__init__(self, *args) + + +class VmOperationError(HyperVError): + """Raised when Hyper-V is unable to change the state of + a VM (start/stop/reboot/destroy) + """ + def __init__(self, *args): + HyperVError.__init__(self, *args) + def get_connection(_): + global wmi + if wmi is None: + wmi = __import__('wmi') return HyperVConnection() class HyperVConnection(object): def __init__(self): - self._conn = wmi.WMI(moniker = '//./root/virtualization') - self._cim_conn = wmi.WMI(moniker = '//./root/cimv2') + self._conn = wmi.WMI(moniker='//./root/virtualization') + self._cim_conn = wmi.WMI(moniker='//./root/cimv2') def list_instances(self): """ Return the names of all the instances known to Hyper-V. """ @@ -121,20 +149,22 @@ class HyperVConnection(object): if vm is not None: raise exception.Duplicate('Attempted to create duplicate name %s' % instance.name) - - user = AuthManager().get_user(instance['user_id']) - project = AuthManager().get_project(instance['project_id']) + + user = manager.AuthManager().get_user(instance['user_id']) + project = manager.AuthManager().get_project(instance['project_id']) #Fetch the file, assume it is a VHD file. - vhdfile = os.path.join(FLAGS.instances_path, instance['str_id'])+".vhd" + base_vhd_filename = os.path.join(FLAGS.instances_path, + instance['str_id']) + vhdfile = "%s.vhd" % (base_vhd_filename) yield images.fetch(instance['image_id'], vhdfile, user, project) - + try: yield self._create_vm(instance) yield self._create_disk(instance['name'], vhdfile) yield self._create_nic(instance['name'], instance['mac_address']) - - logging.debug ('Starting VM %s ', instance.name) + + logging.debug('Starting VM %s ', instance.name) yield self._set_vm_state(instance['name'], 'Enabled') logging.info('Started VM %s ', instance.name) except Exception as exn: @@ -147,23 +177,24 @@ class HyperVConnection(object): vs_gs_data = self._conn.Msvm_VirtualSystemGlobalSettingData.new() vs_gs_data.ElementName = instance['name'] - (job, ret_val) = vs_man_svc.DefineVirtualSystem( + (job, ret_val) = vs_man_svc.DefineVirtualSystem( [], None, vs_gs_data.GetText_(1))[1:] - if ret_val == WMI_JOB_STATUS_STARTED: + if ret_val == WMI_JOB_STATUS_STARTED: success = self._check_job_status(job) else: success = (ret_val == 0) - - if not success: - raise Exception('Failed to create VM %s', instance.name) - - logging.debug('Created VM %s...', instance.name) - vm = self._conn.Msvm_ComputerSystem (ElementName=instance.name)[0] - vmsettings = vm.associators(wmi_result_class= - 'Msvm_VirtualSystemSettingData') + if not success: + raise VmResourceAllocationException('Failed to create VM %s', + instance.name) + + logging.debug('Created VM %s...', instance.name) + vm = self._conn.Msvm_ComputerSystem(ElementName=instance.name)[0] + + vmsettings = vm.associators( + wmi_result_class='Msvm_VirtualSystemSettingData') vmsetting = [s for s in vmsettings - if s.SettingType == 3][0] #avoid snapshots + if s.SettingType == 3][0] # avoid snapshots memsetting = vmsetting.associators(wmi_result_class= 'Msvm_MemorySettingData')[0] #No Dynamic Memory, so reservation, limit and quantity are identical. @@ -174,7 +205,6 @@ class HyperVConnection(object): (job, ret_val) = vs_man_svc.ModifyVirtualSystemResources( vm.path_(), [memsetting.GetText_(1)]) - logging.debug('Set memory for vm %s...', instance.name) procsetting = vmsetting.associators(wmi_result_class= 'Msvm_ProcessorSettingData')[0] @@ -185,41 +215,39 @@ class HyperVConnection(object): (job, ret_val) = vs_man_svc.ModifyVirtualSystemResources( vm.path_(), [procsetting.GetText_(1)]) - logging.debug('Set vcpus for vm %s...', instance.name) - + def _create_disk(self, vm_name, vhdfile): """Create a disk and attach it to the vm""" - logging.debug("Creating disk for %s by attaching disk file %s", \ + logging.debug("Creating disk for %s by attaching disk file %s", vm_name, vhdfile) #Find the IDE controller for the vm. - vms = self._conn.MSVM_ComputerSystem (ElementName=vm_name) + vms = self._conn.MSVM_ComputerSystem(ElementName=vm_name) vm = vms[0] vmsettings = vm.associators( wmi_result_class='Msvm_VirtualSystemSettingData') rasds = vmsettings[0].associators( wmi_result_class='MSVM_ResourceAllocationSettingData') ctrller = [r for r in rasds - if r.ResourceSubType == 'Microsoft Emulated IDE Controller'\ - and r.Address == "0" ] + if r.ResourceSubType == 'Microsoft Emulated IDE Controller'\ + and r.Address == "0"] #Find the default disk drive object for the vm and clone it. diskdflt = self._conn.query( - "SELECT * FROM Msvm_ResourceAllocationSettingData \ - WHERE ResourceSubType LIKE 'Microsoft Synthetic Disk Drive'\ - AND InstanceID LIKE '%Default%'")[0] + "SELECT * FROM Msvm_ResourceAllocationSettingData \ + WHERE ResourceSubType LIKE 'Microsoft Synthetic Disk Drive'\ + AND InstanceID LIKE '%Default%'")[0] diskdrive = self._clone_wmi_obj( 'Msvm_ResourceAllocationSettingData', diskdflt) #Set the IDE ctrller as parent. - diskdrive.Parent = ctrller[0].path_() + diskdrive.Parent = ctrller[0].path_() diskdrive.Address = 0 #Add the cloned disk drive object to the vm. new_resources = self._add_virt_resource(diskdrive, vm) - if new_resources is None: - raise Exception('Failed to add diskdrive to VM %s', vm_name) - + raise VmResourceAllocationError('Failed to add diskdrive to VM %s', + vm_name) diskdrive_path = new_resources[0] - logging.debug("New disk drive path is " + diskdrive_path) + logging.debug("New disk drive path is %s", diskdrive_path) #Find the default VHD disk object. vhddefault = self._conn.query( "SELECT * FROM Msvm_ResourceAllocationSettingData \ @@ -230,64 +258,66 @@ class HyperVConnection(object): vhddisk = self._clone_wmi_obj( 'Msvm_ResourceAllocationSettingData', vhddefault) #Set the new drive as the parent. - vhddisk.Parent = diskdrive_path + vhddisk.Parent = diskdrive_path vhddisk.Connection = [vhdfile] #Add the new vhd object as a virtual hard disk to the vm. new_resources = self._add_virt_resource(vhddisk, vm) if new_resources is None: - raise Exception('Failed to add vhd file to VM %s', vm_name) + raise VmResourceAllocationError('Failed to add vhd file to VM %s', + vm_name) logging.info("Created disk for %s ", vm_name) - + def _create_nic(self, vm_name, mac): """Create a (emulated) nic and attach it to the vm""" logging.debug("Creating nic for %s ", vm_name) #Find the vswitch that is connected to the physical nic. - vms = self._conn.Msvm_ComputerSystem (ElementName=vm_name) + vms = self._conn.Msvm_ComputerSystem(ElementName=vm_name) extswitch = self._find_external_network() vm = vms[0] - switch_svc = self._conn.Msvm_VirtualSwitchManagementService ()[0] + switch_svc = self._conn.Msvm_VirtualSwitchManagementService()[0] #Find the default nic and clone it to create a new nic for the vm. - #Use Msvm_SyntheticEthernetPortSettingData for Windows VMs or Linux with + #Use Msvm_SyntheticEthernetPortSettingData for Windows or Linux with #Linux Integration Components installed. emulatednics_data = self._conn.Msvm_EmulatedEthernetPortSettingData() default_nic_data = [n for n in emulatednics_data - if n.InstanceID.rfind('Default') >0 ] + if n.InstanceID.rfind('Default') > 0] new_nic_data = self._clone_wmi_obj( 'Msvm_EmulatedEthernetPortSettingData', default_nic_data[0]) - #Create a port on the vswitch. - (created_sw, ret_val) = switch_svc.CreateSwitchPort(vm_name, vm_name, + (new_port, ret_val) = switch_svc.CreateSwitchPort(vm_name, vm_name, "", extswitch.path_()) if ret_val != 0: - logging.debug("Failed to create a new port on the external network") - return + logging.error("Failed creating a new port on the external vswitch") + raise VmResourceAllocationError('Failed creating port for %s', + vm_name) logging.debug("Created switch port %s on switch %s", vm_name, extswitch.path_()) #Connect the new nic to the new port. - new_nic_data.Connection = [created_sw] + new_nic_data.Connection = [new_port] new_nic_data.ElementName = vm_name + ' nic' new_nic_data.Address = ''.join(mac.split(':')) new_nic_data.StaticMacAddress = 'TRUE' #Add the new nic to the vm. new_resources = self._add_virt_resource(new_nic_data, vm) if new_resources is None: - raise Exception('Failed to add nic to VM %s', vm_name) + raise VmResourceAllocationError('Failed to add nic to VM %s', + vm_name) logging.info("Created nic for %s ", vm_name) def _add_virt_resource(self, res_setting_data, target_vm): """Add a new resource (disk/nic) to the VM""" vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0] - (job, new_resources, return_val) = vs_man_svc.\ + (job, new_resources, ret_val) = vs_man_svc.\ AddVirtualSystemResources([res_setting_data.GetText_(1)], target_vm.path_()) success = True - if return_val == WMI_JOB_STATUS_STARTED: + if ret_val == WMI_JOB_STATUS_STARTED: success = self._check_job_status(job) else: - success = (return_val == 0) - if success: + success = (ret_val == 0) + if success: return new_resources else: return None @@ -295,24 +325,23 @@ class HyperVConnection(object): #TODO: use the reactor to poll instead of sleep def _check_job_status(self, jobpath): """Poll WMI job state for completion""" + #Jobs have a path of the form: + #\\WIN-P5IG7367DAG\root\virtualization:Msvm_ConcreteJob.InstanceID="8A496B9C-AF4D-4E98-BD3C-1128CD85320D" inst_id = jobpath.split(':')[1].split('=')[1].strip('\"') jobs = self._conn.Msvm_ConcreteJob(InstanceID=inst_id) if len(jobs) == 0: return False job = jobs[0] - while job.JobState == WMI_JOB_STATE_RUNNING: + while job.JobState == WMI_JOB_STATE_RUNNING: time.sleep(0.1) job = self._conn.Msvm_ConcreteJob(InstanceID=inst_id)[0] - - if job.JobState != WMI_JOB_STATE_COMPLETED: - logging.debug("WMI job failed: " + job.ErrorSummaryDescription) + if job.JobState != WMI_JOB_STATE_COMPLETED: + logging.debug("WMI job failed: %s", job.ErrorSummaryDescription) return False - - logging.debug("WMI job succeeded: " + job.Description + ",Elapsed = " \ - + job.ElapsedTime) - + logging.debug("WMI job succeeded: %s, Elapsed=%s ", job.Description, + job.ElapsedTime) return True - + def _find_external_network(self): """Find the vswitch that is connected to the physical nic. Assumes only one physical nic on the host @@ -321,16 +350,15 @@ class HyperVConnection(object): bound = self._conn.Msvm_ExternalEthernetPort(IsBound='TRUE') if len(bound) == 0: return None - return self._conn.Msvm_ExternalEthernetPort(IsBound='TRUE')[0]\ .associators(wmi_result_class='Msvm_SwitchLANEndpoint')[0]\ .associators(wmi_result_class='Msvm_SwitchPort')[0]\ - .associators(wmi_result_class='Msvm_VirtualSwitch')[0] + .associators(wmi_result_class='Msvm_VirtualSwitch')[0] def _clone_wmi_obj(self, wmi_class, wmi_obj): """Clone a WMI object""" - cl = self._conn.__getattr__(wmi_class) #get the class - newinst = cl.new() + cl = self._conn.__getattr__(wmi_class) # get the class + newinst = cl.new() #Copy the properties from the original. for prop in wmi_obj._properties: newinst.Properties_.Item(prop).Value =\ @@ -343,8 +371,8 @@ class HyperVConnection(object): vm = yield self._lookup(instance.name) if vm is None: raise exception.NotFound('instance not present %s' % instance.name) - self._set_vm_state(instance.name, 'Reboot') - + self._set_vm_state(instance.name, 'Reboot') + @defer.inlineCallbacks def destroy(self, instance): """Destroy the VM. Also destroy the associated VHD disk files""" @@ -352,7 +380,7 @@ class HyperVConnection(object): vm = yield self._lookup(instance.name) if vm is None: defer.returnValue(None) - vm = self._conn.Msvm_ComputerSystem (ElementName=instance.name)[0] + vm = self._conn.Msvm_ComputerSystem(ElementName=instance.name)[0] vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0] #Stop the VM first. self._set_vm_state(instance.name, 'Disabled') @@ -361,26 +389,26 @@ class HyperVConnection(object): rasds = vmsettings[0].associators(wmi_result_class= 'MSVM_ResourceAllocationSettingData') disks = [r for r in rasds \ - if r.ResourceSubType == 'Microsoft Virtual Hard Disk' ] + if r.ResourceSubType == 'Microsoft Virtual Hard Disk'] diskfiles = [] #Collect disk file information before destroying the VM. for disk in disks: diskfiles.extend([c for c in disk.Connection]) #Nuke the VM. Does not destroy disks. (job, ret_val) = vs_man_svc.DestroyVirtualSystem(vm.path_()) - if ret_val == WMI_JOB_STATUS_STARTED: + if ret_val == WMI_JOB_STATUS_STARTED: success = self._check_job_status(job) elif ret_val == 0: success = True if not success: - raise Exception('Failed to destroy vm %s' % instance.name) + raise VmOperationError('Failed to destroy vm %s' % instance.name) #Delete associated vhd disk files. for disk in diskfiles: vhdfile = self._cim_conn.CIM_DataFile(Name=disk) for vf in vhdfile: vf.Delete() logging.debug("Deleted disk %s vm %s", vhdfile, instance.name) - + def get_info(self, instance_id): """Get information about the VM""" vm = self._lookup(instance_id) @@ -390,10 +418,10 @@ class HyperVConnection(object): vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0] vmsettings = vm.associators(wmi_result_class= 'Msvm_VirtualSystemSettingData') - settings_paths = [ v.path_() for v in vmsettings] + settings_paths = [v.path_() for v in vmsettings] #See http://msdn.microsoft.com/en-us/library/cc160706%28VS.85%29.aspx summary_info = vs_man_svc.GetSummaryInformation( - [4,100,103,105], settings_paths)[1] + [4, 100, 103, 105], settings_paths)[1] info = summary_info[0] logging.debug("Got Info for vm %s: state=%s, mem=%s, num_cpu=%s, \ cpu_time=%s", instance_id, @@ -401,16 +429,16 @@ class HyperVConnection(object): str(info.MemoryUsage), str(info.NumberOfProcessors), str(info.UpTime)) - + return {'state': HYPERV_POWER_STATE[info.EnabledState], 'max_mem': info.MemoryUsage, 'mem': info.MemoryUsage, 'num_cpu': info.NumberOfProcessors, 'cpu_time': info.UpTime} - + def _lookup(self, i): - vms = self._conn.Msvm_ComputerSystem (ElementName=i) - n = len(vms) + vms = self._conn.Msvm_ComputerSystem(ElementName=i) + n = len(vms) if n == 0: return None elif n > 1: @@ -420,34 +448,36 @@ class HyperVConnection(object): def _set_vm_state(self, vm_name, req_state): """Set the desired state of the VM""" - vms = self._conn.Msvm_ComputerSystem (ElementName=vm_name) + vms = self._conn.Msvm_ComputerSystem(ElementName=vm_name) if len(vms) == 0: return False - status = vms[0].RequestStateChange(REQ_POWER_STATE[req_state]) - job = status[0] - return_val = status[1] - if return_val == WMI_JOB_STATUS_STARTED: + (job, ret_val) = vms[0].RequestStateChange(REQ_POWER_STATE[req_state]) + success = False + if ret_val == WMI_JOB_STATUS_STARTED: success = self._check_job_status(job) - elif return_val == 0: + elif ret_val == 0: + success = True + elif ret_val == 32775: + #Invalid state for current operation. Typically means it is + #already in the state requested success = True if success: logging.info("Successfully changed vm state of %s to %s", vm_name, req_state) - return True else: - logging.debug("Failed to change vm state of %s to %s", + logging.error("Failed to change vm state of %s to %s", vm_name, req_state) - return False - + raise VmOperationError("Failed to change vm state of %s to %s", + vm_name, req_state) + def attach_volume(self, instance_name, device_path, mountpoint): - vm = self._lookup(instance_name) + vm = self._lookup(instance_name) if vm is None: raise exception.NotFound('Cannot attach volume to missing %s vm' % instance_name) def detach_volume(self, instance_name, mountpoint): - vm = self._lookup(instance_name) + vm = self._lookup(instance_name) if vm is None: raise exception.NotFound('Cannot detach volume from missing %s ' % instance_name) - diff --git a/nova/virt/images.py b/nova/virt/images.py index a68d856a1936..6ef652e4a55f 100644 --- a/nova/virt/images.py +++ b/nova/virt/images.py @@ -22,7 +22,6 @@ Handling of VM disk images. """ import logging -import os import os.path import shutil import sys @@ -34,6 +33,7 @@ from nova import flags from nova import process from nova.auth import manager from nova.auth import signer +from nova.objectstore import image FLAGS = flags.FLAGS @@ -55,7 +55,7 @@ def _fetch_image_no_curl(url, path, headers): request.add_header(k, v) def urlretrieve(urlfile, fpath): - chunk = 1*1024*1024 + chunk = 1 * 1024 * 1024 f = open(fpath, "wb") while 1: data = urlfile.read(chunk) @@ -66,7 +66,8 @@ def _fetch_image_no_curl(url, path, headers): urlopened = urllib2.urlopen(request) urlretrieve(urlopened, path) logging.debug("Finished retreving %s -- placed in %s", url, path) - + + def _fetch_s3_image(image, path, user, project): url = image_url(image) logging.debug("About to retrieve %s and place it in %s", url, path) @@ -93,14 +94,16 @@ def _fetch_s3_image(image, path, user, project): cmd += ['-o', path] return process.SharedPool().execute(executable=cmd[0], args=cmd[1:]) + def _fetch_local_image(image, path, user, project): - source = _image_path(os.path.join(image,'image')) + source = _image_path(os.path.join(image, 'image')) logging.debug("About to copy %s to %s", source, path) if sys.platform.startswith('win'): return shutil.copy(source, path) else: return process.simple_execute('cp %s %s' % (source, path)) + def _image_path(path): return os.path.join(FLAGS.images_path, path) From 5a34f93790cf6fb98e9474797f5be3f231a4a6a4 Mon Sep 17 00:00:00 2001 From: Chiradeep Vittal Date: Thu, 14 Oct 2010 11:27:42 -0700 Subject: [PATCH 12/86] fix indent --- nova/virt/images.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nova/virt/images.py b/nova/virt/images.py index 6ef652e4a55f..b7daeb06483a 100644 --- a/nova/virt/images.py +++ b/nova/virt/images.py @@ -91,8 +91,9 @@ def _fetch_s3_image(image, path, user, project): cmd = ['/usr/bin/curl', '--fail', '--silent', url] for (k,v) in headers.iteritems(): cmd += ['-H', '%s: %s' % (k,v)] - cmd += ['-o', path] - return process.SharedPool().execute(executable=cmd[0], args=cmd[1:]) + + cmd += ['-o', path] + return process.SharedPool().execute(executable=cmd[0], args=cmd[1:]) def _fetch_local_image(image, path, user, project): From b328bac09fee6ff2de6e8326e655ee648bda5e2d Mon Sep 17 00:00:00 2001 From: Chiradeep Vittal Date: Thu, 14 Oct 2010 11:56:25 -0700 Subject: [PATCH 13/86] revert to generic exceptions --- nova/virt/hyperv.py | 38 +++++++------------------------------- 1 file changed, 7 insertions(+), 31 deletions(-) diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index 7451cac97224..9688891168c8 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -99,30 +99,6 @@ WMI_JOB_STATUS_STARTED = 4096 WMI_JOB_STATE_RUNNING = 4 WMI_JOB_STATE_COMPLETED = 7 -##### Exceptions - - -class HyperVError(Exception): - """Base Exception class for all hyper-v errors.""" - def __init__(self, *args): - Exception.__init__(self, *args) - - -class VmResourceAllocationError(HyperVError): - """Raised when Hyper-V is unable to create or add a resource to - a VM - """ - def __init__(self, *args): - HyperVError.__init__(self, *args) - - -class VmOperationError(HyperVError): - """Raised when Hyper-V is unable to change the state of - a VM (start/stop/reboot/destroy) - """ - def __init__(self, *args): - HyperVError.__init__(self, *args) - def get_connection(_): global wmi @@ -244,7 +220,7 @@ class HyperVConnection(object): #Add the cloned disk drive object to the vm. new_resources = self._add_virt_resource(diskdrive, vm) if new_resources is None: - raise VmResourceAllocationError('Failed to add diskdrive to VM %s', + raise Exception('Failed to add diskdrive to VM %s', vm_name) diskdrive_path = new_resources[0] logging.debug("New disk drive path is %s", diskdrive_path) @@ -264,7 +240,7 @@ class HyperVConnection(object): #Add the new vhd object as a virtual hard disk to the vm. new_resources = self._add_virt_resource(vhddisk, vm) if new_resources is None: - raise VmResourceAllocationError('Failed to add vhd file to VM %s', + raise Exception('Failed to add vhd file to VM %s', vm_name) logging.info("Created disk for %s ", vm_name) @@ -290,7 +266,7 @@ class HyperVConnection(object): "", extswitch.path_()) if ret_val != 0: logging.error("Failed creating a new port on the external vswitch") - raise VmResourceAllocationError('Failed creating port for %s', + raise Exception('Failed creating port for %s', vm_name) logging.debug("Created switch port %s on switch %s", vm_name, extswitch.path_()) @@ -302,7 +278,7 @@ class HyperVConnection(object): #Add the new nic to the vm. new_resources = self._add_virt_resource(new_nic_data, vm) if new_resources is None: - raise VmResourceAllocationError('Failed to add nic to VM %s', + raise Exception('Failed to add nic to VM %s', vm_name) logging.info("Created nic for %s ", vm_name) @@ -327,7 +303,7 @@ class HyperVConnection(object): """Poll WMI job state for completion""" #Jobs have a path of the form: #\\WIN-P5IG7367DAG\root\virtualization:Msvm_ConcreteJob.InstanceID="8A496B9C-AF4D-4E98-BD3C-1128CD85320D" - inst_id = jobpath.split(':')[1].split('=')[1].strip('\"') + inst_id = jobpath.split('=')[1].strip('"') jobs = self._conn.Msvm_ConcreteJob(InstanceID=inst_id) if len(jobs) == 0: return False @@ -401,7 +377,7 @@ class HyperVConnection(object): elif ret_val == 0: success = True if not success: - raise VmOperationError('Failed to destroy vm %s' % instance.name) + raise Exception('Failed to destroy vm %s' % instance.name) #Delete associated vhd disk files. for disk in diskfiles: vhdfile = self._cim_conn.CIM_DataFile(Name=disk) @@ -467,7 +443,7 @@ class HyperVConnection(object): else: logging.error("Failed to change vm state of %s to %s", vm_name, req_state) - raise VmOperationError("Failed to change vm state of %s to %s", + raise Exception("Failed to change vm state of %s to %s", vm_name, req_state) def attach_volume(self, instance_name, device_path, mountpoint): From a58648f0ce5472e0b671d1b043fc4e0afd01658c Mon Sep 17 00:00:00 2001 From: Chiradeep Vittal Date: Thu, 14 Oct 2010 13:37:49 -0700 Subject: [PATCH 14/86] remove nonexistent exception --- nova/virt/hyperv.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index 9688891168c8..9adb2f00a590 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -161,8 +161,7 @@ class HyperVConnection(object): success = (ret_val == 0) if not success: - raise VmResourceAllocationException('Failed to create VM %s', - instance.name) + raise Exception('Failed to create VM %s', instance.name) logging.debug('Created VM %s...', instance.name) vm = self._conn.Msvm_ComputerSystem(ElementName=instance.name)[0] From 77d7e022fd5f2c8709a6784cc83429494d126a3b Mon Sep 17 00:00:00 2001 From: Eric Day Date: Thu, 9 Dec 2010 13:59:50 -0800 Subject: [PATCH 15/86] Converted the instance table to use a uuid instead of a auto_increment ID and a random internal_id. I had to use a String(32) column with hex and not a String(16) with bytes because SQLAlchemy doesn't like non-unicode strings going in for String types. We could try another type, but I didn't want a primary_key on blob types. --- nova/api/ec2/cloud.py | 70 +++++++++++------------- nova/api/openstack/servers.py | 4 +- nova/compute/api.py | 26 ++++----- nova/db/api.py | 8 +-- nova/db/sqlalchemy/api.py | 32 +++-------- nova/db/sqlalchemy/models.py | 23 ++++---- nova/tests/api/openstack/test_servers.py | 9 ++- nova/tests/cloud_unittest.py | 4 +- 8 files changed, 78 insertions(+), 98 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 05f8c3d0b04c..a831b2a6220f 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -72,18 +72,14 @@ def _gen_key(context, user_id, key_name): return {'private_key': private_key, 'fingerprint': fingerprint} -def ec2_id_to_internal_id(ec2_id): - """Convert an ec2 ID (i-[base 36 number]) to an internal id (int)""" - return int(ec2_id[2:], 36) +def ec2_id_to_id(ec2_id): + """Convert an ec2 ID and an instance ID.""" + return ec2_id[2:] -def internal_id_to_ec2_id(internal_id): - """Convert an internal ID (int) to an ec2 ID (i-[base 36 number])""" - digits = [] - while internal_id != 0: - internal_id, remainder = divmod(internal_id, 36) - digits.append('0123456789abcdefghijklmnopqrstuvwxyz'[remainder]) - return "i-%s" % ''.join(reversed(digits)) +def id_to_ec2_id(instance_id): + """Convert an instance ID to an ec2 ID.""" + return "i-%s" % instance_id class CloudController(object): @@ -153,7 +149,7 @@ class CloudController(object): hostname = instance_ref['hostname'] floating_ip = db.instance_get_floating_address(ctxt, instance_ref['id']) - ec2_id = internal_id_to_ec2_id(instance_ref['internal_id']) + ec2_id = id_to_ec2_id(instance_ref['id']) data = { 'user-data': base64.b64decode(instance_ref['user_data']), 'meta-data': { @@ -437,8 +433,8 @@ class CloudController(object): def get_console_output(self, context, instance_id, **kwargs): # instance_id is passed in as a list of instances ec2_id = instance_id[0] - internal_id = ec2_id_to_internal_id(ec2_id) - instance_ref = self.compute_api.get_instance(context, internal_id) + instance_id = ec2_id_to_id(ec2_id) + instance_ref = self.compute_api.get_instance(context, instance_id) output = rpc.call(context, '%s.%s' % (FLAGS.compute_topic, instance_ref['host']), @@ -464,8 +460,8 @@ class CloudController(object): instance_ec2_id = None instance_data = None if volume.get('instance', None): - internal_id = volume['instance']['internal_id'] - instance_ec2_id = internal_id_to_ec2_id(internal_id) + instance_id = volume['instance']['id'] + instance_ec2_id = id_to_ec2_id(instance_id) instance_data = '%s[%s]' % (instance_ec2_id, volume['instance']['host']) v = {} @@ -534,8 +530,8 @@ class CloudController(object): raise exception.ApiError("Volume status must be available") if volume_ref['attach_status'] == "attached": raise exception.ApiError("Volume is already attached") - internal_id = ec2_id_to_internal_id(instance_id) - instance_ref = self.compute_api.get_instance(context, internal_id) + instance_id = ec2_id_to_id(instance_id) + instance_ref = self.compute_api.get_instance(context, instance_id) host = instance_ref['host'] rpc.cast(context, db.queue_get_for(context, FLAGS.compute_topic, host), @@ -570,11 +566,11 @@ class CloudController(object): # If the instance doesn't exist anymore, # then we need to call detach blind db.volume_detached(context) - internal_id = instance_ref['internal_id'] - ec2_id = internal_id_to_ec2_id(internal_id) + instance_id = instance_ref['id'] + ec2_id = id_to_ec2_id(instance_id) return {'attachTime': volume_ref['attach_time'], 'device': volume_ref['mountpoint'], - 'instanceId': internal_id, + 'instanceId': instance_id, 'requestId': context.request_id, 'status': volume_ref['attach_status'], 'volumeId': volume_ref['id']} @@ -619,8 +615,8 @@ class CloudController(object): if instance['image_id'] == FLAGS.vpn_image_id: continue i = {} - internal_id = instance['internal_id'] - ec2_id = internal_id_to_ec2_id(internal_id) + instance_id = instance['id'] + ec2_id = id_to_ec2_id(instance_id) i['instanceId'] = ec2_id i['imageId'] = instance['image_id'] i['instanceState'] = { @@ -673,8 +669,8 @@ class CloudController(object): ec2_id = None if (floating_ip_ref['fixed_ip'] and floating_ip_ref['fixed_ip']['instance']): - internal_id = floating_ip_ref['fixed_ip']['instance']['ec2_id'] - ec2_id = internal_id_to_ec2_id(internal_id) + instance_id = floating_ip_ref['fixed_ip']['instance']['ec2_id'] + ec2_id = id_to_ec2_id(instance_id) address_rv = {'public_ip': address, 'instance_id': ec2_id} if context.user.is_admin(): @@ -709,8 +705,8 @@ class CloudController(object): return {'releaseResponse': ["Address released."]} def associate_address(self, context, instance_id, public_ip, **kwargs): - internal_id = ec2_id_to_internal_id(instance_id) - instance_ref = self.compute_api.get_instance(context, internal_id) + instance_id = ec2_id_to_id(instance_id) + instance_ref = self.compute_api.get_instance(context, instance_id) fixed_address = db.instance_get_fixed_address(context, instance_ref['id']) floating_ip_ref = db.floating_ip_get_by_address(context, public_ip) @@ -755,7 +751,7 @@ class CloudController(object): description=kwargs.get('display_description'), key_name=kwargs.get('key_name'), security_group=kwargs.get('security_group'), - generate_hostname=internal_id_to_ec2_id) + generate_hostname=id_to_ec2_id) return self._format_run_instances(context, instances[0]['reservation_id']) @@ -764,27 +760,27 @@ class CloudController(object): instance_id is a kwarg so its name cannot be modified.""" logging.debug("Going to start terminating instances") for ec2_id in instance_id: - internal_id = ec2_id_to_internal_id(ec2_id) - self.compute_api.delete_instance(context, internal_id) + instance_id = ec2_id_to_id(ec2_id) + self.compute_api.delete_instance(context, instance_id) return True def reboot_instances(self, context, instance_id, **kwargs): """instance_id is a list of instance ids""" for ec2_id in instance_id: - internal_id = ec2_id_to_internal_id(ec2_id) - self.compute_api.reboot(context, internal_id) + instance_id = ec2_id_to_id(ec2_id) + self.compute_api.reboot(context, instance_id) return True def rescue_instance(self, context, instance_id, **kwargs): """This is an extension to the normal ec2_api""" - internal_id = ec2_id_to_internal_id(instance_id) - self.compute_api.rescue(context, internal_id) + instance_id = ec2_id_to_id(instance_id) + self.compute_api.rescue(context, instance_id) return True def unrescue_instance(self, context, instance_id, **kwargs): """This is an extension to the normal ec2_api""" - internal_id = ec2_id_to_internal_id(instance_id) - self.compute_api.unrescue(context, internal_id) + instance_id = ec2_id_to_id(instance_id) + self.compute_api.unrescue(context, instance_id) return True def update_instance(self, context, ec2_id, **kwargs): @@ -794,8 +790,8 @@ class CloudController(object): if field in kwargs: changes[field] = kwargs[field] if changes: - internal_id = ec2_id_to_internal_id(ec2_id) - inst = self.compute_api.get_instance(context, internal_id) + instance_id = ec2_id_to_id(ec2_id) + inst = self.compute_api.get_instance(context, instance_id) db.instance_update(context, inst['id'], kwargs) return True diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 6f2f6fed992e..f8f40b764e17 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -46,7 +46,7 @@ def _entity_detail(inst): inst_dict = {} mapped_keys = dict(status='state', imageId='image_id', - flavorId='instance_type', name='display_name', id='internal_id') + flavorId='instance_type', name='display_name', id='id') for k, v in mapped_keys.iteritems(): inst_dict[k] = inst[v] @@ -61,7 +61,7 @@ def _entity_detail(inst): def _entity_inst(inst): """ Filters all model attributes save for id and name """ - return dict(server=dict(id=inst['internal_id'], name=inst['display_name'])) + return dict(server=dict(id=inst['id'], name=inst['display_name'])) class Controller(wsgi.Controller): diff --git a/nova/compute/api.py b/nova/compute/api.py index 8e0efa4cc756..f310c575f215 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -36,9 +36,9 @@ from nova.db import base FLAGS = flags.FLAGS -def generate_default_hostname(internal_id): +def generate_default_hostname(instance_id): """Default function to generate a hostname given an instance reference.""" - return str(internal_id) + return str(instance_id) class ComputeAPI(base.Base): @@ -127,7 +127,6 @@ class ComputeAPI(base.Base): **base_options) instance = self.db.instance_create(context, instance) instance_id = instance['id'] - internal_id = instance['internal_id'] elevated = context.elevated() if not security_groups: @@ -138,9 +137,9 @@ class ComputeAPI(base.Base): security_group_id) # Set sane defaults if not specified - updates = dict(hostname=generate_hostname(internal_id)) + updates = dict(hostname=generate_hostname(instance_id)) if 'display_name' not in instance: - updates['display_name'] = "Server %s" % internal_id + updates['display_name'] = "Server %s" % instance_id instance = self.update_instance(context, instance_id, **updates) instances.append(instance) @@ -199,17 +198,16 @@ class ComputeAPI(base.Base): return self.db.instance_update(context, instance_id, kwargs) def delete_instance(self, context, instance_id): - logging.debug("Going to try and terminate %d" % instance_id) + logging.debug("Going to try and terminate %s" % instance_id) try: - instance = self.db.instance_get_by_internal_id(context, - instance_id) + instance = self.db.instance_get_by_id(context, instance_id) except exception.NotFound as e: - logging.warning("Instance %d was not found during terminate", + logging.warning("Instance %s was not found during terminate", instance_id) raise e if (instance['state_description'] == 'terminating'): - logging.warning("Instance %d is already being terminated", + logging.warning("Instance %s is already being terminated", instance_id) return @@ -264,11 +262,11 @@ class ComputeAPI(base.Base): return self.db.instance_get_all(context) def get_instance(self, context, instance_id): - return self.db.instance_get_by_internal_id(context, instance_id) + return self.db.instance_get_by_id(context, instance_id) def reboot(self, context, instance_id): """Reboot the given instance.""" - instance = self.db.instance_get_by_internal_id(context, instance_id) + instance = self.db.instance_get_by_id(context, instance_id) host = instance['host'] rpc.cast(context, self.db.queue_get_for(context, FLAGS.compute_topic, host), @@ -277,7 +275,7 @@ class ComputeAPI(base.Base): def rescue(self, context, instance_id): """Rescue the given instance.""" - instance = self.db.instance_get_by_internal_id(context, instance_id) + instance = self.db.instance_get_by_id(context, instance_id) host = instance['host'] rpc.cast(context, self.db.queue_get_for(context, FLAGS.compute_topic, host), @@ -286,7 +284,7 @@ class ComputeAPI(base.Base): def unrescue(self, context, instance_id): """Unrescue the given instance.""" - instance = self.db.instance_get_by_internal_id(context, instance_id) + instance = self.db.instance_get_by_id(context, instance_id) host = instance['host'] rpc.cast(context, self.db.queue_get_for(context, FLAGS.compute_topic, host), diff --git a/nova/db/api.py b/nova/db/api.py index 8f9dc244301c..e607ac938eea 100644 --- a/nova/db/api.py +++ b/nova/db/api.py @@ -304,9 +304,9 @@ def instance_get_floating_address(context, instance_id): return IMPL.instance_get_floating_address(context, instance_id) -def instance_get_by_internal_id(context, internal_id): - """Get an instance by internal id.""" - return IMPL.instance_get_by_internal_id(context, internal_id) +def instance_get_by_id(context, instance_id): + """Get an instance by id.""" + return IMPL.instance_get_by_id(context, instance_id) def instance_is_vpn(context, instance_id): @@ -658,7 +658,7 @@ def security_group_get_all(context): def security_group_get(context, security_group_id): - """Get security group by its internal id.""" + """Get security group by its id.""" return IMPL.security_group_get(context, security_group_id) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 55036d1d1065..0505b77a634f 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -19,7 +19,7 @@ Implementation of SQLAlchemy backend. """ -import random +import uuid import warnings from nova import db @@ -525,28 +525,18 @@ def fixed_ip_update(context, address, values): ################### -#TODO(gundlach): instance_create and volume_create are nearly identical -#and should be refactored. I expect there are other copy-and-paste -#functions between the two of them as well. @require_context def instance_create(context, values): """Create a new Instance record in the database. context - request context object values - dict containing column values. - 'internal_id' is auto-generated and should not be specified. """ instance_ref = models.Instance() instance_ref.update(values) session = get_session() with session.begin(): - while instance_ref.internal_id == None: - # Instances have integer internal ids. - internal_id = random.randint(0, 2 ** 31 - 1) - if not instance_internal_id_exists(context, internal_id, - session=session): - instance_ref.internal_id = internal_id instance_ref.save(session=session) return instance_ref @@ -652,37 +642,31 @@ def instance_get_all_by_reservation(context, reservation_id): @require_context -def instance_get_by_internal_id(context, internal_id): +def instance_get_by_id(context, instance_id): session = get_session() + if type(instance_id) is int: + instance_id = uuid.UUID(int=instance_id).hex + if is_admin_context(context): result = session.query(models.Instance).\ options(joinedload('security_groups')).\ - filter_by(internal_id=internal_id).\ + filter_by(id=instance_id).\ filter_by(deleted=can_read_deleted(context)).\ first() elif is_user_context(context): result = session.query(models.Instance).\ options(joinedload('security_groups')).\ filter_by(project_id=context.project_id).\ - filter_by(internal_id=internal_id).\ + filter_by(id=instance_id).\ filter_by(deleted=False).\ first() if not result: - raise exception.NotFound('Instance %s not found' % (internal_id)) + raise exception.NotFound('Instance %s not found' % (instance_id)) return result -@require_context -def instance_internal_id_exists(context, internal_id, session=None): - if not session: - session = get_session() - return session.query(exists().\ - where(models.Instance.internal_id == internal_id)).\ - one()[0] - - @require_context def instance_get_fixed_address(context, instance_id): session = get_session() diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index fe0a9a92162d..a0b6fff890b1 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -20,6 +20,7 @@ SQLAlchemy models for nova data. """ import datetime +import uuid from sqlalchemy.orm import relationship, backref, object_mapper from sqlalchemy import Column, Integer, String, schema @@ -39,6 +40,10 @@ FLAGS = flags.FLAGS BASE = declarative_base() +def make_uuid(): + return uuid.uuid1().hex + + class NovaBase(object): """Base class for Nova Models.""" __table_args__ = {'mysql_engine': 'InnoDB'} @@ -154,11 +159,13 @@ class Service(BASE, NovaBase): class Instance(BASE, NovaBase): """Represents a guest vm.""" __tablename__ = 'instances' - id = Column(Integer, primary_key=True) - internal_id = Column(Integer, unique=True) + id = Column(String(32), primary_key=True, default=make_uuid) + + @property + def name(self): + return "instance-%s" % self.id admin_pass = Column(String(255)) - user_id = Column(String(255)) project_id = Column(String(255)) @@ -170,10 +177,6 @@ class Instance(BASE, NovaBase): def project(self): return auth.manager.AuthManager().get_project(self.project_id) - @property - def name(self): - return "instance-%d" % self.internal_id - image_id = Column(String(255)) kernel_id = Column(String(255)) ramdisk_id = Column(String(255)) @@ -238,7 +241,7 @@ class Volume(BASE, NovaBase): host = Column(String(255)) # , ForeignKey('hosts.id')) size = Column(Integer) availability_zone = Column(String(255)) # TODO(vish): foreign key? - instance_id = Column(Integer, ForeignKey('instances.id'), nullable=True) + instance_id = Column(String(32), ForeignKey('instances.id'), nullable=True) instance = relationship(Instance, backref=backref('volumes'), foreign_keys=instance_id, @@ -311,7 +314,7 @@ class SecurityGroupInstanceAssociation(BASE, NovaBase): __tablename__ = 'security_group_instance_association' id = Column(Integer, primary_key=True) security_group_id = Column(Integer, ForeignKey('security_groups.id')) - instance_id = Column(Integer, ForeignKey('instances.id')) + instance_id = Column(String(32), ForeignKey('instances.id')) class SecurityGroup(BASE, NovaBase): @@ -431,7 +434,7 @@ class FixedIp(BASE, NovaBase): address = Column(String(255)) network_id = Column(Integer, ForeignKey('networks.id'), nullable=True) network = relationship(Network, backref=backref('fixed_ips')) - instance_id = Column(Integer, ForeignKey('instances.id'), nullable=True) + instance_id = Column(String(32), ForeignKey('instances.id'), nullable=True) instance = relationship(Instance, backref=backref('fixed_ip', uselist=False), foreign_keys=instance_id, diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 8444b6fce628..d2b0e0fc873c 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -56,8 +56,8 @@ def instance_address(context, instance_id): def stub_instance(id, user_id=1): - return Instance(id=id + 123456, state=0, image_id=10, user_id=user_id, - display_name='server%s' % id, internal_id=id) + return Instance(id=id, state=0, image_id=10, user_id=user_id, + display_name='server%s' % id) class ServersTest(unittest.TestCase): @@ -71,8 +71,7 @@ class ServersTest(unittest.TestCase): fakes.stub_out_key_pair_funcs(self.stubs) fakes.stub_out_image_service(self.stubs) self.stubs.Set(nova.db.api, 'instance_get_all', return_servers) - self.stubs.Set(nova.db.api, 'instance_get_by_internal_id', - return_server) + self.stubs.Set(nova.db.api, 'instance_get_by_id', return_server) self.stubs.Set(nova.db.api, 'instance_get_all_by_user', return_servers) self.stubs.Set(nova.db.api, 'instance_add_security_group', @@ -107,7 +106,7 @@ class ServersTest(unittest.TestCase): def test_create_instance(self): def instance_create(context, inst): - return {'id': 1, 'internal_id': 1, 'display_name': ''} + return {'id': '1', 'display_name': ''} def server_update(context, id, params): return instance_create(context, id) diff --git a/nova/tests/cloud_unittest.py b/nova/tests/cloud_unittest.py index 9886a2449b65..f63eed65ae11 100644 --- a/nova/tests/cloud_unittest.py +++ b/nova/tests/cloud_unittest.py @@ -113,7 +113,7 @@ class CloudTestCase(test.TrialTestCase): self.cloud.allocate_address(self.context) inst = db.instance_create(self.context, {}) fixed = self.network.allocate_fixed_ip(self.context, inst['id']) - ec2_id = cloud.internal_id_to_ec2_id(inst['internal_id']) + ec2_id = cloud.id_to_ec2_id(inst['id']) self.cloud.associate_address(self.context, instance_id=ec2_id, public_ip=address) @@ -289,7 +289,7 @@ class CloudTestCase(test.TrialTestCase): def test_update_of_instance_display_fields(self): inst = db.instance_create(self.context, {}) - ec2_id = cloud.internal_id_to_ec2_id(inst['internal_id']) + ec2_id = cloud.id_to_ec2_id(inst['id']) self.cloud.update_instance(self.context, ec2_id, display_name='c00l 1m4g3') inst = db.instance_get(self.context, inst['id']) From 82ccd2b656a364251aeecbf4c31cd062af6513f0 Mon Sep 17 00:00:00 2001 From: Chiradeep Vittal Date: Tue, 14 Dec 2010 16:48:44 -0800 Subject: [PATCH 16/86] remove some logging --- nova/virt/images.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/nova/virt/images.py b/nova/virt/images.py index b322f3eb2601..69838ac5b42b 100644 --- a/nova/virt/images.py +++ b/nova/virt/images.py @@ -70,7 +70,6 @@ def _fetch_image_no_curl(url, path, headers): def _fetch_s3_image(image, path, user, project): url = image_url(image) - logging.debug("About to retrieve %s and place it in %s", url, path) # This should probably move somewhere else, like e.g. a download_as # method on User objects and at the same time get rewritten to use @@ -98,7 +97,6 @@ def _fetch_s3_image(image, path, user, project): def _fetch_local_image(image, path, user, project): source = _image_path(os.path.join(image, 'image')) - logging.debug("About to copy %s to %s", source, path) if sys.platform.startswith('win'): return shutil.copy(source, path) else: From 438197264ea5ddc8bf076100586af6c71b0bf58d Mon Sep 17 00:00:00 2001 From: Eric Day Date: Mon, 27 Dec 2010 11:22:15 -0800 Subject: [PATCH 17/86] Added custom guid type so we can choose the most efficient backend DB type easily. --- nova/db/sqlalchemy/models.py | 40 ++++++++++++++++++++++++++++++++++-- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index a7725ac3c43c..7ec96bc6d3cf 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -22,12 +22,14 @@ SQLAlchemy models for nova data. import datetime import uuid -from sqlalchemy.orm import relationship, backref, object_mapper from sqlalchemy import Column, Integer, Float, String, schema from sqlalchemy import ForeignKey, DateTime, Boolean, Text +from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy.orm import relationship, backref, object_mapper from sqlalchemy.schema import ForeignKeyConstraint +from sqlalchemy.types import TypeDecorator, CHAR from nova.db.sqlalchemy.session import get_session @@ -44,6 +46,40 @@ def make_uuid(): return uuid.uuid1().hex +class GUID(TypeDecorator): + """Platform-independent GUID type. + + Uses Postgresql's UUID type, otherwise uses + CHAR(32), storing as stringified hex values. + + """ + impl = CHAR + + def load_dialect_impl(self, dialect): + if dialect.name == 'postgresql': + return dialect.type_descriptor(UUID()) + else: + return dialect.type_descriptor(CHAR(32)) + + def process_bind_param(self, value, dialect): + if value is None: + return value + elif dialect.name == 'postgresql': + return str(value) + else: + if not isinstance(value, uuid.UUID): + return "%.32x" % uuid.UUID(value) + else: + # hexstring + return "%.32x" % value + + def process_result_value(self, value, dialect): + if value is None: + return value + else: + return uuid.UUID(value).hex + + class NovaBase(object): """Base class for Nova Models.""" __table_args__ = {'mysql_engine': 'InnoDB'} @@ -169,7 +205,7 @@ class Certificate(BASE, NovaBase): class Instance(BASE, NovaBase): """Represents a guest vm.""" __tablename__ = 'instances' - id = Column(String(32), primary_key=True, default=make_uuid) + id = Column(GUID, primary_key=True, default=make_uuid) @property def name(self): From 89000675dfe321240b3dae53604ba87115a3ca3e Mon Sep 17 00:00:00 2001 From: Eric Day Date: Mon, 27 Dec 2010 11:43:17 -0800 Subject: [PATCH 18/86] Converted instance references to GUID type. --- nova/db/sqlalchemy/models.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 7ec96bc6d3cf..5634dda5e3da 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -279,7 +279,7 @@ 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')) + instance_id = Column(GUID, ForeignKey('instances.id')) memory_available = Column(Float) memory_free = Column(Float) @@ -294,7 +294,7 @@ class InstanceActions(BASE, NovaBase): """Represents a guest VM's actions and results""" __tablename__ = "instance_actions" id = Column(Integer, primary_key=True) - instance_id = Column(Integer, ForeignKey('instances.id')) + instance_id = Column(GUID, ForeignKey('instances.id')) action = Column(String(255)) error = Column(Text) @@ -312,7 +312,7 @@ class Volume(BASE, NovaBase): host = Column(String(255)) # , ForeignKey('hosts.id')) size = Column(Integer) availability_zone = Column(String(255)) # TODO(vish): foreign key? - instance_id = Column(String(32), ForeignKey('instances.id'), nullable=True) + instance_id = Column(GUID, ForeignKey('instances.id'), nullable=True) instance = relationship(Instance, backref=backref('volumes'), foreign_keys=instance_id, @@ -385,7 +385,7 @@ class SecurityGroupInstanceAssociation(BASE, NovaBase): __tablename__ = 'security_group_instance_association' id = Column(Integer, primary_key=True) security_group_id = Column(Integer, ForeignKey('security_groups.id')) - instance_id = Column(String(32), ForeignKey('instances.id')) + instance_id = Column(GUID, ForeignKey('instances.id')) class SecurityGroup(BASE, NovaBase): @@ -505,7 +505,7 @@ class FixedIp(BASE, NovaBase): address = Column(String(255)) network_id = Column(Integer, ForeignKey('networks.id'), nullable=True) network = relationship(Network, backref=backref('fixed_ips')) - instance_id = Column(String(32), ForeignKey('instances.id'), nullable=True) + instance_id = Column(GUID, ForeignKey('instances.id'), nullable=True) instance = relationship(Instance, backref=backref('fixed_ip', uselist=False), foreign_keys=instance_id, From 6debe20395d6ab476bfd2a237df8c2b08050e0e6 Mon Sep 17 00:00:00 2001 From: Eric Day Date: Mon, 27 Dec 2010 12:19:36 -0800 Subject: [PATCH 19/86] Converted Volume model and operation to use UUIDs. --- nova/api/ec2/cloud.py | 10 ++++----- nova/db/sqlalchemy/api.py | 39 ------------------------------------ nova/db/sqlalchemy/models.py | 15 +++++++------- nova/tests/test_cloud.py | 4 ++-- nova/tests/test_xenapi.py | 11 +++++----- 5 files changed, 19 insertions(+), 60 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index d50a950955f8..8c687f173d54 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -458,7 +458,7 @@ class CloudController(object): # NOTE(vish): volume_id is an optional list of volume ids to filter by. volumes = [self._format_volume(context, v) for v in volumes - if volume_id is None or v['ec2_id'] in volume_id] + if volume_id is None or v['id'] in volume_id] return {'volumeSet': volumes} @@ -471,7 +471,7 @@ class CloudController(object): instance_data = '%s[%s]' % (instance_ec2_id, volume['instance']['host']) v = {} - v['volumeId'] = volume['ec2_id'] + v['volumeId'] = volume['id'] v['status'] = volume['status'] v['size'] = volume['size'] v['availabilityZone'] = volume['availability_zone'] @@ -527,7 +527,7 @@ class CloudController(object): return {'volumeSet': [self._format_volume(context, dict(volume_ref))]} def attach_volume(self, context, volume_id, instance_id, device, **kwargs): - volume_ref = db.volume_get_by_ec2_id(context, volume_id) + volume_ref = db.volume_get(context, volume_id) if not re.match("^/dev/[a-z]d[a-z]+$", device): raise exception.ApiError(_("Invalid device specified: %s. " "Example device: /dev/vdb") % device) @@ -553,7 +553,7 @@ class CloudController(object): 'volumeId': volume_ref['id']} def detach_volume(self, context, volume_id, **kwargs): - volume_ref = db.volume_get_by_ec2_id(context, volume_id) + volume_ref = db.volume_get(context, volume_id) instance_ref = db.volume_get_instance(context.elevated(), volume_ref['id']) if not instance_ref: @@ -807,7 +807,7 @@ class CloudController(object): def delete_volume(self, context, volume_id, **kwargs): # TODO: return error if not authorized - volume_ref = db.volume_get_by_ec2_id(context, volume_id) + volume_ref = db.volume_get(context, volume_id) if volume_ref['status'] != "available": raise exception.ApiError(_("Volume status must be available")) now = datetime.datetime.utcnow() diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 57b70288c95d..50f833a5ff70 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -1285,10 +1285,6 @@ def volume_create(context, values): session = get_session() with session.begin(): - while volume_ref.ec2_id == None: - ec2_id = utils.generate_uid('vol') - if not volume_ec2_id_exists(context, ec2_id, session=session): - volume_ref.ec2_id = ec2_id volume_ref.save(session=session) return volume_ref @@ -1386,41 +1382,6 @@ def volume_get_all_by_project(context, project_id): all() -@require_context -def volume_get_by_ec2_id(context, ec2_id): - session = get_session() - result = None - - if is_admin_context(context): - result = session.query(models.Volume).\ - filter_by(ec2_id=ec2_id).\ - filter_by(deleted=can_read_deleted(context)).\ - first() - elif is_user_context(context): - result = session.query(models.Volume).\ - filter_by(project_id=context.project_id).\ - filter_by(ec2_id=ec2_id).\ - filter_by(deleted=False).\ - first() - else: - raise exception.NotAuthorized() - - if not result: - raise exception.NotFound(_('Volume %s not found') % ec2_id) - - return result - - -@require_context -def volume_ec2_id_exists(context, ec2_id, session=None): - if not session: - session = get_session() - - return session.query(exists().\ - where(models.Volume.id == ec2_id)).\ - one()[0] - - @require_admin_context def volume_get_instance(context, volume_id): session = get_session() diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 5634dda5e3da..418c8914e1c4 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -303,8 +303,11 @@ class InstanceActions(BASE, NovaBase): class Volume(BASE, NovaBase): """Represents a block storage device that can be attached to a vm.""" __tablename__ = 'volumes' - id = Column(Integer, primary_key=True) - ec2_id = Column(String(12), unique=True) + id = Column(GUID, primary_key=True, default=make_uuid) + + @property + def name(self): + return "vollume-%s" % self.id user_id = Column(String(255)) project_id = Column(String(255)) @@ -330,10 +333,6 @@ class Volume(BASE, NovaBase): display_name = Column(String(255)) display_description = Column(String(255)) - @property - def name(self): - return self.ec2_id - class Quota(BASE, NovaBase): """Represents quota overrides for a project.""" @@ -357,7 +356,7 @@ class ExportDevice(BASE, NovaBase): id = Column(Integer, primary_key=True) shelf_id = Column(Integer) blade_id = Column(Integer) - volume_id = Column(Integer, ForeignKey('volumes.id'), nullable=True) + volume_id = Column(GUID, ForeignKey('volumes.id'), nullable=True) volume = relationship(Volume, backref=backref('export_device', uselist=False), foreign_keys=volume_id, @@ -373,7 +372,7 @@ class IscsiTarget(BASE, NovaBase): id = Column(Integer, primary_key=True) target_num = Column(Integer) host = Column(String(255)) - volume_id = Column(Integer, ForeignKey('volumes.id'), nullable=True) + volume_id = Column(GUID, ForeignKey('volumes.id'), nullable=True) volume = relationship(Volume, backref=backref('iscsi_target', uselist=False), foreign_keys=volume_id, diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index ca400077a813..42344af1cb06 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -127,9 +127,9 @@ class CloudTestCase(test.TestCase): result = self.cloud.describe_volumes(self.context) self.assertEqual(len(result['volumeSet']), 2) result = self.cloud.describe_volumes(self.context, - volume_id=[vol2['ec2_id']]) + volume_id=[vol2['id']]) self.assertEqual(len(result['volumeSet']), 1) - self.assertEqual(result['volumeSet'][0]['volumeId'], vol2['ec2_id']) + self.assertEqual(result['volumeSet'][0]['volumeId'], vol2['id']) db.volume_destroy(self.context, vol1['id']) db.volume_destroy(self.context, vol2['id']) diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index ed2e4ffde717..900b9af2bcac 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -79,8 +79,8 @@ class XenAPIVolumeTestCase(test.TestCase): helper = volume_utils.VolumeHelper helper.XenAPI = session.get_imported_xenapi() vol = self._create_volume() - info = helper.parse_volume_info(vol['ec2_id'], '/dev/sdc') - label = 'SR-%s' % vol['ec2_id'] + info = helper.parse_volume_info(vol['id'], '/dev/sdc') + label = 'SR-%s' % vol['id'] description = 'Test-SR' sr_ref = helper.create_iscsi_storage(session, info, label, description) srs = fake.get_all('SR') @@ -97,7 +97,7 @@ class XenAPIVolumeTestCase(test.TestCase): # oops, wrong mount point! self.assertRaises(volume_utils.StorageError, helper.parse_volume_info, - vol['ec2_id'], + vol['id'], '/dev/sd') db.volume_destroy(context.get_admin_context(), vol['id']) @@ -108,8 +108,7 @@ class XenAPIVolumeTestCase(test.TestCase): volume = self._create_volume() instance = db.instance_create(self.values) fake.create_vm(instance.name, 'Running') - result = conn.attach_volume(instance.name, volume['ec2_id'], - '/dev/sdc') + result = conn.attach_volume(instance.name, volume['id'], '/dev/sdc') def check(): # check that the VM has a VBD attached to it @@ -134,7 +133,7 @@ class XenAPIVolumeTestCase(test.TestCase): self.assertRaises(Exception, conn.attach_volume, instance.name, - volume['ec2_id'], + volume['id'], '/dev/sdc') def tearDown(self): From 8aea573bd2e44e152fb4ef1627640bab1818dede Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Tue, 28 Dec 2010 23:55:58 -0600 Subject: [PATCH 20/86] initial lock functionality commit --- nova/api/openstack/__init__.py | 73 +++++++++++++++++++++++++++++ nova/api/openstack/servers.py | 86 ++++++++++++++++++++++++++++++++++ nova/compute/api.py | 35 +++++++++++++- nova/compute/manager.py | 24 ++++++++++ nova/db/sqlalchemy/api.py | 22 +++++++++ nova/db/sqlalchemy/models.py | 2 + 6 files changed, 241 insertions(+), 1 deletion(-) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index bebcdc18cda4..b3bb65550539 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -22,6 +22,7 @@ WSGI middleware for OpenStack API controllers. import json import time +import functools import logging import routes @@ -113,3 +114,75 @@ class APIRouter(wsgi.Router): controller=sharedipgroups.Controller()) super(APIRouter, self).__init__(mapper) + + +#class CheckLock(object): +# """ +# decorator used for preventing action against locked instances +# unless, of course, you happen to be admin +# +# """ +# def __init__(self, function): +# self.function = function +# +# def __getattribute__(self, attr): +# if attr == "function": +# return super(CheckLock, self).__getattribute__(attr) +# return self.function.__getattribute__(attr) +# +# def __call__(self, *args, **kwargs): +# logging.info(_("Calling %s. Checking locks and privileges"), +# self.function.__name__) +# +# # get req +# if 'req' is in kwargs: +# req = kwargs['req'] +# else: +# req = args[1] +# +# # check table for lock +# locked = True +# if(locked): +# # check context for admin +# if(req.environ['nova.context'].is_admin): +# self.function(*args, **kwargs) +# else: +# pass +# # return 404 +# +# def __get__(self, obj, objtype): +# f = functools.partial(self.__call__, obj) +# f.__doc__ = self.function.__doc__ +# return f + + + + +#def checks_lock(function): +# """ +# decorator used for preventing action against locked instances +# unless, of course, you happen to be admin +# +# """ +# +# @functools.wraps(function) +# def decorated_function(*args, **kwargs): +# +# # check table for lock +# locked = True +# if(locked): +# try: +# # get context from req and check for admin +# if 'req' is in kwargs: +# req = kwargs['req'] +# else: +# req = args[1] +# if(req.environ['nova.context'].is_admin): +# function(*args, **kwargs) +# else: +# pass +# # return 404 +# except: +# logging.error(_("CheckLock: error getting context")) +# +# return decorated_function diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 10c3973843f0..46e65ca83b71 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -35,6 +35,40 @@ LOG = logging.getLogger('server') LOG.setLevel(logging.DEBUG) +def checks_lock(function): + """ + decorator used for preventing action against locked instances + unless, of course, you happen to be admin + + """ + + @functools.wraps(function) + def decorated_function(*args, **kwargs): + + # grab args to function + try: + if 'req' is in kwargs: + req = kwargs['req'] + else: + req = args[1] + if 'id' is in kwargs: + _id = kwargs['id'] + else: + req = args[2] + context = req.environ['nova.context'] + except: + logging.error(_("CheckLock: argument error")) + + # if locked and admin call function, otherwise 404 + if(compute_api.ComputeAPI().get_lock(context, _id)): + if(req.environ['nova.context'].is_admin): + function(*args, **kwargs) + # return 404 + return faults.Fault(exc.HTTPUnprocessableEntity()) + + return decorated_function + + def _entity_list(entities): """ Coerces a list of servers into proper dictionary format """ return dict(servers=entities) @@ -104,6 +138,7 @@ class Controller(wsgi.Controller): res = [entity_maker(inst)['server'] for inst in limited_list] return _entity_list(res) + @checks_lock def show(self, req, id): """ Returns server details by server id """ try: @@ -113,6 +148,7 @@ class Controller(wsgi.Controller): except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) + @checks_lock def delete(self, req, id): """ Destroys a server """ try: @@ -140,6 +176,7 @@ class Controller(wsgi.Controller): key_data=key_pair['public_key']) return _entity_inst(instances[0]) + @checks_lock def update(self, req, id): """ Updates the server name or password """ inst_dict = self._deserialize(req.body, req) @@ -160,6 +197,7 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPNotFound()) return exc.HTTPNoContent() + @checks_lock def action(self, req, id): """ Multi-purpose method used to reboot, rebuild, and resize a server """ @@ -176,6 +214,51 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() + def lock(self, req, id): + """ + lock the instance with id + admin only operation + + """ + context = req.environ['nova.context'] + try: + self.compute_api.lock(context, id) + except: + readable = traceback.format_exc() + logging.error(_("Compute.api::lock %s"), readable) + return faults.Fault(exc.HTTPUnprocessableEntity()) + return exc.HTTPAccepted() + + def unlock(self, req, id): + """ + unlock the instance with id + admin only operation + + """ + context = req.environ['nova.context'] + try: + self.compute_api.unlock(context, id) + except: + readable = traceback.format_exc() + logging.error(_("Compute.api::unlock %s"), readable) + return faults.Fault(exc.HTTPUnprocessableEntity()) + return exc.HTTPAccepted() + + def get_lock(self, req, id): + """ + return the boolean state of (instance with id)'s lock + + """ + context = req.environ['nova.context'] + try: + self.compute_api.get_lock(context, id) + except: + readable = traceback.format_exc() + logging.error(_("Compute.api::get_lock %s"), readable) + return faults.Fault(exc.HTTPUnprocessableEntity()) + return exc.HTTPAccepted() + + @checks_lock def pause(self, req, id): """ Permit Admins to Pause the server. """ ctxt = req.environ['nova.context'] @@ -187,6 +270,7 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() + @checks_lock def unpause(self, req, id): """ Permit Admins to Unpause the server. """ ctxt = req.environ['nova.context'] @@ -198,6 +282,7 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() + @checks_lock def suspend(self, req, id): """permit admins to suspend the server""" context = req.environ['nova.context'] @@ -209,6 +294,7 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() + @checks_lock def resume(self, req, id): """permit admins to resume the server from suspend""" context = req.environ['nova.context'] diff --git a/nova/compute/api.py b/nova/compute/api.py index a47703461a76..361ab991418f 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -141,7 +141,8 @@ class ComputeAPI(base.Base): 'display_description': description, 'user_data': user_data or '', 'key_name': key_name, - 'key_data': key_data} + 'key_data': key_data, + 'locked': False} elevated = context.elevated() instances = [] @@ -319,3 +320,35 @@ class ComputeAPI(base.Base): self.db.queue_get_for(context, FLAGS.compute_topic, host), {"method": "unrescue_instance", "args": {"instance_id": instance['id']}}) + + def lock(self, context, instance_id): + """ + lock the instance with instance_id + + """ + instance = self.get_instance(context, instance_id) + host = instance['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "lock_instance", + "args": {"instance_id": instance['id']}}) + + def unlock(self, context, instance_id): + """ + unlock the instance with instance_id + + """ + instance = self.get_instance(context, instance_id) + host = instance['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "unlock_instance", + "args": {"instance_id": instance['id']}}) + + def get_lock(self, context, instance_id): + """ + return the boolean state of (instance with instance_id)'s lock + + """ + instance = self.get_instance(context, instance_id) + return instance['locked'] diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 70b175e7c682..05f1e44a2265 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -329,6 +329,30 @@ class ComputeManager(manager.Manager): instance_id, result)) + @exception.wrap_exception + def lock_instance(self, context, instance_id): + """ + lock the instance with instance_id + + """ + context = context.elevated() + instance_ref = self.db.instance_get(context, instance_id) + + logging.debug(_('instance %s: locking'), instance_ref['internal_id']) + self.db.instance_set_lock(context, instance_id, True) + + @exception.wrap_exception + def unlock_instance(self, context, instance_id): + """ + unlock the instance with instance_id + + """ + context = context.elevated() + instance_ref = self.db.instance_get(context, instance_id) + + logging.debug(_('instance %s: unlocking'), instance_ref['internal_id']) + self.db.instance_set_lock(context, instance_id, False) + @exception.wrap_exception def get_console_output(self, context, instance_id): """Send the console output for an instance.""" diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 7e945e4cb3d8..6d774b39ce18 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -856,6 +856,28 @@ def instance_action_create(context, values): return action_ref +@require_admin_context +def instance_set_lock(context, instance_id, lock): + """ + twiddle the locked bit in the db + lock is a boolean + + """ + db.instance_update(context, + instance_id, + {'locked': lock}) + + +#@require_admin_context +#def instance_is_locked(context, instance_id): +# """ +# return the boolean state of (instance with instance_id)'s lock +# +# """ +# instance_ref = instance_get(context, instance_id) +# return instance_ref['locked'] + + ################### diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 693db8d23d97..830ef30a16f5 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -224,6 +224,8 @@ class Instance(BASE, NovaBase): display_name = Column(String(255)) display_description = Column(String(255)) + locked = Column(Boolean) + # TODO(vish): see Ewan's email about state improvements, probably # should be in a driver base class or some such # vmstate_state = running, halted, suspended, paused From 3a85ba4fa4215737731b2e755abfa350c509e46f Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 29 Dec 2010 13:04:41 -0600 Subject: [PATCH 21/86] syntax error --- nova/api/openstack/servers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 46e65ca83b71..7744815fc3ad 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -47,11 +47,11 @@ def checks_lock(function): # grab args to function try: - if 'req' is in kwargs: + if 'req' in kwargs: req = kwargs['req'] else: req = args[1] - if 'id' is in kwargs: + if 'id' in kwargs: _id = kwargs['id'] else: req = args[2] From b6e5c68d65701b840006cea49367879ee88c9b80 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 29 Dec 2010 13:09:49 -0600 Subject: [PATCH 22/86] forgot import --- nova/api/openstack/__init__.py | 1 - nova/api/openstack/servers.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index b3bb65550539..c0bd37fef5b1 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -22,7 +22,6 @@ WSGI middleware for OpenStack API controllers. import json import time -import functools import logging import routes diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 7744815fc3ad..8b837e6fc392 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -17,6 +17,7 @@ import logging import traceback +import functools from webob import exc From 0afb4a06dcb94ae41d04b3d78304746b0cc5b26f Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 29 Dec 2010 13:33:51 -0600 Subject: [PATCH 23/86] refactor --- nova/api/openstack/servers.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 8b837e6fc392..292a664b7ff1 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -58,14 +58,15 @@ def checks_lock(function): req = args[2] context = req.environ['nova.context'] except: - logging.error(_("CheckLock: argument error")) + logging.error(_("CheckLock: argument error: |%s|, |%s|"), args, + kwargs) + # if admin or unlocked call function, otherwise 404 + locked = compute_api.ComputeAPI().get_lock(context, _id) + admin = req.environ['nova.context'].is_admin + if(admin or not locked): + return function(*args, **kwargs) - # if locked and admin call function, otherwise 404 - if(compute_api.ComputeAPI().get_lock(context, _id)): - if(req.environ['nova.context'].is_admin): - function(*args, **kwargs) - # return 404 - return faults.Fault(exc.HTTPUnprocessableEntity()) + return faults.Fault(exc.HTTPNotFound()) return decorated_function From 2eaf3bb2a9d54bb7dd2c518cecca0caf7c80571f Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 29 Dec 2010 13:50:25 -0600 Subject: [PATCH 24/86] added test for lock to os api --- nova/tests/api/openstack/test_servers.py | 44 ++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 5d23db588863..464bae231b54 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -321,5 +321,49 @@ class ServersTest(unittest.TestCase): self.assertEqual(self.server_delete_called, True) + def test_lock(self): + # part one: stubs it to be locked and test pause + def get_locked(context, id): + return True + + # set get to return locked + self.stubs.Set(nova.compute, 'get_lock', get_locked) + + # attempt to pause + FLAGS.allow_admin_api = True + body = dict(server=dict( + name='server_test', imageId=2, flavorId=2, metadata={}, + personality={})) + req = webob.Request.blank('/v1.0/servers/1/pause') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(nova.api.API('os')) + + # expect a 404 since it was locked + self.assertEqual(res.status_int, 404) + + # Part two: stubs it to be unlocked and test pause + def get_unlocked(context, id): + return False + + # set get to return locked + self.stubs.Set(nova.compute, 'get_lock', get_unlocked) + + # attempt to pause + FLAGS.allow_admin_api = True + body = dict(server=dict( + name='server_test', imageId=2, flavorId=2, metadata={}, + personality={})) + req = webob.Request.blank('/v1.0/servers/1/pause') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + res = req.get_response(nova.api.API('os')) + + # expect a 202 since it was unlocked + self.assertEqual(res.status_int, 202) + + if __name__ == "__main__": unittest.main() From 48f0aa891c9c82c1c9e7a2e4bc1bef4da3c4d90b Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 29 Dec 2010 14:30:29 -0600 Subject: [PATCH 25/86] fixed up test for lock --- nova/tests/api/openstack/test_servers.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 464bae231b54..b2a8e5ac0795 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -323,11 +323,11 @@ class ServersTest(unittest.TestCase): def test_lock(self): # part one: stubs it to be locked and test pause - def get_locked(context, id): + def get_locked(self, context, id): return True # set get to return locked - self.stubs.Set(nova.compute, 'get_lock', get_locked) + self.stubs.Set(nova.compute.api.ComputeAPI, 'get_lock', get_locked) # attempt to pause FLAGS.allow_admin_api = True @@ -344,11 +344,11 @@ class ServersTest(unittest.TestCase): self.assertEqual(res.status_int, 404) # Part two: stubs it to be unlocked and test pause - def get_unlocked(context, id): + def get_unlocked(self, context, id): return False # set get to return locked - self.stubs.Set(nova.compute, 'get_lock', get_unlocked) + self.stubs.Set(nova.compute.api.ComputeAPI, 'get_lock', get_unlocked) # attempt to pause FLAGS.allow_admin_api = True From 9b9b5fed18231a800018bc60fa653ec521b34a5c Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 29 Dec 2010 14:32:03 -0600 Subject: [PATCH 26/86] pep8 --- nova/tests/api/openstack/test_servers.py | 1 - 1 file changed, 1 deletion(-) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index b2a8e5ac0795..a122f394644b 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -320,7 +320,6 @@ class ServersTest(unittest.TestCase): self.assertEqual(res.status, '202 Accepted') self.assertEqual(self.server_delete_called, True) - def test_lock(self): # part one: stubs it to be locked and test pause def get_locked(self, context, id): From 6202b21b42615cf15b0dd60089026472e6836c69 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 29 Dec 2010 15:31:53 -0600 Subject: [PATCH 27/86] removed some code i didn't end up using --- nova/api/openstack/__init__.py | 72 ---------------------------------- nova/db/sqlalchemy/api.py | 10 ----- 2 files changed, 82 deletions(-) diff --git a/nova/api/openstack/__init__.py b/nova/api/openstack/__init__.py index 7cceb7733ba7..66aceee2d06c 100644 --- a/nova/api/openstack/__init__.py +++ b/nova/api/openstack/__init__.py @@ -113,75 +113,3 @@ class APIRouter(wsgi.Router): controller=sharedipgroups.Controller()) super(APIRouter, self).__init__(mapper) - - -#class CheckLock(object): -# """ -# decorator used for preventing action against locked instances -# unless, of course, you happen to be admin -# -# """ -# def __init__(self, function): -# self.function = function -# -# def __getattribute__(self, attr): -# if attr == "function": -# return super(CheckLock, self).__getattribute__(attr) -# return self.function.__getattribute__(attr) -# -# def __call__(self, *args, **kwargs): -# logging.info(_("Calling %s. Checking locks and privileges"), -# self.function.__name__) -# -# # get req -# if 'req' is in kwargs: -# req = kwargs['req'] -# else: -# req = args[1] -# -# # check table for lock -# locked = True -# if(locked): -# # check context for admin -# if(req.environ['nova.context'].is_admin): -# self.function(*args, **kwargs) -# else: -# pass -# # return 404 -# -# def __get__(self, obj, objtype): -# f = functools.partial(self.__call__, obj) -# f.__doc__ = self.function.__doc__ -# return f - - - - -#def checks_lock(function): -# """ -# decorator used for preventing action against locked instances -# unless, of course, you happen to be admin -# -# """ -# -# @functools.wraps(function) -# def decorated_function(*args, **kwargs): -# -# # check table for lock -# locked = True -# if(locked): -# try: -# # get context from req and check for admin -# if 'req' is in kwargs: -# req = kwargs['req'] -# else: -# req = args[1] -# if(req.environ['nova.context'].is_admin): -# function(*args, **kwargs) -# else: -# pass -# # return 404 -# except: -# logging.error(_("CheckLock: error getting context")) -# -# return decorated_function diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 6d774b39ce18..ca71df7b3968 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -868,16 +868,6 @@ def instance_set_lock(context, instance_id, lock): {'locked': lock}) -#@require_admin_context -#def instance_is_locked(context, instance_id): -# """ -# return the boolean state of (instance with instance_id)'s lock -# -# """ -# instance_ref = instance_get(context, instance_id) -# return instance_ref['locked'] - - ################### From aac25e8cc6e75d5d0abc41a8cf979300e58bcc3b Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 29 Dec 2010 17:04:40 -0600 Subject: [PATCH 28/86] removed () from if (can't believe i did that) and renamed checks_lock decorator --- nova/api/openstack/servers.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index c7263273c7ae..74b4f55b5480 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -36,7 +36,7 @@ LOG = logging.getLogger('server') LOG.setLevel(logging.DEBUG) -def checks_lock(function): +def checks_instance_lock(function): """ decorator used for preventing action against locked instances unless, of course, you happen to be admin @@ -58,12 +58,12 @@ def checks_lock(function): req = args[2] context = req.environ['nova.context'] except: - logging.error(_("CheckLock: argument error: |%s|, |%s|"), args, + logging.error(_("check_lock: argument error: |%s|, |%s|"), args, kwargs) # if admin or unlocked call function, otherwise 404 locked = compute_api.ComputeAPI().get_lock(context, _id) admin = req.environ['nova.context'].is_admin - if(admin or not locked): + if admin or not locked: return function(*args, **kwargs) return faults.Fault(exc.HTTPNotFound()) @@ -138,7 +138,7 @@ class Controller(wsgi.Controller): res = [entity_maker(inst)['server'] for inst in limited_list] return dict(servers=res) - @checks_lock + @checks_instance_lock def show(self, req, id): """ Returns server details by server id """ try: @@ -148,7 +148,7 @@ class Controller(wsgi.Controller): except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - @checks_lock + @checks_instance_lock def delete(self, req, id): """ Destroys a server """ try: @@ -176,7 +176,7 @@ class Controller(wsgi.Controller): key_data=key_pair['public_key']) return _translate_keys(instances[0]) - @checks_lock + @checks_instance_lock def update(self, req, id): """ Updates the server name or password """ inst_dict = self._deserialize(req.body, req) @@ -198,7 +198,7 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPNotFound()) return exc.HTTPNoContent() - @checks_lock + @checks_instance_lock def action(self, req, id): """ Multi-purpose method used to reboot, rebuild, and resize a server """ @@ -259,7 +259,7 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() - @checks_lock + @checks_instance_lock def pause(self, req, id): """ Permit Admins to Pause the server. """ ctxt = req.environ['nova.context'] @@ -271,7 +271,7 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() - @checks_lock + @checks_instance_lock def unpause(self, req, id): """ Permit Admins to Unpause the server. """ ctxt = req.environ['nova.context'] @@ -283,7 +283,7 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() - @checks_lock + @checks_instance_lock def suspend(self, req, id): """permit admins to suspend the server""" context = req.environ['nova.context'] @@ -295,7 +295,7 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() - @checks_lock + @checks_instance_lock def resume(self, req, id): """permit admins to resume the server from suspend""" context = req.environ['nova.context'] From be6750a77e5121fe8f0d95016da4e96c9de3b5aa Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 29 Dec 2010 17:40:18 -0600 Subject: [PATCH 29/86] removed lock check from show and changed returning 404 to 405 --- nova/api/openstack/servers.py | 3 +-- nova/tests/api/openstack/test_servers.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 74b4f55b5480..24fd5000ca89 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -66,7 +66,7 @@ def checks_instance_lock(function): if admin or not locked: return function(*args, **kwargs) - return faults.Fault(exc.HTTPNotFound()) + return faults.Fault(exc.HTTPMethodNotAllowed()) return decorated_function @@ -138,7 +138,6 @@ class Controller(wsgi.Controller): res = [entity_maker(inst)['server'] for inst in limited_list] return dict(servers=res) - @checks_instance_lock def show(self, req, id): """ Returns server details by server id """ try: diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index a122f394644b..56a5a9b275ce 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -340,7 +340,7 @@ class ServersTest(unittest.TestCase): res = req.get_response(nova.api.API('os')) # expect a 404 since it was locked - self.assertEqual(res.status_int, 404) + self.assertEqual(res.status_int, 405) # Part two: stubs it to be unlocked and test pause def get_unlocked(self, context, id): From 64078137ce12ee52fff710f5a262d57b4ace2809 Mon Sep 17 00:00:00 2001 From: Eric Day Date: Wed, 29 Dec 2010 16:29:15 -0800 Subject: [PATCH 30/86] Moved ec2 volume operations into a volume API interface for other components to use. Added attach/detach as compute.api methods, since they operate in the context of instances (and to avoid a dependency loop). --- nova/api/ec2/cloud.py | 147 +++++++++++----------------------------- nova/compute/api.py | 34 +++++++++- nova/volume/__init__.py | 91 ++++++++++++++++++++++--- 3 files changed, 152 insertions(+), 120 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 8c687f173d54..74c73e0ddb3d 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -38,12 +38,12 @@ from nova import flags from nova import quota from nova import rpc from nova import utils +from nova import volume from nova.compute import api as compute_api from nova.compute import instance_types FLAGS = flags.FLAGS -flags.DECLARE('storage_availability_zone', 'nova.volume.manager') InvalidInputException = exception.InvalidInputException @@ -89,8 +89,10 @@ class CloudController(object): def __init__(self): self.network_manager = utils.import_object(FLAGS.network_manager) self.image_service = utils.import_object(FLAGS.image_service) + self.volume_api = volume.API() self.compute_api = compute_api.ComputeAPI(self.network_manager, - self.image_service) + self.image_service, + self.volume_api) self.setup() def __str__(self): @@ -451,15 +453,10 @@ class CloudController(object): "output": base64.b64encode(output)} def describe_volumes(self, context, volume_id=None, **kwargs): - if context.user.is_admin(): - volumes = db.volume_get_all(context) - else: - volumes = db.volume_get_all_by_project(context, context.project_id) - + volumes = self.volume_api.get(context) # NOTE(vish): volume_id is an optional list of volume ids to filter by. volumes = [self._format_volume(context, v) for v in volumes if volume_id is None or v['id'] in volume_id] - return {'volumeSet': volumes} def _format_volume(self, context, volume): @@ -498,95 +495,17 @@ class CloudController(object): return v def create_volume(self, context, size, **kwargs): - # check quota - if quota.allowed_volumes(context, 1, size) < 1: - logging.warn("Quota exceeeded for %s, tried to create %sG volume", - context.project_id, size) - raise quota.QuotaError("Volume quota exceeded. You cannot " - "create a volume of size %s" % size) - vol = {} - vol['size'] = size - vol['user_id'] = context.user.id - vol['project_id'] = context.project_id - vol['availability_zone'] = FLAGS.storage_availability_zone - vol['status'] = "creating" - vol['attach_status'] = "detached" - vol['display_name'] = kwargs.get('display_name') - vol['display_description'] = kwargs.get('display_description') - volume_ref = db.volume_create(context, vol) - - rpc.cast(context, - FLAGS.scheduler_topic, - {"method": "create_volume", - "args": {"topic": FLAGS.volume_topic, - "volume_id": volume_ref['id']}}) - + volume = self.volume_api.create(context, size, + kwargs.get('display_name'), + kwargs.get('display_description')) # TODO(vish): Instance should be None at db layer instead of # trying to lazy load, but for now we turn it into # a dict to avoid an error. return {'volumeSet': [self._format_volume(context, dict(volume_ref))]} - def attach_volume(self, context, volume_id, instance_id, device, **kwargs): - volume_ref = db.volume_get(context, volume_id) - if not re.match("^/dev/[a-z]d[a-z]+$", device): - raise exception.ApiError(_("Invalid device specified: %s. " - "Example device: /dev/vdb") % device) - # TODO(vish): abstract status checking? - if volume_ref['status'] != "available": - raise exception.ApiError(_("Volume status must be available")) - if volume_ref['attach_status'] == "attached": - raise exception.ApiError(_("Volume is already attached")) - instance_id = ec2_id_to_id(instance_id) - instance_ref = self.compute_api.get_instance(context, instance_id) - host = instance_ref['host'] - rpc.cast(context, - db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "attach_volume", - "args": {"volume_id": volume_ref['id'], - "instance_id": instance_ref['id'], - "mountpoint": device}}) - return {'attachTime': volume_ref['attach_time'], - 'device': volume_ref['mountpoint'], - 'instanceId': instance_ref['id'], - 'requestId': context.request_id, - 'status': volume_ref['attach_status'], - 'volumeId': volume_ref['id']} - - def detach_volume(self, context, volume_id, **kwargs): - volume_ref = db.volume_get(context, volume_id) - instance_ref = db.volume_get_instance(context.elevated(), - volume_ref['id']) - if not instance_ref: - raise exception.ApiError(_("Volume isn't attached to anything!")) - # TODO(vish): abstract status checking? - if volume_ref['status'] == "available": - raise exception.ApiError(_("Volume is already detached")) - try: - host = instance_ref['host'] - rpc.cast(context, - db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "detach_volume", - "args": {"instance_id": instance_ref['id'], - "volume_id": volume_ref['id']}}) - except exception.NotFound: - # If the instance doesn't exist anymore, - # then we need to call detach blind - db.volume_detached(context) - instance_id = instance_ref['id'] - ec2_id = id_to_ec2_id(instance_id) - return {'attachTime': volume_ref['attach_time'], - 'device': volume_ref['mountpoint'], - 'instanceId': instance_id, - 'requestId': context.request_id, - 'status': volume_ref['attach_status'], - 'volumeId': volume_ref['id']} - - def _convert_to_set(self, lst, label): - if lst == None or lst == []: - return None - if not isinstance(lst, list): - lst = [lst] - return [{label: x} for x in lst] + def delete_volume(self, context, volume_id, **kwargs): + self.volume_api.delete(context, volume_id) + return True def update_volume(self, context, volume_id, **kwargs): updatable_fields = ['display_name', 'display_description'] @@ -595,9 +514,36 @@ class CloudController(object): if field in kwargs: changes[field] = kwargs[field] if changes: - db.volume_update(context, volume_id, kwargs) + self.volume_api.update(context, volume_id, kwargs) return True + def attach_volume(self, context, volume_id, instance_id, device, **kwargs): + self.compute_api.attach_volume(context, instance_id, volume_id, device) + volume = self.volume_api.get(context, volume_id) + return {'attachTime': volume['attach_time'], + 'device': volume['mountpoint'], + 'instanceId': instance_id, + 'requestId': context.request_id, + 'status': volume['attach_status'], + 'volumeId': volume_id} + + def detach_volume(self, context, volume_id, **kwargs): + volume = self.volume_api.get(context, volume_id) + instance = self.compute_api.detach_volume(context, volume_id) + return {'attachTime': volume['attach_time'], + 'device': volume['mountpoint'], + 'instanceId': id_to_ec2_id(instance['id']), + 'requestId': context.request_id, + 'status': volume['attach_status'], + 'volumeId': volume_id} + + def _convert_to_set(self, lst, label): + if lst == None or lst == []: + return None + if not isinstance(lst, list): + lst = [lst] + return [{label: x} for x in lst] + def describe_instances(self, context, **kwargs): return self._format_describe_instances(context) @@ -805,21 +751,6 @@ class CloudController(object): db.instance_update(context, inst['id'], kwargs) return True - def delete_volume(self, context, volume_id, **kwargs): - # TODO: return error if not authorized - volume_ref = db.volume_get(context, volume_id) - if volume_ref['status'] != "available": - raise exception.ApiError(_("Volume status must be available")) - now = datetime.datetime.utcnow() - db.volume_update(context, volume_ref['id'], {'status': 'deleting', - 'terminated_at': now}) - host = volume_ref['host'] - rpc.cast(context, - db.queue_get_for(context, FLAGS.volume_topic, host), - {"method": "delete_volume", - "args": {"volume_id": volume_ref['id']}}) - return True - def describe_images(self, context, image_id=None, **kwargs): # Note: image_id is a list! images = self.image_service.index(context) diff --git a/nova/compute/api.py b/nova/compute/api.py index 28af434e3f1f..870fcdbe4da1 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -30,6 +30,7 @@ from nova import flags from nova import quota from nova import rpc from nova import utils +from nova import volume from nova.compute import instance_types from nova.db import base @@ -44,13 +45,17 @@ def generate_default_hostname(instance_id): class ComputeAPI(base.Base): """API for interacting with the compute manager.""" - def __init__(self, network_manager=None, image_service=None, **kwargs): + def __init__(self, network_manager=None, image_service=None, + volume_api=None, **kwargs): if not network_manager: network_manager = utils.import_object(FLAGS.network_manager) self.network_manager = network_manager if not image_service: image_service = utils.import_object(FLAGS.image_service) self.image_service = image_service + if not volume_api: + volume_api = volume.API() + self.volume_api = volume_api super(ComputeAPI, self).__init__(**kwargs) def get_network_topic(self, context, instance_id): @@ -298,3 +303,30 @@ class ComputeAPI(base.Base): self.db.queue_get_for(context, FLAGS.compute_topic, host), {"method": "unrescue_instance", "args": {"instance_id": instance['id']}}) + + def attach_volume(self, context, instance_id, volume_id, device): + if not re.match("^/dev/[a-z]d[a-z]+$", device): + raise exception.ApiError(_("Invalid device specified: %s. " + "Example device: /dev/vdb") % device) + self.volume_api.check_attach(context, volume_id) + instance = self.get_instance(context, instance_id) + host = instance['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "attach_volume", + "args": {"volume_id": volume_id, + "instance_id": instance_id, + "mountpoint": device}}) + + def detach_volume(self, context, volume_id): + instance = self.db.volume_get_instance(context.elevated(), volume_id) + if not instance: + raise exception.ApiError(_("Volume isn't attached to anything!")) + self.volume_api.check_detach(context, volume_id) + host = instance['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "detach_volume", + "args": {"instance_id": instance['id'], + "volume_id": volume_id}}) + return instance diff --git a/nova/volume/__init__.py b/nova/volume/__init__.py index d6e944fc0bdf..48ecdbe68b17 100644 --- a/nova/volume/__init__.py +++ b/nova/volume/__init__.py @@ -17,15 +17,84 @@ # under the License. """ -:mod:`nova.volume` -- Nova Block Storage -===================================================== - -.. automodule:: nova.volume - :platform: Unix -.. moduleauthor:: Jesse Andrews -.. moduleauthor:: Devin Carlen -.. moduleauthor:: Vishvananda Ishaya -.. moduleauthor:: Joshua McKenty -.. moduleauthor:: Manish Singh -.. moduleauthor:: Andy Smith +Handles all requests relating to volumes. """ + +import datetime +import logging + +from nova import db +from nova import exception +from nova import flags +from nova import quota +from nova import rpc +from nova.db import base + +FLAGS = flags.FLAGS +flags.DECLARE('storage_availability_zone', 'nova.volume.manager') + + +class API(base.Base): + """API for interacting with the volume manager.""" + + def create(self, context, size, name, description): + if quota.allowed_volumes(context, 1, size) < 1: + logging.warn("Quota exceeeded for %s, tried to create %sG volume", + context.project_id, size) + raise quota.QuotaError("Volume quota exceeded. You cannot " + "create a volume of size %s" % size) + + options = { + 'size': size, + 'user_id': context.user.id, + 'project_id': context.project_id, + 'availability_zone': FLAGS.storage_availability_zone, + 'status': "creating", + 'attach_status': "detached", + 'display_name': name, + 'display_description': description} + + volume = self.db.volume_create(context, options) + rpc.cast(context, + FLAGS.scheduler_topic, + {"method": "create_volume", + "args": {"topic": FLAGS.volume_topic, + "volume_id": volume['id']}}) + return volume + + def delete(self, context, volume_id): + volume = self.db.volume_get(context, volume_id) + if volume['status'] != "available": + raise exception.ApiError(_("Volume status must be available")) + now = datetime.datetime.utcnow() + self.db.volume_update(context, volume_id, {'status': 'deleting', + 'terminated_at': now}) + host = volume['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.volume_topic, host), + {"method": "delete_volume", + "args": {"volume_id": volume_id}}) + + def update(self, context, volume_id, fields): + self.db.volume_update(context, volume_id, fields) + + def get(self, context, volume_id=None): + if volume_id is not None: + return self.db.volume_get(context, volume_id) + if context.user.is_admin(): + return self.db.volume_get_all(context) + return self.db.volume_get_all_by_project(context, context.project_id) + + def check_attach(self, context, volume_id): + volume = self.db.volume_get(context, volume_id) + # TODO(vish): abstract status checking? + if volume['status'] != "available": + raise exception.ApiError(_("Volume status must be available")) + if volume['attach_status'] == "attached": + raise exception.ApiError(_("Volume is already attached")) + + def check_detach(self, context, volume_id): + volume = self.db.volume_get(context, volume_id) + # TODO(vish): abstract status checking? + if volume['status'] == "available": + raise exception.ApiError(_("Volume is already detached")) From 24e253a1feaa0a39e4095f447f62f7ea9b43c8bb Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 29 Dec 2010 18:30:01 -0600 Subject: [PATCH 31/86] moved check lock decorator to compute api level. altered openstack.test_servers according and wrote test for lock in tests.test_compute --- nova/api/openstack/servers.py | 43 ---------------------- nova/compute/api.py | 44 +++++++++++++++++++++++ nova/tests/api/openstack/test_servers.py | 45 ++++++++---------------- nova/tests/test_compute.py | 17 +++++++++ 4 files changed, 76 insertions(+), 73 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 24fd5000ca89..497a04ae37b3 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -17,7 +17,6 @@ import logging import traceback -import functools from webob import exc @@ -36,41 +35,6 @@ LOG = logging.getLogger('server') LOG.setLevel(logging.DEBUG) -def checks_instance_lock(function): - """ - decorator used for preventing action against locked instances - unless, of course, you happen to be admin - - """ - - @functools.wraps(function) - def decorated_function(*args, **kwargs): - - # grab args to function - try: - if 'req' in kwargs: - req = kwargs['req'] - else: - req = args[1] - if 'id' in kwargs: - _id = kwargs['id'] - else: - req = args[2] - context = req.environ['nova.context'] - except: - logging.error(_("check_lock: argument error: |%s|, |%s|"), args, - kwargs) - # if admin or unlocked call function, otherwise 404 - locked = compute_api.ComputeAPI().get_lock(context, _id) - admin = req.environ['nova.context'].is_admin - if admin or not locked: - return function(*args, **kwargs) - - return faults.Fault(exc.HTTPMethodNotAllowed()) - - return decorated_function - - def _translate_detail_keys(inst): """ Coerces into dictionary format, mapping everything to Rackspace-like attributes for return""" @@ -147,7 +111,6 @@ class Controller(wsgi.Controller): except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) - @checks_instance_lock def delete(self, req, id): """ Destroys a server """ try: @@ -175,7 +138,6 @@ class Controller(wsgi.Controller): key_data=key_pair['public_key']) return _translate_keys(instances[0]) - @checks_instance_lock def update(self, req, id): """ Updates the server name or password """ inst_dict = self._deserialize(req.body, req) @@ -197,7 +159,6 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPNotFound()) return exc.HTTPNoContent() - @checks_instance_lock def action(self, req, id): """ Multi-purpose method used to reboot, rebuild, and resize a server """ @@ -258,7 +219,6 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() - @checks_instance_lock def pause(self, req, id): """ Permit Admins to Pause the server. """ ctxt = req.environ['nova.context'] @@ -270,7 +230,6 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() - @checks_instance_lock def unpause(self, req, id): """ Permit Admins to Unpause the server. """ ctxt = req.environ['nova.context'] @@ -282,7 +241,6 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() - @checks_instance_lock def suspend(self, req, id): """permit admins to suspend the server""" context = req.environ['nova.context'] @@ -294,7 +252,6 @@ class Controller(wsgi.Controller): return faults.Fault(exc.HTTPUnprocessableEntity()) return exc.HTTPAccepted() - @checks_instance_lock def resume(self, req, id): """permit admins to resume the server from suspend""" context = req.environ['nova.context'] diff --git a/nova/compute/api.py b/nova/compute/api.py index 361ab991418f..d720a8f2c740 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -23,6 +23,7 @@ Handles all API requests relating to instances (guest vms). import datetime import logging import time +import functools from nova import db from nova import exception @@ -36,6 +37,40 @@ from nova.db import base FLAGS = flags.FLAGS +def checks_instance_lock(function): + """ + decorator used for preventing action against locked instances + unless, of course, you happen to be admin + + """ + + @functools.wraps(function) + def decorated_function(*args, **kwargs): + + # grab args to function + try: + if 'context' in kwargs: + context = kwargs['context'] + else: + context = args[1] + if 'instance_id' in kwargs: + instance_id = kwargs['instance_id'] + else: + instance_id = args[2] + locked = ComputeAPI().get_lock(context, instance_id) + admin = context.is_admin + except: + logging.error(_("check_instance_lock: argument error: |%s|, |%s|"), + args, + kwargs) + # if admin or unlocked call function, otherwise 405 + if admin or not locked: + return function(*args, **kwargs) + raise Exception(_("Instance is locked, cannot execute |%s|"), function) + + return decorated_function + + def generate_default_hostname(internal_id): """Default function to generate a hostname given an instance reference.""" return str(internal_id) @@ -198,6 +233,7 @@ class ComputeAPI(base.Base): 'project_id': context.project_id} db.security_group_create(context, values) + @checks_instance_lock def update_instance(self, context, instance_id, **kwargs): """Updates the instance in the datastore. @@ -212,6 +248,7 @@ class ComputeAPI(base.Base): """ return self.db.instance_update(context, instance_id, kwargs) + @checks_instance_lock def delete_instance(self, context, instance_id): logging.debug("Going to try and terminate %d" % instance_id) try: @@ -258,6 +295,7 @@ class ComputeAPI(base.Base): def get_instance(self, context, instance_id): return self.db.instance_get_by_internal_id(context, instance_id) + @checks_instance_lock def reboot(self, context, instance_id): """Reboot the given instance.""" instance = self.db.instance_get_by_internal_id(context, instance_id) @@ -267,6 +305,7 @@ class ComputeAPI(base.Base): {"method": "reboot_instance", "args": {"instance_id": instance['id']}}) + @checks_instance_lock def pause(self, context, instance_id): """Pause the given instance.""" instance = self.db.instance_get_by_internal_id(context, instance_id) @@ -276,6 +315,7 @@ class ComputeAPI(base.Base): {"method": "pause_instance", "args": {"instance_id": instance['id']}}) + @checks_instance_lock def unpause(self, context, instance_id): """Unpause the given instance.""" instance = self.db.instance_get_by_internal_id(context, instance_id) @@ -285,6 +325,7 @@ class ComputeAPI(base.Base): {"method": "unpause_instance", "args": {"instance_id": instance['id']}}) + @checks_instance_lock def suspend(self, context, instance_id): """suspend the instance with instance_id""" instance = self.db.instance_get_by_internal_id(context, instance_id) @@ -294,6 +335,7 @@ class ComputeAPI(base.Base): {"method": "suspend_instance", "args": {"instance_id": instance['id']}}) + @checks_instance_lock def resume(self, context, instance_id): """resume the instance with instance_id""" instance = self.db.instance_get_by_internal_id(context, instance_id) @@ -303,6 +345,7 @@ class ComputeAPI(base.Base): {"method": "resume_instance", "args": {"instance_id": instance['id']}}) + @checks_instance_lock def rescue(self, context, instance_id): """Rescue the given instance.""" instance = self.db.instance_get_by_internal_id(context, instance_id) @@ -312,6 +355,7 @@ class ComputeAPI(base.Base): {"method": "rescue_instance", "args": {"instance_id": instance['id']}}) + @checks_instance_lock def unrescue(self, context, instance_id): """Unrescue the given instance.""" instance = self.db.instance_get_by_internal_id(context, instance_id) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 56a5a9b275ce..05419fb70f51 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -321,47 +321,32 @@ class ServersTest(unittest.TestCase): self.assertEqual(self.server_delete_called, True) def test_lock(self): - # part one: stubs it to be locked and test pause + FLAGS.allow_admin_api = True + body = dict(server=dict( + name='server_test', imageId=2, flavorId=2, metadata={}, + personality={})) + req = webob.Request.blank('/v1.0/servers/1/pause') + req.method = 'POST' + req.content_type = 'application/json' + req.body = json.dumps(body) + + # part one: stubs it to be locked and attempt pause expecting exception def get_locked(self, context, id): return True - - # set get to return locked self.stubs.Set(nova.compute.api.ComputeAPI, 'get_lock', get_locked) - # attempt to pause - FLAGS.allow_admin_api = True - body = dict(server=dict( - name='server_test', imageId=2, flavorId=2, metadata={}, - personality={})) - req = webob.Request.blank('/v1.0/servers/1/pause') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - res = req.get_response(nova.api.API('os')) + # pause should raise exception on locked instance + self.assertRaises(Exception, req.get_response, nova.api.API('os')) - # expect a 404 since it was locked - self.assertEqual(res.status_int, 405) - - # Part two: stubs it to be unlocked and test pause + # Part two: stubs it to be unlocked and attempt pause expecting success def get_unlocked(self, context, id): return False - - # set get to return locked self.stubs.Set(nova.compute.api.ComputeAPI, 'get_lock', get_unlocked) - # attempt to pause - FLAGS.allow_admin_api = True - body = dict(server=dict( - name='server_test', imageId=2, flavorId=2, metadata={}, - personality={})) - req = webob.Request.blank('/v1.0/servers/1/pause') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) res = req.get_response(nova.api.API('os')) - # expect a 202 since it was unlocked - self.assertEqual(res.status_int, 202) + # expecting no exception, test will fail if exception is raised + res = req.get_response(nova.api.API('os')) if __name__ == "__main__": diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index bcb8a1526a13..422d59da0ed0 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -170,3 +170,20 @@ class ComputeTestCase(test.TestCase): self.context, instance_id) self.compute.terminate_instance(self.context, instance_id) + + def test_lock(self): + """ensure locked instance cannot be changed""" + instance_id = self._create_instance() + self.compute.run_instance(self.context, instance_id) + self.compute.pause_instance(self.context, instance_id) + self.compute.lock_instance(self.context, instance_id) + + # pause should raise exception on locked instance + self.assertRaises(Exception, self.compute.unpause_instance, + self.context, instance_id) + + # test will fail if exception is raised + self.compute.unlock_instance(self.context, instance_id) + self.compute.unpause_instance(self.context, instance_id) + + self.compute.terminate_instance(self.context, instance_id) From 74785bf8c070bf0760724b3412f4ee1bb05cf72b Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 29 Dec 2010 18:34:49 -0600 Subject: [PATCH 32/86] fixd variables being out of scope in lock decorator --- nova/compute/api.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index d720a8f2c740..073129c13e71 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -47,6 +47,10 @@ def checks_instance_lock(function): @functools.wraps(function) def decorated_function(*args, **kwargs): + # assume worst case (have to declare so they are in scope) + admin = False + locked = True + # grab args to function try: if 'context' in kwargs: @@ -60,7 +64,7 @@ def checks_instance_lock(function): locked = ComputeAPI().get_lock(context, instance_id) admin = context.is_admin except: - logging.error(_("check_instance_lock: argument error: |%s|, |%s|"), + raise Exception(_("check_instance_lock argument error |%s|, |%s|"), args, kwargs) # if admin or unlocked call function, otherwise 405 From d06f85c611adf244f2c757f023c92c2b6cad2e7c Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 29 Dec 2010 18:40:03 -0600 Subject: [PATCH 33/86] altered error exception/logging --- nova/compute/api.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index 073129c13e71..19459c6d9154 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -64,8 +64,7 @@ def checks_instance_lock(function): locked = ComputeAPI().get_lock(context, instance_id) admin = context.is_admin except: - raise Exception(_("check_instance_lock argument error |%s|, |%s|"), - args, + logging.error(_("check_instance_lock: arguments: |%s| |%s|"), args, kwargs) # if admin or unlocked call function, otherwise 405 if admin or not locked: From 837724193ece16310ff588a84d23891a75ced2f2 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 29 Dec 2010 18:46:36 -0600 Subject: [PATCH 34/86] altered error exception/logging --- nova/compute/api.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index 19459c6d9154..6602f2534b66 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -63,9 +63,11 @@ def checks_instance_lock(function): instance_id = args[2] locked = ComputeAPI().get_lock(context, instance_id) admin = context.is_admin - except: + except Excetion as e: logging.error(_("check_instance_lock: arguments: |%s| |%s|"), args, kwargs) + raise e + # if admin or unlocked call function, otherwise 405 if admin or not locked: return function(*args, **kwargs) From 6f76367d2fefcec9b957352dd60e76c2cc3ba233 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 29 Dec 2010 18:49:45 -0600 Subject: [PATCH 35/86] typo, trying to hurry.. look where that got me --- nova/compute/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index 6602f2534b66..232d1f26b879 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -63,7 +63,7 @@ def checks_instance_lock(function): instance_id = args[2] locked = ComputeAPI().get_lock(context, instance_id) admin = context.is_admin - except Excetion as e: + except Exception as e: logging.error(_("check_instance_lock: arguments: |%s| |%s|"), args, kwargs) raise e From b848f7459eb65ad365177d831783b3d63818f977 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 29 Dec 2010 18:57:33 -0600 Subject: [PATCH 36/86] added some logging --- nova/compute/api.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/nova/compute/api.py b/nova/compute/api.py index 232d1f26b879..0513ce95d38b 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -47,6 +47,9 @@ def checks_instance_lock(function): @functools.wraps(function) def decorated_function(*args, **kwargs): + logging.info(_("check_instance_locks decorating |%s|"), function) + logging.info(_("check_instance_locks: arguments: |%s| |%s|"), args, + kwargs) # assume worst case (have to declare so they are in scope) admin = False locked = True From 32b310f430c5db05c99de65a5bd400675770ef1d Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 29 Dec 2010 19:27:43 -0600 Subject: [PATCH 37/86] removed db.set_lock, using update_instance instead --- nova/compute/manager.py | 4 ++-- nova/db/sqlalchemy/api.py | 12 ------------ 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 05f1e44a2265..9a33c7cac899 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -339,7 +339,7 @@ class ComputeManager(manager.Manager): instance_ref = self.db.instance_get(context, instance_id) logging.debug(_('instance %s: locking'), instance_ref['internal_id']) - self.db.instance_set_lock(context, instance_id, True) + self.db.instance_update(context, instance_id, {'locked': True}) @exception.wrap_exception def unlock_instance(self, context, instance_id): @@ -351,7 +351,7 @@ class ComputeManager(manager.Manager): instance_ref = self.db.instance_get(context, instance_id) logging.debug(_('instance %s: unlocking'), instance_ref['internal_id']) - self.db.instance_set_lock(context, instance_id, False) + self.db.instance_update(context, instance_id, {'locked': False}) @exception.wrap_exception def get_console_output(self, context, instance_id): diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index ca71df7b3968..7e945e4cb3d8 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -856,18 +856,6 @@ def instance_action_create(context, values): return action_ref -@require_admin_context -def instance_set_lock(context, instance_id, lock): - """ - twiddle the locked bit in the db - lock is a boolean - - """ - db.instance_update(context, - instance_id, - {'locked': lock}) - - ################### From f1523f2fd19cde4ddbb046dc0362a0ac7d6b79e8 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 29 Dec 2010 20:48:33 -0600 Subject: [PATCH 38/86] moved check lock decorator from the compute api to the come manager... when it rains it pours --- nova/compute/api.py | 52 --------------------------------- nova/compute/manager.py | 65 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 52 deletions(-) diff --git a/nova/compute/api.py b/nova/compute/api.py index 0513ce95d38b..361ab991418f 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -23,7 +23,6 @@ Handles all API requests relating to instances (guest vms). import datetime import logging import time -import functools from nova import db from nova import exception @@ -37,48 +36,6 @@ from nova.db import base FLAGS = flags.FLAGS -def checks_instance_lock(function): - """ - decorator used for preventing action against locked instances - unless, of course, you happen to be admin - - """ - - @functools.wraps(function) - def decorated_function(*args, **kwargs): - - logging.info(_("check_instance_locks decorating |%s|"), function) - logging.info(_("check_instance_locks: arguments: |%s| |%s|"), args, - kwargs) - # assume worst case (have to declare so they are in scope) - admin = False - locked = True - - # grab args to function - try: - if 'context' in kwargs: - context = kwargs['context'] - else: - context = args[1] - if 'instance_id' in kwargs: - instance_id = kwargs['instance_id'] - else: - instance_id = args[2] - locked = ComputeAPI().get_lock(context, instance_id) - admin = context.is_admin - except Exception as e: - logging.error(_("check_instance_lock: arguments: |%s| |%s|"), args, - kwargs) - raise e - - # if admin or unlocked call function, otherwise 405 - if admin or not locked: - return function(*args, **kwargs) - raise Exception(_("Instance is locked, cannot execute |%s|"), function) - - return decorated_function - - def generate_default_hostname(internal_id): """Default function to generate a hostname given an instance reference.""" return str(internal_id) @@ -241,7 +198,6 @@ class ComputeAPI(base.Base): 'project_id': context.project_id} db.security_group_create(context, values) - @checks_instance_lock def update_instance(self, context, instance_id, **kwargs): """Updates the instance in the datastore. @@ -256,7 +212,6 @@ class ComputeAPI(base.Base): """ return self.db.instance_update(context, instance_id, kwargs) - @checks_instance_lock def delete_instance(self, context, instance_id): logging.debug("Going to try and terminate %d" % instance_id) try: @@ -303,7 +258,6 @@ class ComputeAPI(base.Base): def get_instance(self, context, instance_id): return self.db.instance_get_by_internal_id(context, instance_id) - @checks_instance_lock def reboot(self, context, instance_id): """Reboot the given instance.""" instance = self.db.instance_get_by_internal_id(context, instance_id) @@ -313,7 +267,6 @@ class ComputeAPI(base.Base): {"method": "reboot_instance", "args": {"instance_id": instance['id']}}) - @checks_instance_lock def pause(self, context, instance_id): """Pause the given instance.""" instance = self.db.instance_get_by_internal_id(context, instance_id) @@ -323,7 +276,6 @@ class ComputeAPI(base.Base): {"method": "pause_instance", "args": {"instance_id": instance['id']}}) - @checks_instance_lock def unpause(self, context, instance_id): """Unpause the given instance.""" instance = self.db.instance_get_by_internal_id(context, instance_id) @@ -333,7 +285,6 @@ class ComputeAPI(base.Base): {"method": "unpause_instance", "args": {"instance_id": instance['id']}}) - @checks_instance_lock def suspend(self, context, instance_id): """suspend the instance with instance_id""" instance = self.db.instance_get_by_internal_id(context, instance_id) @@ -343,7 +294,6 @@ class ComputeAPI(base.Base): {"method": "suspend_instance", "args": {"instance_id": instance['id']}}) - @checks_instance_lock def resume(self, context, instance_id): """resume the instance with instance_id""" instance = self.db.instance_get_by_internal_id(context, instance_id) @@ -353,7 +303,6 @@ class ComputeAPI(base.Base): {"method": "resume_instance", "args": {"instance_id": instance['id']}}) - @checks_instance_lock def rescue(self, context, instance_id): """Rescue the given instance.""" instance = self.db.instance_get_by_internal_id(context, instance_id) @@ -363,7 +312,6 @@ class ComputeAPI(base.Base): {"method": "rescue_instance", "args": {"instance_id": instance['id']}}) - @checks_instance_lock def unrescue(self, context, instance_id): """Unrescue 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 9a33c7cac899..22415959626c 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -36,6 +36,7 @@ terminating it. import datetime import logging +import functools from nova import exception from nova import flags @@ -53,6 +54,48 @@ flags.DEFINE_string('stub_network', False, 'Stub network related code') +def checks_instance_lock(function): + """ + decorator used for preventing action against locked instances + unless, of course, you happen to be admin + + """ + + @functools.wraps(function) + def decorated_function(*args, **kwargs): + + logging.info(_("check_instance_locks decorating |%s|"), function) + logging.info(_("check_instance_locks: arguments: |%s| |%s|"), args, + kwargs) + # assume worst case (have to declare so they are in scope) + admin = False + locked = True + + # grab args to function + try: + if 'context' in kwargs: + context = kwargs['context'] + else: + context = args[1] + if 'instance_id' in kwargs: + instance_id = kwargs['instance_id'] + else: + instance_id = args[2] + locked = args[0].get_locked(context, instance_id) + admin = context.is_admin + except Exception as e: + logging.error(_("check_instance_lock: arguments: |%s| |%s|"), args, + kwargs) + raise e + + # if admin or unlocked call function, otherwise 405 + if admin or not locked: + return function(*args, **kwargs) + raise Exception(_("Instance is locked, cannot execute |%s|"), function) + + return decorated_function + + class ComputeManager(manager.Manager): """Manages the running instances from creation to destruction.""" @@ -158,6 +201,7 @@ class ComputeManager(manager.Manager): self._update_state(context, instance_id) @exception.wrap_exception + @checks_instance_lock def terminate_instance(self, context, instance_id): """Terminate an instance on this machine.""" context = context.elevated() @@ -202,6 +246,7 @@ class ComputeManager(manager.Manager): self.db.instance_destroy(context, instance_id) @exception.wrap_exception + @checks_instance_lock def reboot_instance(self, context, instance_id): """Reboot an instance on this server.""" context = context.elevated() @@ -225,6 +270,7 @@ class ComputeManager(manager.Manager): self._update_state(context, instance_id) @exception.wrap_exception + @checks_instance_lock def rescue_instance(self, context, instance_id): """Rescue an instance on this server.""" context = context.elevated() @@ -241,6 +287,7 @@ class ComputeManager(manager.Manager): self._update_state(context, instance_id) @exception.wrap_exception + @checks_instance_lock def unrescue_instance(self, context, instance_id): """Rescue an instance on this server.""" context = context.elevated() @@ -261,6 +308,7 @@ class ComputeManager(manager.Manager): self._update_state(context, instance_id) @exception.wrap_exception + @checks_instance_lock def pause_instance(self, context, instance_id): """Pause an instance on this server.""" context = context.elevated() @@ -279,6 +327,7 @@ class ComputeManager(manager.Manager): result)) @exception.wrap_exception + @checks_instance_lock def unpause_instance(self, context, instance_id): """Unpause a paused instance on this server.""" context = context.elevated() @@ -297,6 +346,7 @@ class ComputeManager(manager.Manager): result)) @exception.wrap_exception + @checks_instance_lock def suspend_instance(self, context, instance_id): """suspend the instance with instance_id""" context = context.elevated() @@ -314,6 +364,7 @@ class ComputeManager(manager.Manager): result)) @exception.wrap_exception + @checks_instance_lock def resume_instance(self, context, instance_id): """resume the suspended instance with instance_id""" context = context.elevated() @@ -353,6 +404,18 @@ class ComputeManager(manager.Manager): logging.debug(_('instance %s: unlocking'), instance_ref['internal_id']) self.db.instance_update(context, instance_id, {'locked': False}) + @exception.wrap_exception + def get_locked(self, context, instance_id): + """ + return the boolean state of (instance with instance_id)'s lock + + """ + context = context.elevated() + logging.debug(_('instance %s: getting locked'), + instance_ref['internal_id']) + instance_ref = self.db.instance_get(context, instance_id) + return instance_ref['locked'] + @exception.wrap_exception def get_console_output(self, context, instance_id): """Send the console output for an instance.""" @@ -363,6 +426,7 @@ class ComputeManager(manager.Manager): return self.driver.get_console_output(instance_ref) @exception.wrap_exception + @checks_instance_lock def attach_volume(self, context, instance_id, volume_id, mountpoint): """Attach a volume to an instance.""" context = context.elevated() @@ -392,6 +456,7 @@ class ComputeManager(manager.Manager): return True @exception.wrap_exception + @checks_instance_lock def detach_volume(self, context, instance_id, volume_id): """Detach a volume from an instance.""" context = context.elevated() From 656233762a61929d43f671e4765d52f25299562f Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 29 Dec 2010 20:52:48 -0600 Subject: [PATCH 39/86] syntax error --- nova/compute/manager.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 22415959626c..cd2c95d8dbc4 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -411,8 +411,7 @@ class ComputeManager(manager.Manager): """ context = context.elevated() - logging.debug(_('instance %s: getting locked'), - instance_ref['internal_id']) + logging.debug(_('instance %s: getting locked state'), instance_id) instance_ref = self.db.instance_get(context, instance_id) return instance_ref['locked'] From 2515d8ee9e32e0658b6179e900cf2e0e87a032dc Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 29 Dec 2010 21:16:53 -0600 Subject: [PATCH 40/86] fixed up the compute lock test, was failing because the context was always admin --- nova/tests/test_compute.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 422d59da0ed0..f914294f0fda 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -175,15 +175,15 @@ class ComputeTestCase(test.TestCase): """ensure locked instance cannot be changed""" instance_id = self._create_instance() self.compute.run_instance(self.context, instance_id) - self.compute.pause_instance(self.context, instance_id) self.compute.lock_instance(self.context, instance_id) + non_admin_context = context.RequestContext(None, None, False, False) # pause should raise exception on locked instance - self.assertRaises(Exception, self.compute.unpause_instance, - self.context, instance_id) + self.assertRaises(Exception, self.compute.reboot_instance, + non_admin_context, instance_id) # test will fail if exception is raised self.compute.unlock_instance(self.context, instance_id) - self.compute.unpause_instance(self.context, instance_id) + self.compute.reboot_instance(non_admin_context, instance_id) self.compute.terminate_instance(self.context, instance_id) From da7d31d5a4fa712ae24f6ec56d7469a3ee453c87 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 29 Dec 2010 21:26:45 -0600 Subject: [PATCH 41/86] removed tests.api.openstack.test_servers test_lock, to hell with it. i'm not even sure if testing lock needs to be at this level --- nova/tests/api/openstack/test_servers.py | 28 ------------------------ 1 file changed, 28 deletions(-) diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 05419fb70f51..5d23db588863 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -320,34 +320,6 @@ class ServersTest(unittest.TestCase): self.assertEqual(res.status, '202 Accepted') self.assertEqual(self.server_delete_called, True) - def test_lock(self): - FLAGS.allow_admin_api = True - body = dict(server=dict( - name='server_test', imageId=2, flavorId=2, metadata={}, - personality={})) - req = webob.Request.blank('/v1.0/servers/1/pause') - req.method = 'POST' - req.content_type = 'application/json' - req.body = json.dumps(body) - - # part one: stubs it to be locked and attempt pause expecting exception - def get_locked(self, context, id): - return True - self.stubs.Set(nova.compute.api.ComputeAPI, 'get_lock', get_locked) - - # pause should raise exception on locked instance - self.assertRaises(Exception, req.get_response, nova.api.API('os')) - - # Part two: stubs it to be unlocked and attempt pause expecting success - def get_unlocked(self, context, id): - return False - self.stubs.Set(nova.compute.api.ComputeAPI, 'get_lock', get_unlocked) - - res = req.get_response(nova.api.API('os')) - - # expecting no exception, test will fail if exception is raised - res = req.get_response(nova.api.API('os')) - if __name__ == "__main__": unittest.main() From 4531600425d71659581aa549bdc5e719e41efc9e Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 29 Dec 2010 22:08:38 -0600 Subject: [PATCH 42/86] altered the compute lock test --- nova/compute/manager.py | 16 ++++++++++------ nova/tests/test_compute.py | 12 +++++++----- 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index cd2c95d8dbc4..2671d401c367 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -70,6 +70,7 @@ def checks_instance_lock(function): # assume worst case (have to declare so they are in scope) admin = False locked = True + instance_id = False # grab args to function try: @@ -81,17 +82,20 @@ def checks_instance_lock(function): instance_id = kwargs['instance_id'] else: instance_id = args[2] - locked = args[0].get_locked(context, instance_id) + locked = args[0].get_lock(context, instance_id) admin = context.is_admin except Exception as e: - logging.error(_("check_instance_lock: arguments: |%s| |%s|"), args, + logging.error(_("check_instance_lock fail! args: |%s| |%s|"), args, kwargs) raise e - # if admin or unlocked call function, otherwise 405 + # if admin or unlocked call function otherwise log error if admin or not locked: - return function(*args, **kwargs) - raise Exception(_("Instance is locked, cannot execute |%s|"), function) + function(*args, **kwargs) + else: + logging.error(_("Instance |%s| is locked, cannot execute |%s|"), + instance_id, function) + return False return decorated_function @@ -405,7 +409,7 @@ class ComputeManager(manager.Manager): self.db.instance_update(context, instance_id, {'locked': False}) @exception.wrap_exception - def get_locked(self, context, instance_id): + def get_lock(self, context, instance_id): """ return the boolean state of (instance with instance_id)'s lock diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index f914294f0fda..78582b75a3ec 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -178,12 +178,14 @@ class ComputeTestCase(test.TestCase): self.compute.lock_instance(self.context, instance_id) non_admin_context = context.RequestContext(None, None, False, False) - # pause should raise exception on locked instance - self.assertRaises(Exception, self.compute.reboot_instance, - non_admin_context, instance_id) - # test will fail if exception is raised + # decorator for reboot should return False + ret_val = self.compute.reboot_instance(non_admin_context,instance_id) + self.assertEqual(ret_val, False) + + # decorator for pause should return the result of the function reboot self.compute.unlock_instance(self.context, instance_id) - self.compute.reboot_instance(non_admin_context, instance_id) + ret_val = self.compute.reboot_instance(non_admin_context,instance_id) + self.assertNotEqual(ret_val, None) self.compute.terminate_instance(self.context, instance_id) From a4088ce75347acb2ee2f2550c185afb4ce3231de Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Wed, 29 Dec 2010 22:16:34 -0600 Subject: [PATCH 43/86] fixed the compute lock test --- nova/tests/test_compute.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 78582b75a3ec..993c4fd3cb42 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -175,17 +175,17 @@ class ComputeTestCase(test.TestCase): """ensure locked instance cannot be changed""" instance_id = self._create_instance() self.compute.run_instance(self.context, instance_id) - self.compute.lock_instance(self.context, instance_id) non_admin_context = context.RequestContext(None, None, False, False) - # decorator for reboot should return False + # decorator should return False (fail) with locked nonadmin context + self.compute.lock_instance(self.context, instance_id) ret_val = self.compute.reboot_instance(non_admin_context,instance_id) self.assertEqual(ret_val, False) - # decorator for pause should return the result of the function reboot + # decorator should return None (success) with unlocked nonadmin context self.compute.unlock_instance(self.context, instance_id) ret_val = self.compute.reboot_instance(non_admin_context,instance_id) - self.assertNotEqual(ret_val, None) + self.assertEqual(ret_val, None) self.compute.terminate_instance(self.context, instance_id) From b1a08af498ed6b52e3373a23196ded0396e6d34b Mon Sep 17 00:00:00 2001 From: Eric Day Date: Wed, 29 Dec 2010 20:30:36 -0800 Subject: [PATCH 44/86] Cleaned up the compute API, mostly consistency with other parts of the system and renaming redundant module names. --- nova/api/ec2/cloud.py | 24 +- nova/api/openstack/servers.py | 22 +- nova/compute/__init__.py | 324 +++++++++++++++++++++- nova/compute/api.py | 332 ----------------------- nova/tests/api/openstack/test_servers.py | 8 +- nova/tests/test_compute.py | 10 +- 6 files changed, 341 insertions(+), 379 deletions(-) delete mode 100644 nova/compute/api.py diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 74c73e0ddb3d..cc58f3cfe718 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -31,6 +31,7 @@ import os from nova import context import IPy +from nova import compute from nova import crypto from nova import db from nova import exception @@ -39,7 +40,6 @@ from nova import quota from nova import rpc from nova import utils from nova import volume -from nova.compute import api as compute_api from nova.compute import instance_types @@ -90,9 +90,9 @@ class CloudController(object): self.network_manager = utils.import_object(FLAGS.network_manager) self.image_service = utils.import_object(FLAGS.image_service) self.volume_api = volume.API() - self.compute_api = compute_api.ComputeAPI(self.network_manager, - self.image_service, - self.volume_api) + self.compute_api = compute.API(self.network_manager, + self.image_service, + self.volume_api) self.setup() def __str__(self): @@ -116,7 +116,7 @@ class CloudController(object): def _get_mpi_data(self, context, project_id): result = {} - for instance in self.compute_api.get_instances(context, project_id): + for instance in self.compute_api.get(context, project_id=project_id): if instance['fixed_ip']: line = '%s slots=%d' % (instance['fixed_ip']['address'], instance['vcpus']) @@ -440,7 +440,7 @@ class CloudController(object): # instance_id is passed in as a list of instances ec2_id = instance_id[0] instance_id = ec2_id_to_id(ec2_id) - instance_ref = self.compute_api.get_instance(context, instance_id) + instance_ref = self.compute_api.get(context, instance_id) output = rpc.call(context, '%s.%s' % (FLAGS.compute_topic, instance_ref['host']), @@ -561,7 +561,7 @@ class CloudController(object): instances = db.instance_get_all_by_reservation(context, reservation_id) else: - instances = self.compute_api.get_instances(context) + instances = self.compute_api.get(context) for instance in instances: if not context.user.is_admin(): if instance['image_id'] == FLAGS.vpn_image_id: @@ -664,7 +664,7 @@ class CloudController(object): def associate_address(self, context, instance_id, public_ip, **kwargs): instance_id = ec2_id_to_id(instance_id) - instance_ref = self.compute_api.get_instance(context, instance_id) + instance_ref = self.compute_api.get(context, instance_id) fixed_address = db.instance_get_fixed_address(context, instance_ref['id']) floating_ip_ref = db.floating_ip_get_by_address(context, public_ip) @@ -695,7 +695,7 @@ class CloudController(object): def run_instances(self, context, **kwargs): max_count = int(kwargs.get('max_count', 1)) - instances = self.compute_api.create_instances(context, + instances = self.compute_api.create(context, instance_types.get_by_type(kwargs.get('instance_type', None)), kwargs['image_id'], min_count=int(kwargs.get('min_count', max_count)), @@ -703,7 +703,7 @@ class CloudController(object): kernel_id=kwargs.get('kernel_id', None), ramdisk_id=kwargs.get('ramdisk_id'), display_name=kwargs.get('display_name'), - description=kwargs.get('display_description'), + display_description=kwargs.get('display_description'), key_name=kwargs.get('key_name'), user_data=kwargs.get('user_data'), security_group=kwargs.get('security_group'), @@ -717,7 +717,7 @@ class CloudController(object): logging.debug("Going to start terminating instances") for ec2_id in instance_id: instance_id = ec2_id_to_id(ec2_id) - self.compute_api.delete_instance(context, instance_id) + self.compute_api.delete(context, instance_id) return True def reboot_instances(self, context, instance_id, **kwargs): @@ -747,7 +747,7 @@ class CloudController(object): changes[field] = kwargs[field] if changes: instance_id = ec2_id_to_id(ec2_id) - inst = self.compute_api.get_instance(context, instance_id) + inst = self.compute_api.get(context, instance_id) db.instance_update(context, inst['id'], kwargs) return True diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index cac5428b9365..30b2160bd21a 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -20,12 +20,12 @@ import traceback from webob import exc +from nova import compute from nova import exception from nova import wsgi from nova.api.openstack import common from nova.api.openstack import faults from nova.auth import manager as auth_manager -from nova.compute import api as compute_api from nova.compute import instance_types from nova.compute import power_state import nova.api.openstack @@ -81,7 +81,7 @@ class Controller(wsgi.Controller): "status", "progress"]}}} def __init__(self): - self.compute_api = compute_api.ComputeAPI() + self.compute_api = compute.API() super(Controller, self).__init__() def index(self, req): @@ -97,8 +97,7 @@ class Controller(wsgi.Controller): entity_maker - either _entity_detail or _entity_inst """ - instance_list = self.compute_api.get_instances( - req.environ['nova.context']) + instance_list = self.compute_api.get(req.environ['nova.context']) limited_list = common.limited(instance_list, req) res = [entity_maker(inst)['server'] for inst in limited_list] return _entity_list(res) @@ -106,8 +105,7 @@ class Controller(wsgi.Controller): def show(self, req, id): """ Returns server details by server id """ try: - instance = self.compute_api.get_instance( - req.environ['nova.context'], int(id)) + instance = self.compute_api.get(req.environ['nova.context'], id) return _entity_detail(instance) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) @@ -115,8 +113,7 @@ class Controller(wsgi.Controller): def delete(self, req, id): """ Destroys a server """ try: - self.compute_api.delete_instance(req.environ['nova.context'], - int(id)) + self.compute_api.delete(req.environ['nova.context'], id) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) return exc.HTTPAccepted() @@ -129,12 +126,12 @@ class Controller(wsgi.Controller): key_pair = auth_manager.AuthManager.get_key_pairs( req.environ['nova.context'])[0] - instances = self.compute_api.create_instances( + instances = self.compute_api.create( req.environ['nova.context'], instance_types.get_by_flavor_id(env['server']['flavorId']), env['server']['imageId'], display_name=env['server']['name'], - description=env['server']['name'], + display_description=env['server']['name'], key_name=key_pair['name'], key_data=key_pair['public_key']) return _entity_inst(instances[0]) @@ -152,9 +149,8 @@ class Controller(wsgi.Controller): update_dict['display_name'] = inst_dict['server']['name'] try: - self.compute_api.update_instance(req.environ['nova.context'], - instance['id'], - **update_dict) + self.compute_api.update(req.environ['nova.context'], id, + **update_dict) except exception.NotFound: return faults.Fault(exc.HTTPNotFound()) return exc.HTTPNoContent() diff --git a/nova/compute/__init__.py b/nova/compute/__init__.py index a5df2ec1acc2..648c0ff6a9c6 100644 --- a/nova/compute/__init__.py +++ b/nova/compute/__init__.py @@ -17,16 +17,316 @@ # under the License. """ -:mod:`nova.compute` -- Compute Nodes using LibVirt -===================================================== - -.. automodule:: nova.compute - :platform: Unix - :synopsis: Thin wrapper around libvirt for VM mgmt. -.. moduleauthor:: Jesse Andrews -.. moduleauthor:: Devin Carlen -.. moduleauthor:: Vishvananda Ishaya -.. moduleauthor:: Joshua McKenty -.. moduleauthor:: Manish Singh -.. moduleauthor:: Andy Smith +Handles all requests relating to instances (guest vms). """ + +import datetime +import logging +import time + +from nova import db +from nova import exception +from nova import flags +from nova import quota +from nova import rpc +from nova import utils +from nova import volume +from nova.compute import instance_types +from nova.db import base + +FLAGS = flags.FLAGS + + +def generate_default_hostname(instance_id): + """Default function to generate a hostname given an instance reference.""" + return str(instance_id) + + +class API(base.Base): + """API for interacting with the compute manager.""" + + def __init__(self, network_manager=None, image_service=None, + volume_api=None, **kwargs): + if not network_manager: + network_manager = utils.import_object(FLAGS.network_manager) + self.network_manager = network_manager + if not image_service: + image_service = utils.import_object(FLAGS.image_service) + self.image_service = image_service + if not volume_api: + volume_api = volume.API() + self.volume_api = volume_api + super(API, self).__init__(**kwargs) + + def get_network_topic(self, context, instance_id): + try: + instance = self.get(context, instance_id) + except exception.NotFound as e: + logging.warning("Instance %d was not found in get_network_topic", + instance_id) + raise e + + host = instance['host'] + if not host: + raise exception.Error("Instance %d has no host" % instance_id) + topic = self.db.queue_get_for(context, FLAGS.compute_topic, host) + return rpc.call(context, + topic, + {"method": "get_network_topic", "args": {'fake': 1}}) + + def create(self, context, instance_type, + image_id, kernel_id=None, ramdisk_id=None, + min_count=1, max_count=1, + display_name='', display_description='', + key_name=None, key_data=None, security_group='default', + user_data=None, generate_hostname=generate_default_hostname): + """Create the number of instances requested if quota and + other arguments check out ok.""" + + num_instances = quota.allowed_instances(context, max_count, + instance_type) + if num_instances < min_count: + logging.warn("Quota exceeeded for %s, tried to run %s instances", + context.project_id, min_count) + raise quota.QuotaError("Instance quota exceeded. You can only " + "run %s more instances of this type." % + num_instances, "InstanceLimitExceeded") + + is_vpn = image_id == FLAGS.vpn_image_id + if not is_vpn: + image = self.image_service.show(context, image_id) + if kernel_id is None: + kernel_id = image.get('kernelId', None) + if ramdisk_id is None: + ramdisk_id = image.get('ramdiskId', None) + # No kernel and ramdisk for raw images + if kernel_id == str(FLAGS.null_kernel): + kernel_id = None + ramdisk_id = None + logging.debug("Creating a raw instance") + # Make sure we have access to kernel and ramdisk (if not raw) + if kernel_id: + self.image_service.show(context, kernel_id) + if ramdisk_id: + self.image_service.show(context, ramdisk_id) + + if security_group is None: + security_group = ['default'] + if not type(security_group) is list: + security_group = [security_group] + + security_groups = [] + self.ensure_default_security_group(context) + for security_group_name in security_group: + group = db.security_group_get_by_name(context, + context.project_id, + security_group_name) + security_groups.append(group['id']) + + if key_data is None and key_name: + key_pair = db.key_pair_get(context, context.user_id, key_name) + key_data = key_pair['public_key'] + + type_data = instance_types.INSTANCE_TYPES[instance_type] + base_options = { + 'reservation_id': utils.generate_uid('r'), + 'image_id': image_id, + 'kernel_id': kernel_id or '', + 'ramdisk_id': ramdisk_id or '', + 'state_description': 'scheduling', + 'user_id': context.user_id, + 'project_id': context.project_id, + 'launch_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()), + 'instance_type': instance_type, + 'memory_mb': type_data['memory_mb'], + 'vcpus': type_data['vcpus'], + 'local_gb': type_data['local_gb'], + 'display_name': display_name, + 'display_description': display_description, + 'user_data': user_data or '', + 'key_name': key_name, + 'key_data': key_data} + + elevated = context.elevated() + instances = [] + logging.debug(_("Going to run %s instances..."), num_instances) + for num in range(num_instances): + instance = dict(mac_address=utils.generate_mac(), + launch_index=num, + **base_options) + instance = self.db.instance_create(context, instance) + instance_id = instance['id'] + + elevated = context.elevated() + if not security_groups: + security_groups = [] + for security_group_id in security_groups: + self.db.instance_add_security_group(elevated, + instance_id, + security_group_id) + + # Set sane defaults if not specified + updates = dict(hostname=generate_hostname(instance_id)) + if 'display_name' not in instance: + updates['display_name'] = "Server %s" % instance_id + + instance = self.update(context, instance_id, **updates) + instances.append(instance) + + logging.debug(_("Casting to scheduler for %s/%s's instance %s"), + context.project_id, context.user_id, instance_id) + rpc.cast(context, + FLAGS.scheduler_topic, + {"method": "run_instance", + "args": {"topic": FLAGS.compute_topic, + "instance_id": instance_id}}) + + return instances + + def ensure_default_security_group(self, context): + """ Create security group for the security context if it + does not already exist + + :param context: the security context + + """ + try: + db.security_group_get_by_name(context, context.project_id, + 'default') + except exception.NotFound: + values = {'name': 'default', + 'description': 'default', + 'user_id': context.user_id, + 'project_id': context.project_id} + db.security_group_create(context, values) + + def update(self, context, instance_id, **kwargs): + """Updates the instance in the datastore. + + :param context: The security context + :param instance_id: ID of the instance to update + :param kwargs: All additional keyword args are treated + as data fields of the instance to be + updated + + :retval None + + """ + return self.db.instance_update(context, instance_id, kwargs) + + def delete(self, context, instance_id): + logging.debug("Going to try and terminate %s" % instance_id) + try: + instance = self.get(context, instance_id) + except exception.NotFound as e: + logging.warning(_("Instance %s was not found during terminate"), + instance_id) + raise e + + if (instance['state_description'] == 'terminating'): + logging.warning(_("Instance %s is already being terminated"), + instance_id) + return + + self.update(context, + instance['id'], + state_description='terminating', + state=0, + terminated_at=datetime.datetime.utcnow()) + + host = instance['host'] + if host: + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "terminate_instance", + "args": {"instance_id": instance['id']}}) + else: + self.db.instance_destroy(context, instance['id']) + + def get(self, context, instance_id=None, project_id=None): + """Get one or more instances, possibly filtered by project + ID or user ID. If there is no filter and the context is + an admin, it will retreive all instances in the system.""" + if instance_id is not None: + return self.db.instance_get_by_id(context, instance_id) + if project_id or not context.is_admin: + if not context.project: + return self.db.instance_get_all_by_user(context, + context.user_id) + if project_id is None: + project_id = context.project_id + return self.db.instance_get_all_by_project(context, + project_id) + return self.db.instance_get_all(context) + + def reboot(self, context, instance_id): + """Reboot the given instance.""" + instance = self.get(context, instance_id) + host = instance['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "reboot_instance", + "args": {"instance_id": instance['id']}}) + + def pause(self, context, instance_id): + """Pause the given instance.""" + instance = self.get(context, instance_id) + host = instance['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "pause_instance", + "args": {"instance_id": instance['id']}}) + + def unpause(self, context, instance_id): + """Unpause the given instance.""" + instance = self.get(context, instance_id) + host = instance['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "unpause_instance", + "args": {"instance_id": instance['id']}}) + + def rescue(self, context, instance_id): + """Rescue the given instance.""" + instance = self.get(context, instance_id) + host = instance['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "rescue_instance", + "args": {"instance_id": instance['id']}}) + + def unrescue(self, context, instance_id): + """Unrescue the given instance.""" + instance = self.get(context, instance_id) + host = instance['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "unrescue_instance", + "args": {"instance_id": instance['id']}}) + + def attach_volume(self, context, instance_id, volume_id, device): + if not re.match("^/dev/[a-z]d[a-z]+$", device): + raise exception.ApiError(_("Invalid device specified: %s. " + "Example device: /dev/vdb") % device) + self.volume_api.check_attach(context, volume_id) + instance = self.get(context, instance_id) + host = instance['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "attach_volume", + "args": {"volume_id": volume_id, + "instance_id": instance_id, + "mountpoint": device}}) + + def detach_volume(self, context, volume_id): + instance = self.db.volume_get_instance(context.elevated(), volume_id) + if not instance: + raise exception.ApiError(_("Volume isn't attached to anything!")) + self.volume_api.check_detach(context, volume_id) + host = instance['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "detach_volume", + "args": {"instance_id": instance['id'], + "volume_id": volume_id}}) + return instance diff --git a/nova/compute/api.py b/nova/compute/api.py deleted file mode 100644 index 870fcdbe4da1..000000000000 --- a/nova/compute/api.py +++ /dev/null @@ -1,332 +0,0 @@ -# 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. - -""" -Handles all API requests relating to instances (guest vms). -""" - -import datetime -import logging -import time - -from nova import db -from nova import exception -from nova import flags -from nova import quota -from nova import rpc -from nova import utils -from nova import volume -from nova.compute import instance_types -from nova.db import base - -FLAGS = flags.FLAGS - - -def generate_default_hostname(instance_id): - """Default function to generate a hostname given an instance reference.""" - return str(instance_id) - - -class ComputeAPI(base.Base): - """API for interacting with the compute manager.""" - - def __init__(self, network_manager=None, image_service=None, - volume_api=None, **kwargs): - if not network_manager: - network_manager = utils.import_object(FLAGS.network_manager) - self.network_manager = network_manager - if not image_service: - image_service = utils.import_object(FLAGS.image_service) - self.image_service = image_service - if not volume_api: - volume_api = volume.API() - self.volume_api = volume_api - super(ComputeAPI, self).__init__(**kwargs) - - def get_network_topic(self, context, instance_id): - try: - instance = self.db.instance_get_by_id(context, instance_id) - except exception.NotFound as e: - logging.warning("Instance %d was not found in get_network_topic", - instance_id) - raise e - - host = instance['host'] - if not host: - raise exception.Error("Instance %d has no host" % instance_id) - topic = self.db.queue_get_for(context, FLAGS.compute_topic, host) - return rpc.call(context, - topic, - {"method": "get_network_topic", "args": {'fake': 1}}) - - def create_instances(self, context, instance_type, image_id, min_count=1, - max_count=1, kernel_id=None, ramdisk_id=None, - display_name='', description='', key_name=None, - key_data=None, security_group='default', - user_data=None, - generate_hostname=generate_default_hostname): - """Create the number of instances requested if quote and - other arguments check out ok.""" - - num_instances = quota.allowed_instances(context, max_count, - instance_type) - if num_instances < min_count: - logging.warn("Quota exceeeded for %s, tried to run %s instances", - context.project_id, min_count) - raise quota.QuotaError("Instance quota exceeded. You can only " - "run %s more instances of this type." % - num_instances, "InstanceLimitExceeded") - - is_vpn = image_id == FLAGS.vpn_image_id - if not is_vpn: - image = self.image_service.show(context, image_id) - if kernel_id is None: - kernel_id = image.get('kernelId', None) - if ramdisk_id is None: - ramdisk_id = image.get('ramdiskId', None) - #No kernel and ramdisk for raw images - if kernel_id == str(FLAGS.null_kernel): - kernel_id = None - ramdisk_id = None - logging.debug("Creating a raw instance") - # Make sure we have access to kernel and ramdisk (if not raw) - if kernel_id: - self.image_service.show(context, kernel_id) - if ramdisk_id: - self.image_service.show(context, ramdisk_id) - - if security_group is None: - security_group = ['default'] - if not type(security_group) is list: - security_group = [security_group] - - security_groups = [] - self.ensure_default_security_group(context) - for security_group_name in security_group: - group = db.security_group_get_by_name(context, - context.project_id, - security_group_name) - security_groups.append(group['id']) - - if key_data is None and key_name: - key_pair = db.key_pair_get(context, context.user_id, key_name) - key_data = key_pair['public_key'] - - type_data = instance_types.INSTANCE_TYPES[instance_type] - base_options = { - 'reservation_id': utils.generate_uid('r'), - 'image_id': image_id, - 'kernel_id': kernel_id or '', - 'ramdisk_id': ramdisk_id or '', - 'state_description': 'scheduling', - 'user_id': context.user_id, - 'project_id': context.project_id, - 'launch_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()), - 'instance_type': instance_type, - 'memory_mb': type_data['memory_mb'], - 'vcpus': type_data['vcpus'], - 'local_gb': type_data['local_gb'], - 'display_name': display_name, - 'display_description': description, - 'user_data': user_data or '', - 'key_name': key_name, - 'key_data': key_data} - - elevated = context.elevated() - instances = [] - logging.debug(_("Going to run %s instances..."), num_instances) - for num in range(num_instances): - instance = dict(mac_address=utils.generate_mac(), - launch_index=num, - **base_options) - instance = self.db.instance_create(context, instance) - instance_id = instance['id'] - - elevated = context.elevated() - if not security_groups: - security_groups = [] - for security_group_id in security_groups: - self.db.instance_add_security_group(elevated, - instance_id, - security_group_id) - - # Set sane defaults if not specified - updates = dict(hostname=generate_hostname(instance_id)) - if 'display_name' not in instance: - updates['display_name'] = "Server %s" % instance_id - - instance = self.update_instance(context, instance_id, **updates) - instances.append(instance) - - logging.debug(_("Casting to scheduler for %s/%s's instance %s"), - context.project_id, context.user_id, instance_id) - rpc.cast(context, - FLAGS.scheduler_topic, - {"method": "run_instance", - "args": {"topic": FLAGS.compute_topic, - "instance_id": instance_id}}) - - return instances - - def ensure_default_security_group(self, context): - """ Create security group for the security context if it - does not already exist - - :param context: the security context - - """ - try: - db.security_group_get_by_name(context, context.project_id, - 'default') - except exception.NotFound: - values = {'name': 'default', - 'description': 'default', - 'user_id': context.user_id, - 'project_id': context.project_id} - db.security_group_create(context, values) - - def update_instance(self, context, instance_id, **kwargs): - """Updates the instance in the datastore. - - :param context: The security context - :param instance_id: ID of the instance to update - :param kwargs: All additional keyword args are treated - as data fields of the instance to be - updated - - :retval None - - """ - return self.db.instance_update(context, instance_id, kwargs) - - def delete_instance(self, context, instance_id): - logging.debug("Going to try and terminate %s" % instance_id) - try: - instance = self.db.instance_get_by_id(context, instance_id) - except exception.NotFound as e: - logging.warning(_("Instance %s was not found during terminate"), - instance_id) - raise e - - if (instance['state_description'] == 'terminating'): - logging.warning(_("Instance %s is already being terminated"), - instance_id) - return - - self.update_instance(context, - instance['id'], - state_description='terminating', - state=0, - terminated_at=datetime.datetime.utcnow()) - - host = instance['host'] - if host: - rpc.cast(context, - self.db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "terminate_instance", - "args": {"instance_id": instance['id']}}) - else: - self.db.instance_destroy(context, instance['id']) - - def get_instances(self, context, project_id=None): - """Get all instances, possibly filtered by project ID or - user ID. If there is no filter and the context is an admin, - it will retreive all instances in the system.""" - if project_id or not context.is_admin: - if not context.project: - return self.db.instance_get_all_by_user(context, - context.user_id) - if project_id is None: - project_id = context.project_id - return self.db.instance_get_all_by_project(context, project_id) - return self.db.instance_get_all(context) - - def get_instance(self, context, instance_id): - return self.db.instance_get_by_id(context, instance_id) - - def reboot(self, context, instance_id): - """Reboot the given instance.""" - instance = self.db.instance_get_by_id(context, instance_id) - host = instance['host'] - rpc.cast(context, - self.db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "reboot_instance", - "args": {"instance_id": instance['id']}}) - - def pause(self, context, instance_id): - """Pause the given instance.""" - instance = self.db.instance_get_by_id(context, instance_id) - host = instance['host'] - rpc.cast(context, - self.db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "pause_instance", - "args": {"instance_id": instance['id']}}) - - def unpause(self, context, instance_id): - """Unpause the given instance.""" - instance = self.db.instance_get_by_id(context, instance_id) - host = instance['host'] - rpc.cast(context, - self.db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "unpause_instance", - "args": {"instance_id": instance['id']}}) - - def rescue(self, context, instance_id): - """Rescue the given instance.""" - instance = self.db.instance_get_by_id(context, instance_id) - host = instance['host'] - rpc.cast(context, - self.db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "rescue_instance", - "args": {"instance_id": instance['id']}}) - - def unrescue(self, context, instance_id): - """Unrescue the given instance.""" - instance = self.db.instance_get_by_id(context, instance_id) - host = instance['host'] - rpc.cast(context, - self.db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "unrescue_instance", - "args": {"instance_id": instance['id']}}) - - def attach_volume(self, context, instance_id, volume_id, device): - if not re.match("^/dev/[a-z]d[a-z]+$", device): - raise exception.ApiError(_("Invalid device specified: %s. " - "Example device: /dev/vdb") % device) - self.volume_api.check_attach(context, volume_id) - instance = self.get_instance(context, instance_id) - host = instance['host'] - rpc.cast(context, - self.db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "attach_volume", - "args": {"volume_id": volume_id, - "instance_id": instance_id, - "mountpoint": device}}) - - def detach_volume(self, context, volume_id): - instance = self.db.volume_get_instance(context.elevated(), volume_id) - if not instance: - raise exception.ApiError(_("Volume isn't attached to anything!")) - self.volume_api.check_detach(context, volume_id) - host = instance['host'] - rpc.cast(context, - self.db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "detach_volume", - "args": {"instance_id": instance['id'], - "volume_id": volume_id}}) - return instance diff --git a/nova/tests/api/openstack/test_servers.py b/nova/tests/api/openstack/test_servers.py index 173772364ee0..1cdfb86f19e2 100644 --- a/nova/tests/api/openstack/test_servers.py +++ b/nova/tests/api/openstack/test_servers.py @@ -86,10 +86,8 @@ class ServersTest(unittest.TestCase): instance_address) self.stubs.Set(nova.db.api, 'instance_get_floating_address', instance_address) - self.stubs.Set(nova.compute.api.ComputeAPI, 'pause', - fake_compute_api) - self.stubs.Set(nova.compute.api.ComputeAPI, 'unpause', - fake_compute_api) + self.stubs.Set(nova.compute.API, 'pause', fake_compute_api) + self.stubs.Set(nova.compute.API, 'unpause', fake_compute_api) self.allow_admin = FLAGS.allow_admin_api def tearDown(self): @@ -100,7 +98,7 @@ class ServersTest(unittest.TestCase): req = webob.Request.blank('/v1.0/servers/1') res = req.get_response(nova.api.API('os')) res_dict = json.loads(res.body) - self.assertEqual(res_dict['server']['id'], 1) + self.assertEqual(res_dict['server']['id'], '1') self.assertEqual(res_dict['server']['name'], 'server1') def test_get_server_list(self): diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 348bb3351d86..f7067b98a859 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -22,6 +22,7 @@ Tests For Compute import datetime import logging +from nova import compute from nova import context from nova import db from nova import exception @@ -29,7 +30,6 @@ from nova import flags from nova import test from nova import utils from nova.auth import manager -from nova.compute import api as compute_api FLAGS = flags.FLAGS @@ -44,7 +44,7 @@ class ComputeTestCase(test.TestCase): stub_network=True, network_manager='nova.network.manager.FlatManager') self.compute = utils.import_object(FLAGS.compute_manager) - self.compute_api = compute_api.ComputeAPI() + self.compute_api = compute.API() self.manager = manager.AuthManager() self.user = self.manager.create_user('fake', 'fake', 'fake') self.project = self.manager.create_project('fake', 'fake', 'fake') @@ -72,7 +72,7 @@ class ComputeTestCase(test.TestCase): """Verify that an instance cannot be created without a display_name.""" cases = [dict(), dict(display_name=None)] for instance in cases: - ref = self.compute_api.create_instances(self.context, + ref = self.compute_api.create(self.context, FLAGS.default_instance_type, None, **instance) try: self.assertNotEqual(ref[0].display_name, None) @@ -80,13 +80,13 @@ class ComputeTestCase(test.TestCase): db.instance_destroy(self.context, ref[0]['id']) def test_create_instance_associates_security_groups(self): - """Make sure create_instances associates security groups""" + """Make sure create associates security groups""" values = {'name': 'default', 'description': 'default', 'user_id': self.user.id, 'project_id': self.project.id} group = db.security_group_create(self.context, values) - ref = self.compute_api.create_instances(self.context, + ref = self.compute_api.create(self.context, FLAGS.default_instance_type, None, security_group=['default']) try: self.assertEqual(len(ref[0]['security_groups']), 1) From 750a0c9b413ad3912d522355332cffadd9667d0c Mon Sep 17 00:00:00 2001 From: Eric Day Date: Wed, 29 Dec 2010 21:41:42 -0800 Subject: [PATCH 45/86] Converted a few more ec2 calls to use compute api. --- nova/api/ec2/cloud.py | 13 ++++--------- nova/compute/__init__.py | 12 +++++++++--- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index cc58f3cfe718..5ffc301be3f8 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -138,7 +138,7 @@ class CloudController(object): def get_metadata(self, address): ctxt = context.get_admin_context() - instance_ref = db.fixed_ip_get_instance(ctxt, address) + instance_ref = self.compute_api.get(ctxt, fixed_ip=address) if instance_ref is None: return None mpi = self._get_mpi_data(ctxt, instance_ref['project_id']) @@ -555,13 +555,9 @@ class CloudController(object): assert len(i) == 1 return i[0] - def _format_instances(self, context, reservation_id=None): + def _format_instances(self, context, **kwargs): reservations = {} - if reservation_id: - instances = db.instance_get_all_by_reservation(context, - reservation_id) - else: - instances = self.compute_api.get(context) + instances = self.compute_api.get(context, **kwargs) for instance in instances: if not context.user.is_admin(): if instance['image_id'] == FLAGS.vpn_image_id: @@ -747,8 +743,7 @@ class CloudController(object): changes[field] = kwargs[field] if changes: instance_id = ec2_id_to_id(ec2_id) - inst = self.compute_api.get(context, instance_id) - db.instance_update(context, inst['id'], kwargs) + self.compute_api.update(context, instance_id, **kwargs) return True def describe_images(self, context, image_id=None, **kwargs): diff --git a/nova/compute/__init__.py b/nova/compute/__init__.py index 648c0ff6a9c6..fd1cdcd1beb0 100644 --- a/nova/compute/__init__.py +++ b/nova/compute/__init__.py @@ -243,12 +243,18 @@ class API(base.Base): else: self.db.instance_destroy(context, instance['id']) - def get(self, context, instance_id=None, project_id=None): - """Get one or more instances, possibly filtered by project - ID or user ID. If there is no filter and the context is + def get(self, context, instance_id=None, project_id=None, + reservation_id=None, fixed_ip=None): + """Get one or more instances, possibly filtered by one of the + given parameters. If there is no filter and the context is an admin, it will retreive all instances in the system.""" if instance_id is not None: return self.db.instance_get_by_id(context, instance_id) + if reservation_id is not None: + return self.db.instance_get_all_by_reservation(context, + reservation_id) + if fixed_ip is not None: + return self.db.fixed_ip_get_instance(context, fixed_ip) if project_id or not context.is_admin: if not context.project: return self.db.instance_get_all_by_user(context, From 26cdebcf742c5fea533c9947ef7278948a772e29 Mon Sep 17 00:00:00 2001 From: Cory Wright Date: Thu, 30 Dec 2010 12:10:31 -0500 Subject: [PATCH 46/86] Mention Authors and .mailmap files in Developer Guide --- doc/source/community.rst | 3 ++- doc/source/devref/development.environment.rst | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/doc/source/community.rst b/doc/source/community.rst index bfb93414ce1e..01ff5f055aa2 100644 --- a/doc/source/community.rst +++ b/doc/source/community.rst @@ -35,7 +35,8 @@ Contributing Code To contribute code, sign up for a Launchpad account and sign a contributor license agreement, available on the `OpenStack Wiki `_. Once the CLA is signed you -can contribute code through the Bazaar version control system which is related to your Launchpad account. +can contribute code through the Bazaar version control system which is related to your Launchpad +account. See the :doc:`devref/development.environment` page to get started. #openstack on Freenode IRC Network ---------------------------------- diff --git a/doc/source/devref/development.environment.rst b/doc/source/devref/development.environment.rst index 6344c5382ced..9e189f04ef94 100644 --- a/doc/source/devref/development.environment.rst +++ b/doc/source/devref/development.environment.rst @@ -88,7 +88,12 @@ Here's how to get the latest code:: source .nova_venv/bin/activate ./run_tests.sh -And then you can do cleaning work or hack hack hack with a branched named cleaning:: +Then you can do cleaning work or hack hack hack with a branched named cleaning. + +Contributing Your Work +---------------------- + +Once your work is complete you may wish to contribute it to the project. Add your name and email address to the `Authors` file (as well as the `.mailmap` file if you use multiple email addresses), and push the branch to Launchpad:: bzr push lp:~launchpaduserid/nova/cleaning From 384f39966b6387fcc9466f0b42bcc0ffaf49ba4c Mon Sep 17 00:00:00 2001 From: Cory Wright Date: Thu, 30 Dec 2010 12:25:14 -0500 Subject: [PATCH 47/86] Note that contributors are required to be listed in Authors file before work can be merged into trunk --- doc/source/devref/development.environment.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/devref/development.environment.rst b/doc/source/devref/development.environment.rst index 9e189f04ef94..3de2e228748b 100644 --- a/doc/source/devref/development.environment.rst +++ b/doc/source/devref/development.environment.rst @@ -93,7 +93,7 @@ Then you can do cleaning work or hack hack hack with a branched named cleaning. Contributing Your Work ---------------------- -Once your work is complete you may wish to contribute it to the project. Add your name and email address to the `Authors` file (as well as the `.mailmap` file if you use multiple email addresses), and push the branch to Launchpad:: +Once your work is complete you may wish to contribute it to the project. Add your name and email address to the `Authors` file, and also to the `.mailmap` file if you use multiple email addresses. Your contributions can not be merged into trunk unless you are listed in the Authors file. Now, push the branch to Launchpad:: bzr push lp:~launchpaduserid/nova/cleaning From 71d715d422a746f4951877d8ff76e0ace355281e Mon Sep 17 00:00:00 2001 From: Eric Day Date: Thu, 30 Dec 2010 15:43:41 -0800 Subject: [PATCH 48/86] Moved network operation code in ec2 api into a generic network API class. Removed a circular dependency with compute/quota. --- nova/api/ec2/cloud.py | 59 ++++-------------------------- nova/compute/__init__.py | 20 ++++++---- nova/network/__init__.py | 79 ++++++++++++++++++++++++++++++++++------ nova/quota.py | 8 ++-- nova/tests/test_quota.py | 6 +-- 5 files changed, 93 insertions(+), 79 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 5ffc301be3f8..d68c598e9090 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -36,7 +36,7 @@ from nova import crypto from nova import db from nova import exception from nova import flags -from nova import quota +from nova import network from nova import rpc from nova import utils from nova import volume @@ -87,11 +87,10 @@ class CloudController(object): sent to the other nodes. """ def __init__(self): - self.network_manager = utils.import_object(FLAGS.network_manager) self.image_service = utils.import_object(FLAGS.image_service) + self.network_api = network.API() self.volume_api = volume.API() - self.compute_api = compute.API(self.network_manager, - self.image_service, + self.compute_api = compute.API(self.image_service, self.network_api, self.volume_api) self.setup() @@ -629,64 +628,20 @@ class CloudController(object): return {'addressesSet': addresses} def allocate_address(self, context, **kwargs): - # check quota - if quota.allowed_floating_ips(context, 1) < 1: - logging.warn(_("Quota exceeeded for %s, tried to allocate " - "address"), - context.project_id) - raise quota.QuotaError(_("Address quota exceeded. You cannot " - "allocate any more addresses")) - # NOTE(vish): We don't know which network host should get the ip - # when we allocate, so just send it to any one. This - # will probably need to move into a network supervisor - # at some point. - public_ip = rpc.call(context, - FLAGS.network_topic, - {"method": "allocate_floating_ip", - "args": {"project_id": context.project_id}}) + public_ip = self.network_api.allocate_floating_ip(context) return {'addressSet': [{'publicIp': public_ip}]} def release_address(self, context, public_ip, **kwargs): - floating_ip_ref = db.floating_ip_get_by_address(context, public_ip) - # NOTE(vish): We don't know which network host should get the ip - # when we deallocate, so just send it to any one. This - # will probably need to move into a network supervisor - # at some point. - rpc.cast(context, - FLAGS.network_topic, - {"method": "deallocate_floating_ip", - "args": {"floating_address": floating_ip_ref['address']}}) + self.network_api.release_floating_ip(context, public_ip) return {'releaseResponse': ["Address released."]} def associate_address(self, context, instance_id, public_ip, **kwargs): instance_id = ec2_id_to_id(instance_id) - instance_ref = self.compute_api.get(context, instance_id) - fixed_address = db.instance_get_fixed_address(context, - instance_ref['id']) - floating_ip_ref = db.floating_ip_get_by_address(context, public_ip) - # NOTE(vish): Perhaps we should just pass this on to compute and - # let compute communicate with network. - network_topic = self.compute_api.get_network_topic(context, - instance_id) - rpc.cast(context, - network_topic, - {"method": "associate_floating_ip", - "args": {"floating_address": floating_ip_ref['address'], - "fixed_address": fixed_address}}) + self.compute_api.associate_floating_ip(context, instance_id, public_ip) return {'associateResponse': ["Address associated."]} def disassociate_address(self, context, public_ip, **kwargs): - floating_ip_ref = db.floating_ip_get_by_address(context, public_ip) - # NOTE(vish): Get the topic from the host name of the network of - # the associated fixed ip. - if not floating_ip_ref.get('fixed_ip'): - raise exception.ApiError('Address is not associated.') - host = floating_ip_ref['fixed_ip']['network']['host'] - topic = db.queue_get_for(context, FLAGS.network_topic, host) - rpc.cast(context, - topic, - {"method": "disassociate_floating_ip", - "args": {"floating_address": floating_ip_ref['address']}}) + self.network_api.disassociate_floating_ip(context, public_ip) return {'disassociateResponse': ["Address disassociated."]} def run_instances(self, context, **kwargs): diff --git a/nova/compute/__init__.py b/nova/compute/__init__.py index fd1cdcd1beb0..14c242641ab1 100644 --- a/nova/compute/__init__.py +++ b/nova/compute/__init__.py @@ -27,6 +27,7 @@ import time from nova import db from nova import exception from nova import flags +from nova import network from nova import quota from nova import rpc from nova import utils @@ -45,14 +46,14 @@ def generate_default_hostname(instance_id): class API(base.Base): """API for interacting with the compute manager.""" - def __init__(self, network_manager=None, image_service=None, - volume_api=None, **kwargs): - if not network_manager: - network_manager = utils.import_object(FLAGS.network_manager) - self.network_manager = network_manager + def __init__(self, image_service=None, network_api=None, volume_api=None, + **kwargs): if not image_service: image_service = utils.import_object(FLAGS.image_service) self.image_service = image_service + if not network_api: + network_api = network.API() + self.network_api = network_api if not volume_api: volume_api = volume.API() self.volume_api = volume_api @@ -83,8 +84,9 @@ class API(base.Base): """Create the number of instances requested if quota and other arguments check out ok.""" + type_data = instance_types.INSTANCE_TYPES[instance_type] num_instances = quota.allowed_instances(context, max_count, - instance_type) + type_data['vcpus']) if num_instances < min_count: logging.warn("Quota exceeeded for %s, tried to run %s instances", context.project_id, min_count) @@ -127,7 +129,6 @@ class API(base.Base): key_pair = db.key_pair_get(context, context.user_id, key_name) key_data = key_pair['public_key'] - type_data = instance_types.INSTANCE_TYPES[instance_type] base_options = { 'reservation_id': utils.generate_uid('r'), 'image_id': image_id, @@ -336,3 +337,8 @@ class API(base.Base): "args": {"instance_id": instance['id'], "volume_id": volume_id}}) return instance + + def associate_floating_ip(self, context, instance_id, address): + instance = self.get(context, instance_id) + self.network_api.associate_floating_ip(context, address, + instance['fixed_ip']) diff --git a/nova/network/__init__.py b/nova/network/__init__.py index dcc54db094a8..cbd912047cab 100644 --- a/nova/network/__init__.py +++ b/nova/network/__init__.py @@ -17,16 +17,71 @@ # under the License. """ -:mod:`nova.network` -- Network Nodes -===================================================== - -.. automodule:: nova.network - :platform: Unix - :synopsis: Network is responsible for managing networking -.. moduleauthor:: Jesse Andrews -.. moduleauthor:: Devin Carlen -.. moduleauthor:: Vishvananda Ishaya -.. moduleauthor:: Joshua McKenty -.. moduleauthor:: Manish Singh -.. moduleauthor:: Andy Smith +Handles all requests relating to instances (guest vms). """ + +import logging + +from nova import db +from nova import flags +from nova import quota +from nova import rpc +from nova.db import base + +FLAGS = flags.FLAGS + + +class API(base.Base): + """API for interacting with the network manager.""" + + def allocate_floating_ip(self, context): + if quota.allowed_floating_ips(context, 1) < 1: + logging.warn(_("Quota exceeeded for %s, tried to allocate " + "address"), + context.project_id) + raise quota.QuotaError(_("Address quota exceeded. You cannot " + "allocate any more addresses")) + # NOTE(vish): We don't know which network host should get the ip + # when we allocate, so just send it to any one. This + # will probably need to move into a network supervisor + # at some point. + return rpc.call(context, + FLAGS.network_topic, + {"method": "allocate_floating_ip", + "args": {"project_id": context.project_id}}) + + def release_floating_ip(self, context, address): + floating_ip = self.db.floating_ip_get_by_address(context, address) + # NOTE(vish): We don't know which network host should get the ip + # when we deallocate, so just send it to any one. This + # will probably need to move into a network supervisor + # at some point. + rpc.cast(context, + FLAGS.network_topic, + {"method": "deallocate_floating_ip", + "args": {"floating_address": floating_ip['address']}}) + + def associate_floating_ip(self, context, floating_ip, fixed_ip): + if isinstance(fixed_ip, str) or isinstance(fixed_ip, unicode): + fixed_ip = self.db.fixed_ip_get_by_address(context, fixed_ip) + floating_ip = self.db.floating_ip_get_by_address(context, floating_ip) + # NOTE(vish): Perhaps we should just pass this on to compute and + # let compute communicate with network. + host = fixed_ip['network']['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.network_topic, host), + {"method": "associate_floating_ip", + "args": {"floating_address": floating_ip['address'], + "fixed_address": fixed_ip['address']}}) + + def disassociate_floating_ip(self, context, address): + floating_ip = self.db.floating_ip_get_by_address(context, address) + if not floating_ip.get('fixed_ip'): + raise exception.ApiError('Address is not associated.') + # NOTE(vish): Get the topic from the host name of the network of + # the associated fixed ip. + host = floating_ip['fixed_ip']['network']['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.network_topic, host), + {"method": "disassociate_floating_ip", + "args": {"floating_address": floating_ip['address']}}) diff --git a/nova/quota.py b/nova/quota.py index f6ca9f77c733..12dbfcf43862 100644 --- a/nova/quota.py +++ b/nova/quota.py @@ -22,7 +22,6 @@ Quotas for instances, volumes, and floating ips from nova import db from nova import exception from nova import flags -from nova.compute import instance_types FLAGS = flags.FLAGS @@ -54,7 +53,7 @@ def get_quota(context, project_id): return rval -def allowed_instances(context, num_instances, instance_type): +def allowed_instances(context, num_instances, cores_per_instance): """Check quota and return min(num_instances, allowed_instances)""" project_id = context.project_id context = context.elevated() @@ -63,10 +62,9 @@ def allowed_instances(context, num_instances, instance_type): quota = get_quota(context, project_id) allowed_instances = quota['instances'] - used_instances allowed_cores = quota['cores'] - used_cores - type_cores = instance_types.INSTANCE_TYPES[instance_type]['vcpus'] - num_cores = num_instances * type_cores + num_cores = num_instances * cores_per_instance allowed_instances = min(allowed_instances, - int(allowed_cores // type_cores)) + int(allowed_cores // cores_per_instance)) return min(num_instances, allowed_instances) diff --git a/nova/tests/test_quota.py b/nova/tests/test_quota.py index 8cf2a5e54652..c158187746b3 100644 --- a/nova/tests/test_quota.py +++ b/nova/tests/test_quota.py @@ -78,14 +78,14 @@ class QuotaTestCase(test.TestCase): def test_quota_overrides(self): """Make sure overriding a projects quotas works""" - num_instances = quota.allowed_instances(self.context, 100, 'm1.small') + num_instances = quota.allowed_instances(self.context, 100, 1) self.assertEqual(num_instances, 2) db.quota_create(self.context, {'project_id': self.project.id, 'instances': 10}) - num_instances = quota.allowed_instances(self.context, 100, 'm1.small') + num_instances = quota.allowed_instances(self.context, 100, 1) self.assertEqual(num_instances, 4) db.quota_update(self.context, self.project.id, {'cores': 100}) - num_instances = quota.allowed_instances(self.context, 100, 'm1.small') + num_instances = quota.allowed_instances(self.context, 100, 1) self.assertEqual(num_instances, 10) db.quota_destroy(self.context, self.project.id) From 66f8e28fb4f4a898803ac6a38974a9fa804612d0 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Thu, 30 Dec 2010 18:12:57 -0600 Subject: [PATCH 49/86] completed the basic xenstore read/write/delete functionality --- nova/virt/xenapi/vmops.py | 222 ++++++++++++++++-- nova/virt/xenapi_conn.py | 28 ++- .../xenapi/etc/xapi.d/plugins/xenstore.py | 158 +++++++++++++ 3 files changed, 378 insertions(+), 30 deletions(-) create mode 100755 plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 0517209e2086..5c9455db3c28 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -1,6 +1,7 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright (c) 2010 Citrix Systems, Inc. +# Copyright 2010 OpenStack LLC. # # 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 @@ -18,6 +19,7 @@ Management class for VM-related functions (spawn, reboot, etc). """ +import json import logging from nova import db @@ -36,7 +38,6 @@ class VMOps(object): """ Management class for VM-related tasks """ - def __init__(self, session): self.XenAPI = session.get_imported_xenapi() self._session = session @@ -120,6 +121,20 @@ class VMOps(object): timer.f = _wait_for_boot return timer.start(interval=0.5, now=True) + def _get_vm_opaque_ref(self, instance_or_vm): + """Refactored out the common code of many methods that receive either + a vm name or a vm instance, and want a vm instance in return. + """ + try: + instance_name = instance_or_vm.name + vm = VMHelper.lookup(self._session, instance_name) + except AttributeError: + # A vm opaque ref was passed + vm = instance_or_vm + if vm is None: + raise Exception(_('Instance not present %s') % instance_name) + return vm + def snapshot(self, instance, name): """ Create snapshot from a running VM instance @@ -168,11 +183,7 @@ class VMOps(object): def reboot(self, instance): """Reboot VM instance""" - instance_name = instance.name - vm = VMHelper.lookup(self._session, instance_name) - if vm is None: - raise exception.NotFound(_('instance not' - ' found %s') % instance_name) + vm = self._get_vm_opaque_ref(instance) task = self._session.call_xenapi('Async.VM.clean_reboot', vm) self._session.wait_for_task(instance.id, task) @@ -215,27 +226,19 @@ class VMOps(object): ret = None try: ret = self._session.wait_for_task(instance_id, task) - except XenAPI.Failure, exc: + except self.XenAPI.Failure, exc: logging.warn(exc) callback(ret) def pause(self, instance, callback): """Pause VM instance""" - instance_name = instance.name - vm = VMHelper.lookup(self._session, instance_name) - if vm is None: - raise exception.NotFound(_('Instance not' - ' found %s') % instance_name) + vm = self._get_vm_opaque_ref(instance) task = self._session.call_xenapi('Async.VM.pause', vm) self._wait_with_callback(instance.id, task, callback) def unpause(self, instance, callback): """Unpause VM instance""" - instance_name = instance.name - vm = VMHelper.lookup(self._session, instance_name) - if vm is None: - raise exception.NotFound(_('Instance not' - ' found %s') % instance_name) + vm = self._get_vm_opaque_ref(instance) task = self._session.call_xenapi('Async.VM.unpause', vm) self._wait_with_callback(instance.id, task, callback) @@ -270,9 +273,7 @@ class VMOps(object): def get_diagnostics(self, instance_id): """Return data about VM diagnostics""" - vm = VMHelper.lookup(self._session, instance_id) - if vm is None: - raise exception.NotFound(_("Instance not found %s") % instance_id) + vm = self._get_vm_opaque_ref(instance) rec = self._session.get_xenapi().VM.get_record(vm) return VMHelper.compile_diagnostics(self._session, rec) @@ -280,3 +281,184 @@ class VMOps(object): """Return snapshot of console""" # TODO: implement this to fix pylint! return 'FAKE CONSOLE OUTPUT of instance' + + def list_from_xenstore(self, vm, path): + """Runs the xenstore-ls command to get a listing of all records + from 'path' downward. Returns a dict with the sub-paths as keys, + and the value stored in those paths as values. If nothing is + found at that path, returns None. + """ + ret = self._make_xenstore_call('list_records', vm, path) + try: + return json.loads(ret) + except ValueError: + # Not a valid JSON value + return ret + + def read_from_xenstore(self, vm, path): + """Returns the value stored in the xenstore record for the given VM + at the specified location. A XenAPIPlugin.PluginError will be raised + if any error is encountered in the read process. + """ + try: + ret = self._make_xenstore_call('read_record', vm, path, + {'ignore_missing_path': 'True'}) + except self.XenAPI.Failure, e: + print "XENERR", e + return None + except StandardError, e: + print "ERR", type(e), e, e.msg + return None + try: + return json.loads(ret) + except ValueError: + # Not a JSON object + if ret == "None": + # Can't marshall None over RPC calls. + return None + return ret + + def write_to_xenstore(self, vm, path, value): + """Writes the passed value to the xenstore record for the given VM + at the specified location. A XenAPIPlugin.PluginError will be raised + if any error is encountered in the write process. + """ + return self._make_xenstore_call('write_record', vm, path, {'value': json.dumps(value)}) + + def clear_xenstore(self, vm, path): + """Deletes the VM's xenstore record for the specified path. + If there is no such record, the request is ignored. + """ + self._make_xenstore_call('delete_record', vm, path) + + def _make_xenstore_call(self, method, vm, path, addl_args={}): + """Handles calls to the xenstore xenapi plugin.""" + return self._make_plugin_call('xenstore.py', method=method, vm=vm, path=path, + addl_args=addl_args) + + def _make_plugin_call(self, plugin, method, vm, path, addl_args={}): + """Abstracts out the process of calling a method of a xenapi plugin. + Any errors raised by the plugin will in turn raise a RuntimeError here. + """ + vm = self._get_vm_opaque_ref(vm) + rec = self._session.get_xenapi().VM.get_record(vm) + args = {'dom_id': rec['domid'], 'path': path} + args.update(addl_args) + # If the 'testing_mode' attribute is set, add that to the args. + if getattr(self, 'testing_mode', False): + args['testing_mode'] = 'true' + try: + task = self._session.async_call_plugin(plugin, method, args) + ret = self._session.wait_for_task(0, task) + except self.XenAPI.Failure, e: + raise RuntimeError("%s" % e.details[-1]) + return ret + + def add_to_xenstore(self, vm, path, key, value): + """Adds the passed key/value pair to the xenstore record for + the given VM at the specified location. A XenAPIPlugin.PluginError + will be raised if any error is encountered in the write process. + """ + current = self.read_from_xenstore(vm, path) + if not current: + # Nothing at that location + current = {key: value} + else: + current[key] = value + self.write_to_xenstore(vm, path, current) + + def remove_from_xenstore(self, vm, path, key_or_keys): + """Takes either a single key or a list of keys and removes + them from the xenstoreirecord data for the given VM. + If the key doesn't exist, the request is ignored. + """ + current = self.list_from_xenstore(vm, path) + if not current: + return + if isinstance(key_or_keys, basestring): + keys = [key_or_keys] + else: + keys = key_or_keys + keys.sort(lambda x,y: cmp(y.count('/'), x.count('/'))) + for key in keys: + if path: + keypath = "%s/%s" % (path, key) + else: + keypath = key + self._make_xenstore_call('delete_record', vm, keypath) + + + ######################################################################## + ###### The following methods interact with the xenstore parameter + ###### record, not the live xenstore. They were created before I + ###### knew the difference, and are left in here in case they prove + ###### to be useful. They all have '_param' added to their method + ###### names to distinguish them. (dabo) + ######################################################################## + def read_partial_from_param_xenstore(self, instance_or_vm, key_prefix): + """Returns a dict of all the keys in the xenstore parameter record + for the given instance that begin with the key_prefix. + """ + data = self.read_from_param_xenstore(instance_or_vm) + badkeys = [k for k in data.keys() + if not k.startswith(key_prefix)] + for badkey in badkeys: + del data[badkey] + return data + + def read_from_param_xenstore(self, instance_or_vm, keys=None): + """Returns the xenstore parameter record data for the specified VM instance + as a dict. Accepts an optional key or list of keys; if a value for 'keys' + is passed, the returned dict is filtered to only return the values + for those keys. + """ + vm = self._get_vm_opaque_ref(instance_or_vm) + data = self._session.call_xenapi_request('VM.get_xenstore_data', (vm, )) + ret = {} + if keys is None: + keys = data.keys() + elif isinstance(keys, basestring): + keys = [keys] + for key in keys: + raw = data.get(key) + if raw: + ret[key] = json.loads(raw) + else: + ret[key] = raw + return ret + + def add_to_param_xenstore(self, instance_or_vm, key, val): + """Takes a key/value pair and adds it to the xenstore parameter + record for the given vm instance. If the key exists in xenstore, + it is overwritten""" + vm = self._get_vm_opaque_ref(instance_or_vm) + self.remove_from_param_xenstore(instance_or_vm, key) + jsonval = json.dumps(val) + self._session.call_xenapi_request('VM.add_to_xenstore_data', + (vm, key, jsonval)) + + def write_to_param_xenstore(self, instance_or_vm, mapping): + """Takes a dict and writes each key/value pair to the xenstore + parameter record for the given vm instance. Any existing data for + those keys is overwritten. + """ + for k, v in mapping.iteritems(): + self.add_to_param_xenstore(instance_or_vm, k, v) + + def remove_from_param_xenstore(self, instance_or_vm, key_or_keys): + """Takes either a single key or a list of keys and removes + them from the xenstore parameter record data for the given VM. + If the key doesn't exist, the request is ignored. + """ + vm = self._get_vm_opaque_ref(instance_or_vm) + if isinstance(key_or_keys, basestring): + keys = [key_or_keys] + else: + keys = key_or_keys + for key in keys: + self._session.call_xenapi_request('VM.remove_from_xenstore_data', (vm, key)) + + def clear_param_xenstore(self, instance_or_vm): + """Removes all data from the xenstore parameter record for this VM.""" + self.write_to_param_xenstore(instance_or_vm, {}) + ######################################################################## diff --git a/nova/virt/xenapi_conn.py b/nova/virt/xenapi_conn.py index d12e78c67f09..863461ee7820 100644 --- a/nova/virt/xenapi_conn.py +++ b/nova/virt/xenapi_conn.py @@ -1,6 +1,7 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright (c) 2010 Citrix Systems, Inc. +# Copyright 2010 OpenStack LLC. # # 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 @@ -19,15 +20,15 @@ A connection to XenServer or Xen Cloud Platform. The concurrency model for this class is as follows: -All XenAPI calls are on a thread (using t.i.t.deferToThread, via the decorator -deferredToThread). They are remote calls, and so may hang for the usual -reasons. They should not be allowed to block the reactor thread. +All XenAPI calls are on a green thread (using eventlet's "tpool" +thread pool). They are remote calls, and so may hang for the usual +reasons. All long-running XenAPI calls (VM.start, VM.reboot, etc) are called async -(using XenAPI.VM.async_start etc). These return a task, which can then be -polled for completion. Polling is handled using reactor.callLater. +(using XenAPI.VM.async_start etc). These return a task, which can then be +polled for completion. -This combination of techniques means that we don't block the reactor thread at +This combination of techniques means that we don't block the main thread at all, and at the same time we don't hold lots of threads waiting for long-running operations. @@ -81,7 +82,7 @@ flags.DEFINE_string('xenapi_connection_password', flags.DEFINE_float('xenapi_task_poll_interval', 0.5, 'The interval used for polling of remote tasks ' - '(Async.VM.start, etc). Used only if ' + '(Async.VM.start, etc). Used only if ' 'connection_type=xenapi.') flags.DEFINE_float('xenapi_vhd_coalesce_poll_interval', 5.0, @@ -213,6 +214,14 @@ class XenAPISession(object): f = f.__getattr__(m) return tpool.execute(f, *args) + def call_xenapi_request(self, method, *args): + """Some interactions with dom0, such as interacting with xenstore's + param record, require using the xenapi_request method of the session + object. This wraps that call on a background thread. + """ + f = self._session.xenapi_request + return tpool.execute(f, method, *args) + def async_call_plugin(self, plugin, fn, args): """Call Async.host.call_plugin on a background thread.""" return tpool.execute(self._unwrap_plugin_exceptions, @@ -222,7 +231,6 @@ class XenAPISession(object): def wait_for_task(self, id, task): """Return the result of the given task. The task is polled until it completes.""" - done = event.Event() loop = utils.LoopingCall(self._poll_task, id, task, done) loop.start(FLAGS.xenapi_task_poll_interval, now=True) @@ -235,7 +243,7 @@ class XenAPISession(object): return self.XenAPI.Session(url) def _poll_task(self, id, task, done): - """Poll the given XenAPI task, and fire the given Deferred if we + """Poll the given XenAPI task, and fire the given action if we get a result.""" try: name = self._session.xenapi.task.get_name_label(task) @@ -290,7 +298,7 @@ class XenAPISession(object): def _parse_xmlrpc_value(val): - """Parse the given value as if it were an XML-RPC value. This is + """Parse the given value as if it were an XML-RPC value. This is sometimes used as the format for the task.result field.""" if not val: return val diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py new file mode 100755 index 000000000000..3b9d65b856f7 --- /dev/null +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py @@ -0,0 +1,158 @@ +#!/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 reading/writing information to xenstore +# + +try: + import json +except ImportError: + import simplejson as json +import subprocess + +import XenAPIPlugin + +from pluginlib_nova import * +configure_logging("xenstore") + + +def read_record(self, arg_dict): + """Returns the value stored at the given path for the given dom_id. + These must be encoded as key/value pairs in arg_dict. You can + optinally include a key 'ignore_missing_path'; if this is present + and boolean True, attempting to read a non-existent path will return + the string 'None' instead of raising an exception. + """ + cmd = "xenstore-read /local/domain/%(dom_id)s/%(path)s" % arg_dict + try: + return _run_command(cmd).rstrip("\n") + except PluginError, e: + if arg_dict.get("ignore_missing_path", False): + cmd = "xenstore-exists /local/domain/%(dom_id)s/%(path)s; echo $?" % arg_dict + ret = _run_command(cmd).strip() + # If the path exists, the cmd should return "0" + if ret != "0": + # No such path, so ignore the error + return "None" + # Either we shouldn't ignore path errors, or another + # error was hit. Re-raise. + raise + +def write_record(self, arg_dict): + """Writes to xenstore at the specified path. If there is information + already stored in that location, it is overwritten. As in read_record, + the dom_id and path must be specified in the arg_dict; additionally, + you must specify a 'value' key, whose value must be a string. Typically, + you can json-ify more complex values and store the json output. + """ + cmd = "xenstore-write /local/domain/%(dom_id)s/%(path)s '%(value)s'" % arg_dict + _run_command(cmd) + return arg_dict["value"] + +def list_records(self, arg_dict): + """Returns all the stored data at or below the given path for the + given dom_id. The data is returned as a json-ified dict, with the + path as the key and the stored value as the value. If the path + doesn't exist, an empty dict is returned. + """ + cmd = "xenstore-ls /local/domain/%(dom_id)s/%(path)s" % arg_dict + cmd = cmd.rstrip("/") + try: + recs = _run_command(cmd) + except PluginError, e: + if "No such file or directory" in "%s" % e: + # Path doesn't exist. + return json.dumps({}) + raise + base_path = arg_dict["path"] + paths = _paths_from_ls(recs) + ret = {} + for path in paths: + if base_path: + arg_dict["path"] = "%s/%s" % (base_path, path) + else: + arg_dict["path"] = path + rec = read_record(self, arg_dict) + try: + val = json.loads(rec) + except ValueError: + val = rec + ret[path] = val + return json.dumps(ret) + +def delete_record(self, arg_dict): + """Just like it sounds: it removes the record for the specified + VM and the specified path from xenstore. + """ + cmd = "xenstore-rm /local/domain/%(dom_id)s/%(path)s" % arg_dict + return _run_command(cmd) + +def _paths_from_ls(recs): + """The xenstore-ls command returns a listing that isn't terribly + useful. This method cleans that up into a dict with each path + as the key, and the associated string as the value. + """ + ret = {} + last_nm = "" + level = 0 + path = [] + ret = [] + for ln in recs.splitlines(): + nm, val = ln.rstrip().split(" = ") + barename = nm.lstrip() + this_level = len(nm) - len(barename) + if this_level == 0: + ret.append(barename) + level = 0 + path = [] + elif this_level == level: + # child of same parent + ret.append("%s/%s" % ("/".join(path), barename)) + elif this_level > level: + path.append(last_nm) + ret.append("%s/%s" % ("/".join(path), barename)) + level = this_level + elif this_level < level: + path = path[:this_level] + ret.append("%s/%s" % ("/".join(path), barename)) + level = this_level + last_nm = barename + return ret + +def _run_command(cmd): + """Abstracts out the basics of issuing system commands. If the command + returns anything in stderr, a PluginError is raised with that information. + Otherwise, the output from stdout is returned. + """ + pipe = subprocess.PIPE + proc = subprocess.Popen([cmd], shell=True, stdin=pipe, stdout=pipe, stderr=pipe, close_fds=True) + proc.wait() + err = proc.stderr.read() + if err: + raise PluginError(err) + return proc.stdout.read() + + +if __name__ == "__main__": + XenAPIPlugin.dispatch( + {"read_record": read_record, + "write_record": write_record, + "list_records": list_records, + "delete_record": delete_record}) From b5f5ec40bbc6b898ac73444e9a0f0372c92fc12a Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Thu, 30 Dec 2010 18:25:30 -0600 Subject: [PATCH 50/86] removed some debugging code left in previous push. --- nova/virt/xenapi/vmops.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index 5c9455db3c28..c46a881a894a 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -304,10 +304,6 @@ class VMOps(object): ret = self._make_xenstore_call('read_record', vm, path, {'ignore_missing_path': 'True'}) except self.XenAPI.Failure, e: - print "XENERR", e - return None - except StandardError, e: - print "ERR", type(e), e, e.msg return None try: return json.loads(ret) From a89730699db2add131028ccda031ead3526b2c4f Mon Sep 17 00:00:00 2001 From: Devin Carlen Date: Thu, 30 Dec 2010 17:50:32 -0800 Subject: [PATCH 51/86] Removed dependencies on flags.py from adminclient --- nova/adminclient.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/nova/adminclient.py b/nova/adminclient.py index 6ae9f0c0f409..b2609c8c40e2 100644 --- a/nova/adminclient.py +++ b/nova/adminclient.py @@ -23,12 +23,9 @@ import base64 import boto import httplib -from nova import flags from boto.ec2.regioninfo import RegionInfo -FLAGS = flags.FLAGS - DEFAULT_CLC_URL = 'http://127.0.0.1:8773' DEFAULT_REGION = 'nova' @@ -199,8 +196,8 @@ class NovaAdminClient(object): self, clc_url=DEFAULT_CLC_URL, region=DEFAULT_REGION, - access_key=FLAGS.aws_access_key_id, - secret_key=FLAGS.aws_secret_access_key, + access_key=None, + secret_key=None, **kwargs): parts = self.split_clc_url(clc_url) From b097d5a247f95fac180c3270cb1f613edfa46523 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Fri, 31 Dec 2010 04:44:45 -0600 Subject: [PATCH 52/86] Corrected the sloppy import in the xenstore plugin that was copied from other plugins. --- .../xenserver/xenapi/etc/xapi.d/plugins/xenstore.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py index 3b9d65b856f7..e0a1251707ed 100755 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py @@ -29,8 +29,8 @@ import subprocess import XenAPIPlugin -from pluginlib_nova import * -configure_logging("xenstore") +import pluginlib_nova as pluginlib +pluginlib.configure_logging("xenstore") def read_record(self, arg_dict): @@ -43,7 +43,7 @@ def read_record(self, arg_dict): cmd = "xenstore-read /local/domain/%(dom_id)s/%(path)s" % arg_dict try: return _run_command(cmd).rstrip("\n") - except PluginError, e: + except pluginlib.PluginError, e: if arg_dict.get("ignore_missing_path", False): cmd = "xenstore-exists /local/domain/%(dom_id)s/%(path)s; echo $?" % arg_dict ret = _run_command(cmd).strip() @@ -76,7 +76,7 @@ def list_records(self, arg_dict): cmd = cmd.rstrip("/") try: recs = _run_command(cmd) - except PluginError, e: + except pluginlib.PluginError, e: if "No such file or directory" in "%s" % e: # Path doesn't exist. return json.dumps({}) @@ -146,7 +146,7 @@ def _run_command(cmd): proc.wait() err = proc.stderr.read() if err: - raise PluginError(err) + raise pluginlib.PluginError(err) return proc.stdout.read() From f0e4bed6f4bf4ab3835ecd3e54eb9d7ac21dd5f1 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Fri, 31 Dec 2010 07:04:40 -0600 Subject: [PATCH 53/86] fixed pep8 issues --- nova/virt/xenapi/vmops.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index c46a881a894a..20f5e0215c33 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -285,7 +285,7 @@ class VMOps(object): def list_from_xenstore(self, vm, path): """Runs the xenstore-ls command to get a listing of all records from 'path' downward. Returns a dict with the sub-paths as keys, - and the value stored in those paths as values. If nothing is + and the value stored in those paths as values. If nothing is found at that path, returns None. """ ret = self._make_xenstore_call('list_records', vm, path) @@ -319,7 +319,8 @@ class VMOps(object): at the specified location. A XenAPIPlugin.PluginError will be raised if any error is encountered in the write process. """ - return self._make_xenstore_call('write_record', vm, path, {'value': json.dumps(value)}) + return self._make_xenstore_call('write_record', vm, path, + {'value': json.dumps(value)}) def clear_xenstore(self, vm, path): """Deletes the VM's xenstore record for the specified path. @@ -329,8 +330,8 @@ class VMOps(object): def _make_xenstore_call(self, method, vm, path, addl_args={}): """Handles calls to the xenstore xenapi plugin.""" - return self._make_plugin_call('xenstore.py', method=method, vm=vm, path=path, - addl_args=addl_args) + return self._make_plugin_call('xenstore.py', method=method, vm=vm, + path=path, addl_args=addl_args) def _make_plugin_call(self, plugin, method, vm, path, addl_args={}): """Abstracts out the process of calling a method of a xenapi plugin. @@ -375,7 +376,7 @@ class VMOps(object): keys = [key_or_keys] else: keys = key_or_keys - keys.sort(lambda x,y: cmp(y.count('/'), x.count('/'))) + keys.sort(lambda x, y: cmp(y.count('/'), x.count('/'))) for key in keys: if path: keypath = "%s/%s" % (path, key) @@ -383,9 +384,8 @@ class VMOps(object): keypath = key self._make_xenstore_call('delete_record', vm, keypath) - ######################################################################## - ###### The following methods interact with the xenstore parameter + ###### The following methods interact with the xenstore parameter ###### record, not the live xenstore. They were created before I ###### knew the difference, and are left in here in case they prove ###### to be useful. They all have '_param' added to their method @@ -403,13 +403,14 @@ class VMOps(object): return data def read_from_param_xenstore(self, instance_or_vm, keys=None): - """Returns the xenstore parameter record data for the specified VM instance - as a dict. Accepts an optional key or list of keys; if a value for 'keys' - is passed, the returned dict is filtered to only return the values - for those keys. + """Returns the xenstore parameter record data for the specified VM + instance as a dict. Accepts an optional key or list of keys; if a + value for 'keys' is passed, the returned dict is filtered to only + return the values for those keys. """ vm = self._get_vm_opaque_ref(instance_or_vm) - data = self._session.call_xenapi_request('VM.get_xenstore_data', (vm, )) + data = self._session.call_xenapi_request('VM.get_xenstore_data', + (vm, )) ret = {} if keys is None: keys = data.keys() @@ -452,7 +453,8 @@ class VMOps(object): else: keys = key_or_keys for key in keys: - self._session.call_xenapi_request('VM.remove_from_xenstore_data', (vm, key)) + self._session.call_xenapi_request('VM.remove_from_xenstore_data', + (vm, key)) def clear_param_xenstore(self, instance_or_vm): """Removes all data from the xenstore parameter record for this VM.""" From 933531440767f0696e14a73069448d0c3f5ae24e Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Fri, 31 Dec 2010 08:53:01 -0600 Subject: [PATCH 54/86] Added OpenStack's copyright to the xenstore plugin. --- plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py index e0a1251707ed..71ed82d6270f 100755 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py @@ -1,6 +1,7 @@ #!/usr/bin/env python # Copyright (c) 2010 Citrix Systems, Inc. +# Copyright 2010 OpenStack LLC. # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. From a36b38d78ef887e23451c1dd22fdde8615c40b08 Mon Sep 17 00:00:00 2001 From: Eric Day Date: Mon, 3 Jan 2011 11:19:36 -0800 Subject: [PATCH 55/86] Removed compute dependency in quota.py. --- nova/compute/__init__.py | 4 ++-- nova/quota.py | 8 +++----- nova/tests/test_quota.py | 6 +++--- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/nova/compute/__init__.py b/nova/compute/__init__.py index f096dab63d62..7d797282abbb 100644 --- a/nova/compute/__init__.py +++ b/nova/compute/__init__.py @@ -83,8 +83,9 @@ class API(base.Base): """Create the number of instances requested if quota and other arguments check out ok.""" + type_data = instance_types.INSTANCE_TYPES[instance_type] num_instances = quota.allowed_instances(context, max_count, - instance_type) + type_data['vcpus']) if num_instances < min_count: logging.warn("Quota exceeeded for %s, tried to run %s instances", context.project_id, min_count) @@ -127,7 +128,6 @@ class API(base.Base): key_pair = db.key_pair_get(context, context.user_id, key_name) key_data = key_pair['public_key'] - type_data = instance_types.INSTANCE_TYPES[instance_type] base_options = { 'reservation_id': utils.generate_uid('r'), 'image_id': image_id, diff --git a/nova/quota.py b/nova/quota.py index f6ca9f77c733..12dbfcf43862 100644 --- a/nova/quota.py +++ b/nova/quota.py @@ -22,7 +22,6 @@ Quotas for instances, volumes, and floating ips from nova import db from nova import exception from nova import flags -from nova.compute import instance_types FLAGS = flags.FLAGS @@ -54,7 +53,7 @@ def get_quota(context, project_id): return rval -def allowed_instances(context, num_instances, instance_type): +def allowed_instances(context, num_instances, cores_per_instance): """Check quota and return min(num_instances, allowed_instances)""" project_id = context.project_id context = context.elevated() @@ -63,10 +62,9 @@ def allowed_instances(context, num_instances, instance_type): quota = get_quota(context, project_id) allowed_instances = quota['instances'] - used_instances allowed_cores = quota['cores'] - used_cores - type_cores = instance_types.INSTANCE_TYPES[instance_type]['vcpus'] - num_cores = num_instances * type_cores + num_cores = num_instances * cores_per_instance allowed_instances = min(allowed_instances, - int(allowed_cores // type_cores)) + int(allowed_cores // cores_per_instance)) return min(num_instances, allowed_instances) diff --git a/nova/tests/test_quota.py b/nova/tests/test_quota.py index 8cf2a5e54652..c158187746b3 100644 --- a/nova/tests/test_quota.py +++ b/nova/tests/test_quota.py @@ -78,14 +78,14 @@ class QuotaTestCase(test.TestCase): def test_quota_overrides(self): """Make sure overriding a projects quotas works""" - num_instances = quota.allowed_instances(self.context, 100, 'm1.small') + num_instances = quota.allowed_instances(self.context, 100, 1) self.assertEqual(num_instances, 2) db.quota_create(self.context, {'project_id': self.project.id, 'instances': 10}) - num_instances = quota.allowed_instances(self.context, 100, 'm1.small') + num_instances = quota.allowed_instances(self.context, 100, 1) self.assertEqual(num_instances, 4) db.quota_update(self.context, self.project.id, {'cores': 100}) - num_instances = quota.allowed_instances(self.context, 100, 'm1.small') + num_instances = quota.allowed_instances(self.context, 100, 1) self.assertEqual(num_instances, 10) db.quota_destroy(self.context, self.project.id) From 97cfb850033597eebe6be88266cd0e1f457ec9bc Mon Sep 17 00:00:00 2001 From: Chiradeep Vittal Date: Mon, 3 Jan 2011 11:37:07 -0800 Subject: [PATCH 56/86] Merge from trunk: process replaced with util --- nova/virt/images.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/nova/virt/images.py b/nova/virt/images.py index 69838ac5b42b..0ec578b06914 100644 --- a/nova/virt/images.py +++ b/nova/virt/images.py @@ -30,7 +30,7 @@ import urllib2 import urlparse from nova import flags -from nova import process +from nova import utils from nova.auth import manager from nova.auth import signer from nova.objectstore import image @@ -60,9 +60,7 @@ def _fetch_image_no_curl(url, path, headers): while 1: data = urlfile.read(chunk) if not data: - break - f.write(data) - + break f.write(data) urlopened = urllib2.urlopen(request) urlretrieve(urlopened, path) logging.debug("Finished retreving %s -- placed in %s", url, path) @@ -73,7 +71,7 @@ def _fetch_s3_image(image, path, user, project): # This should probably move somewhere else, like e.g. a download_as # method on User objects and at the same time get rewritten to use - # twisted web client. + # a web client. headers = {} headers['Date'] = time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()) @@ -90,9 +88,9 @@ def _fetch_s3_image(image, path, user, project): cmd = ['/usr/bin/curl', '--fail', '--silent', url] for (k, v) in headers.iteritems(): cmd += ['-H', '%s: %s' % (k, v)] - - cmd += ['-o', path] - return process.SharedPool().execute(executable=cmd[0], args=cmd[1:]) + cmd += ['-o', path] + cmd_out = ' '.join(cmd) + return utils.execute(cmd_out) def _fetch_local_image(image, path, user, project): @@ -100,7 +98,7 @@ def _fetch_local_image(image, path, user, project): if sys.platform.startswith('win'): return shutil.copy(source, path) else: - return process.simple_execute('cp %s %s' % (source, path)) + return utils.execute('cp %s %s' % (source, path)) def _image_path(path): From 6a8f011789ddad57726ce55962b51a04a69fe527 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Mon, 3 Jan 2011 16:08:52 -0600 Subject: [PATCH 57/86] Fixes LP688545 --- nova/compute/manager.py | 1 + nova/db/sqlalchemy/__init__.py | 21 ++++++++++++++++++++- nova/flags.py | 2 ++ nova/tests/test_xenapi.py | 19 ++++++++++--------- 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index c9aff75acdf9..6e8f34347825 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -327,6 +327,7 @@ class ComputeManager(manager.Manager): instance_ref["internal_id"]) return self.driver.get_diagnostics(instance_ref) + @exception.wrap_exception def suspend_instance(self, context, instance_id): """suspend the instance with instance_id""" context = context.elevated() diff --git a/nova/db/sqlalchemy/__init__.py b/nova/db/sqlalchemy/__init__.py index 3288ebd20f5e..22aa1cfe6c8b 100644 --- a/nova/db/sqlalchemy/__init__.py +++ b/nova/db/sqlalchemy/__init__.py @@ -19,6 +19,25 @@ """ SQLAlchemy database backend """ +import logging +import time + +from sqlalchemy.exc import OperationalError + +from nova import flags from nova.db.sqlalchemy import models -models.register_models() + +FLAGS = flags.FLAGS + + +for i in xrange(FLAGS.sql_max_retries): + if i > 0: + time.sleep(FLAGS.sql_retry_interval) + + try: + models.register_models() + break + except OperationalError: + logging.exception(_("Data store is unreachable." + " Trying again in %d seconds.") % FLAGS.sql_retry_interval) diff --git a/nova/flags.py b/nova/flags.py index e872ba217403..4b73349270a3 100644 --- a/nova/flags.py +++ b/nova/flags.py @@ -266,6 +266,8 @@ DEFINE_string('sql_connection', DEFINE_string('sql_idle_timeout', '3600', 'timeout for idle sql database connections') +DEFINE_integer('sql_max_retries', 12, 'sql connection attempts') +DEFINE_integer('sql_retry_interval', 10, 'sql connection retry interval') DEFINE_string('compute_manager', 'nova.compute.manager.ComputeManager', 'Manager for compute') diff --git a/nova/tests/test_xenapi.py b/nova/tests/test_xenapi.py index c95a53af3c65..33571dad09be 100644 --- a/nova/tests/test_xenapi.py +++ b/nova/tests/test_xenapi.py @@ -250,15 +250,16 @@ 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' - } + 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 From 8bbcadafbc25c7ab478d6143293232f2cea24411 Mon Sep 17 00:00:00 2001 From: Eric Day Date: Tue, 4 Jan 2011 09:44:15 -0800 Subject: [PATCH 58/86] Removed UUID keys for instance and volume. --- nova/api/ec2/cloud.py | 12 ++++-- nova/db/sqlalchemy/models.py | 64 +++++--------------------------- nova/virt/xenapi/volume_utils.py | 3 ++ 3 files changed, 21 insertions(+), 58 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index a6818855dcd2..b1eaafc8ba2a 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -72,13 +72,17 @@ def _gen_key(context, user_id, key_name): def ec2_id_to_id(ec2_id): - """Convert an ec2 ID and an instance ID.""" - return ec2_id[2:] + """Convert an ec2 ID (i-[base 36 number]) to an instance id (int)""" + return int(ec2_id[2:], 36) def id_to_ec2_id(instance_id): - """Convert an instance ID to an ec2 ID.""" - return "i-%s" % instance_id + """Convert an instance ID (int) to an ec2 ID (i-[base 36 number])""" + digits = [] + while instance_id != 0: + instance_id, remainder = divmod(instance_id, 36) + digits.append('0123456789abcdefghijklmnopqrstuvwxyz'[remainder]) + return "i-%s" % ''.join(reversed(digits)) class CloudController(object): diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 78ab1ba9b539..2acff8e6012f 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -20,17 +20,13 @@ SQLAlchemy models for nova data. """ import datetime -import uuid from sqlalchemy.orm import relationship, backref, object_mapper from sqlalchemy import Column, Integer, String, schema from sqlalchemy import ForeignKey, DateTime, Boolean, Text -from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import relationship, backref, object_mapper from sqlalchemy.schema import ForeignKeyConstraint -from sqlalchemy.types import TypeDecorator, CHAR from nova.db.sqlalchemy.session import get_session @@ -43,46 +39,6 @@ FLAGS = flags.FLAGS BASE = declarative_base() -def make_uuid(): - return uuid.uuid1().hex - - -class GUID(TypeDecorator): - """Platform-independent GUID type. - - Uses Postgresql's UUID type, otherwise uses - CHAR(32), storing as stringified hex values. - - """ - impl = CHAR - - def load_dialect_impl(self, dialect): - if dialect.name == 'postgresql': - return dialect.type_descriptor(UUID()) - else: - return dialect.type_descriptor(CHAR(32)) - - def process_bind_param(self, value, dialect): - if value is None: - return value - elif dialect.name == 'postgresql': - return str(value) - else: - if isinstance(value, int): - return "%.32x" % uuid.UUID(int=value) - elif not isinstance(value, uuid.UUID): - return "%.32x" % uuid.UUID(value) - else: - # hexstring - return "%.32x" % value - - def process_result_value(self, value, dialect): - if value is None: - return value - else: - return uuid.UUID(value).hex - - class NovaBase(object): """Base class for Nova Models.""" __table_args__ = {'mysql_engine': 'InnoDB'} @@ -208,11 +164,11 @@ class Certificate(BASE, NovaBase): class Instance(BASE, NovaBase): """Represents a guest vm.""" __tablename__ = 'instances' - id = Column(GUID, primary_key=True, default=make_uuid) + id = Column(Integer, primary_key=True, autoincrement=True) @property def name(self): - return "instance-%s" % self.id + return "instance-%08x" % self.id admin_pass = Column(String(255)) user_id = Column(String(255)) @@ -284,7 +240,7 @@ class InstanceActions(BASE, NovaBase): """Represents a guest VM's actions and results""" __tablename__ = "instance_actions" id = Column(Integer, primary_key=True) - instance_id = Column(GUID, ForeignKey('instances.id')) + instance_id = Column(Integer, ForeignKey('instances.id')) action = Column(String(255)) error = Column(Text) @@ -293,11 +249,11 @@ class InstanceActions(BASE, NovaBase): class Volume(BASE, NovaBase): """Represents a block storage device that can be attached to a vm.""" __tablename__ = 'volumes' - id = Column(GUID, primary_key=True, default=make_uuid) + id = Column(Integer, primary_key=True, autoincrement=True) @property def name(self): - return "vollume-%s" % self.id + return "volume-%08x" % self.id user_id = Column(String(255)) project_id = Column(String(255)) @@ -305,7 +261,7 @@ class Volume(BASE, NovaBase): host = Column(String(255)) # , ForeignKey('hosts.id')) size = Column(Integer) availability_zone = Column(String(255)) # TODO(vish): foreign key? - instance_id = Column(GUID, ForeignKey('instances.id'), nullable=True) + instance_id = Column(Integer, ForeignKey('instances.id'), nullable=True) instance = relationship(Instance, backref=backref('volumes'), foreign_keys=instance_id, @@ -346,7 +302,7 @@ class ExportDevice(BASE, NovaBase): id = Column(Integer, primary_key=True) shelf_id = Column(Integer) blade_id = Column(Integer) - volume_id = Column(GUID, ForeignKey('volumes.id'), nullable=True) + volume_id = Column(Integer, ForeignKey('volumes.id'), nullable=True) volume = relationship(Volume, backref=backref('export_device', uselist=False), foreign_keys=volume_id, @@ -362,7 +318,7 @@ class IscsiTarget(BASE, NovaBase): id = Column(Integer, primary_key=True) target_num = Column(Integer) host = Column(String(255)) - volume_id = Column(GUID, ForeignKey('volumes.id'), nullable=True) + volume_id = Column(Integer, ForeignKey('volumes.id'), nullable=True) volume = relationship(Volume, backref=backref('iscsi_target', uselist=False), foreign_keys=volume_id, @@ -374,7 +330,7 @@ class SecurityGroupInstanceAssociation(BASE, NovaBase): __tablename__ = 'security_group_instance_association' id = Column(Integer, primary_key=True) security_group_id = Column(Integer, ForeignKey('security_groups.id')) - instance_id = Column(GUID, ForeignKey('instances.id')) + instance_id = Column(Integer, ForeignKey('instances.id')) class SecurityGroup(BASE, NovaBase): @@ -494,7 +450,7 @@ class FixedIp(BASE, NovaBase): address = Column(String(255)) network_id = Column(Integer, ForeignKey('networks.id'), nullable=True) network = relationship(Network, backref=backref('fixed_ips')) - instance_id = Column(GUID, ForeignKey('instances.id'), nullable=True) + instance_id = Column(Integer, ForeignKey('instances.id'), nullable=True) instance = relationship(Instance, backref=backref('fixed_ip', uselist=False), foreign_keys=instance_id, diff --git a/nova/virt/xenapi/volume_utils.py b/nova/virt/xenapi/volume_utils.py index 1ca813bcf8f2..0ad6fd450468 100644 --- a/nova/virt/xenapi/volume_utils.py +++ b/nova/virt/xenapi/volume_utils.py @@ -202,6 +202,9 @@ class VolumeHelper(HelperBase): def _get_volume_id(path): """Retrieve the volume id from device_path""" + # If we have the ID and not a path, just return it. + if isinstance(path, int): + return path # n must contain at least the volume_id # /vol- is for remote volumes # -vol- is for local volumes From 4b0509f014aa164273a0e544441838be5352b1eb Mon Sep 17 00:00:00 2001 From: Eric Day Date: Tue, 4 Jan 2011 12:16:43 -0800 Subject: [PATCH 59/86] Removed leftover UUID reference. --- nova/db/sqlalchemy/api.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index bb84883e7e6a..aaa07e3c9291 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -19,7 +19,6 @@ Implementation of SQLAlchemy backend. """ -import uuid import warnings from nova import db @@ -742,9 +741,6 @@ def instance_get_project_vpn(context, project_id): def instance_get_by_id(context, instance_id): session = get_session() - if type(instance_id) is int: - instance_id = uuid.UUID(int=instance_id).hex - if is_admin_context(context): result = session.query(models.Instance).\ options(joinedload('security_groups')).\ From 2899896d1c7742ad59e2da2d2369bc2ff9526fed Mon Sep 17 00:00:00 2001 From: Eric Day Date: Tue, 4 Jan 2011 12:27:50 -0800 Subject: [PATCH 60/86] Renamed argument to represent possible types in volume_utils. --- nova/virt/xenapi/volume_utils.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/nova/virt/xenapi/volume_utils.py b/nova/virt/xenapi/volume_utils.py index 0ad6fd450468..4bbc41b0323a 100644 --- a/nova/virt/xenapi/volume_utils.py +++ b/nova/virt/xenapi/volume_utils.py @@ -200,18 +200,19 @@ class VolumeHelper(HelperBase): return -1 -def _get_volume_id(path): +def _get_volume_id(path_or_id): """Retrieve the volume id from device_path""" # If we have the ID and not a path, just return it. - if isinstance(path, int): - return path + if isinstance(path_or_id, int): + return path_or_id # n must contain at least the volume_id # /vol- is for remote volumes # -vol- is for local volumes # see compute/manager->setup_compute_volume - volume_id = path[path.find('/vol-') + 1:] - if volume_id == path: - volume_id = path[path.find('-vol-') + 1:].replace('--', '-') + volume_id = path_or_id[path_or_id.find('/vol-') + 1:] + if volume_id == path_or_id: + volume_id = path_or_id[path_or_id.find('-vol-') + 1:] + volume_id = volume_id.replace('--', '-') return volume_id From 02c86d1e1146c1162a36620560eb8116ce8d47f1 Mon Sep 17 00:00:00 2001 From: Ed Leafe Date: Tue, 4 Jan 2011 15:20:10 -0600 Subject: [PATCH 61/86] Made the plugin output fully json-ified, so I could remove the exception handlers in vmops.py. Cleaned up some pep8 issues that weren't caught in earlier runs. --- nova/virt/xenapi/vmops.py | 19 +++------ .../etc/xapi.d/plugins/pluginlib_nova.py | 28 ++++++++----- .../xenapi/etc/xapi.d/plugins/xenstore.py | 39 ++++++++++++++----- 3 files changed, 55 insertions(+), 31 deletions(-) diff --git a/nova/virt/xenapi/vmops.py b/nova/virt/xenapi/vmops.py index d539f9416f6f..b6d6207827c4 100644 --- a/nova/virt/xenapi/vmops.py +++ b/nova/virt/xenapi/vmops.py @@ -289,11 +289,7 @@ class VMOps(object): found at that path, returns None. """ ret = self._make_xenstore_call('list_records', vm, path) - try: - return json.loads(ret) - except ValueError: - # Not a valid JSON value - return ret + return json.loads(ret) def read_from_xenstore(self, vm, path): """Returns the value stored in the xenstore record for the given VM @@ -305,14 +301,11 @@ class VMOps(object): {'ignore_missing_path': 'True'}) except self.XenAPI.Failure, e: return None - try: - return json.loads(ret) - except ValueError: - # Not a JSON object - if ret == "None": - # Can't marshall None over RPC calls. - return None - return ret + ret = json.loads(ret) + if ret == "None": + # Can't marshall None over RPC calls. + return None + return ret def write_to_xenstore(self, vm, path, value): """Writes the passed value to the xenstore record for the given VM diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py index 2d323a0167ba..8e7a829d5a62 100755 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/pluginlib_nova.py @@ -45,6 +45,7 @@ class PluginError(Exception): def __init__(self, *args): Exception.__init__(self, *args) + class ArgumentError(PluginError): """Raised when required arguments are missing, argument values are invalid, or incompatible arguments are given. @@ -67,6 +68,7 @@ def ignore_failure(func, *args, **kwargs): ARGUMENT_PATTERN = re.compile(r'^[a-zA-Z0-9_:\.\-,]+$') + def validate_exists(args, key, default=None): """Validates that a string argument to a RPC method call is given, and matches the shell-safe regex, with an optional default value in case it @@ -76,20 +78,24 @@ def validate_exists(args, key, default=None): """ if key in args: if len(args[key]) == 0: - raise ArgumentError('Argument %r value %r is too short.' % (key, args[key])) + raise ArgumentError('Argument %r value %r is too short.' % + (key, args[key])) if not ARGUMENT_PATTERN.match(args[key]): - raise ArgumentError('Argument %r value %r contains invalid characters.' % (key, args[key])) + raise ArgumentError('Argument %r value %r contains invalid ' + 'characters.' % (key, args[key])) if args[key][0] == '-': - raise ArgumentError('Argument %r value %r starts with a hyphen.' % (key, args[key])) + raise ArgumentError('Argument %r value %r starts with a hyphen.' + % (key, args[key])) return args[key] elif default is not None: return default else: raise ArgumentError('Argument %s is required.' % key) + def validate_bool(args, key, default=None): - """Validates that a string argument to a RPC method call is a boolean string, - with an optional default value in case it does not exist. + """Validates that a string argument to a RPC method call is a boolean + string, with an optional default value in case it does not exist. Returns the python boolean value. """ @@ -99,7 +105,9 @@ def validate_bool(args, key, default=None): elif value.lower() == 'false': return False else: - raise ArgumentError("Argument %s may not take value %r. Valid values are ['true', 'false']." % (key, value)) + raise ArgumentError("Argument %s may not take value %r. " + "Valid values are ['true', 'false']." % (key, value)) + def exists(args, key): """Validates that a freeform string argument to a RPC method call is given. @@ -110,6 +118,7 @@ def exists(args, key): else: raise ArgumentError('Argument %s is required.' % key) + def optional(args, key): """If the given key is in args, return the corresponding value, otherwise return None""" @@ -122,13 +131,14 @@ def get_this_host(session): def get_domain_0(session): this_host_ref = get_this_host(session) - expr = 'field "is_control_domain" = "true" and field "resident_on" = "%s"' % this_host_ref + expr = 'field "is_control_domain" = "true" and field "resident_on" = "%s"' + expr = expr % this_host_ref return session.xenapi.VM.get_all_records_where(expr).keys()[0] def create_vdi(session, sr_ref, name_label, virtual_size, read_only): vdi_ref = session.xenapi.VDI.create( - { 'name_label': name_label, + {'name_label': name_label, 'name_description': '', 'SR': sr_ref, 'virtual_size': str(virtual_size), @@ -138,7 +148,7 @@ def create_vdi(session, sr_ref, name_label, virtual_size, read_only): 'xenstore_data': {}, 'other_config': {}, 'sm_config': {}, - 'tags': [] }) + 'tags': []}) logging.debug('Created VDI %s (%s, %s, %s) on %s.', vdi_ref, name_label, virtual_size, read_only, sr_ref) return vdi_ref diff --git a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py index 71ed82d6270f..695bf3448d69 100755 --- a/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py +++ b/plugins/xenserver/xenapi/etc/xapi.d/plugins/xenstore.py @@ -34,10 +34,17 @@ import pluginlib_nova as pluginlib pluginlib.configure_logging("xenstore") +def jsonify(fnc): + def wrapper(*args, **kwargs): + return json.dumps(fnc(*args, **kwargs)) + return wrapper + + +@jsonify def read_record(self, arg_dict): """Returns the value stored at the given path for the given dom_id. - These must be encoded as key/value pairs in arg_dict. You can - optinally include a key 'ignore_missing_path'; if this is present + These must be encoded as key/value pairs in arg_dict. You can + optinally include a key 'ignore_missing_path'; if this is present and boolean True, attempting to read a non-existent path will return the string 'None' instead of raising an exception. """ @@ -46,16 +53,21 @@ def read_record(self, arg_dict): return _run_command(cmd).rstrip("\n") except pluginlib.PluginError, e: if arg_dict.get("ignore_missing_path", False): - cmd = "xenstore-exists /local/domain/%(dom_id)s/%(path)s; echo $?" % arg_dict + cmd = "xenstore-exists /local/domain/%(dom_id)s/%(path)s; echo $?" + cmd = cmd % arg_dict ret = _run_command(cmd).strip() # If the path exists, the cmd should return "0" if ret != "0": - # No such path, so ignore the error - return "None" + # No such path, so ignore the error and return the + # string 'None', since None can't be marshalled + # over RPC. + return "None" # Either we shouldn't ignore path errors, or another # error was hit. Re-raise. raise + +@jsonify def write_record(self, arg_dict): """Writes to xenstore at the specified path. If there is information already stored in that location, it is overwritten. As in read_record, @@ -63,10 +75,13 @@ def write_record(self, arg_dict): you must specify a 'value' key, whose value must be a string. Typically, you can json-ify more complex values and store the json output. """ - cmd = "xenstore-write /local/domain/%(dom_id)s/%(path)s '%(value)s'" % arg_dict + cmd = "xenstore-write /local/domain/%(dom_id)s/%(path)s '%(value)s'" + cmd = cmd % arg_dict _run_command(cmd) return arg_dict["value"] + +@jsonify def list_records(self, arg_dict): """Returns all the stored data at or below the given path for the given dom_id. The data is returned as a json-ified dict, with the @@ -80,7 +95,8 @@ def list_records(self, arg_dict): except pluginlib.PluginError, e: if "No such file or directory" in "%s" % e: # Path doesn't exist. - return json.dumps({}) + return {} + return str(e) raise base_path = arg_dict["path"] paths = _paths_from_ls(recs) @@ -96,8 +112,10 @@ def list_records(self, arg_dict): except ValueError: val = rec ret[path] = val - return json.dumps(ret) + return ret + +@jsonify def delete_record(self, arg_dict): """Just like it sounds: it removes the record for the specified VM and the specified path from xenstore. @@ -105,6 +123,7 @@ def delete_record(self, arg_dict): cmd = "xenstore-rm /local/domain/%(dom_id)s/%(path)s" % arg_dict return _run_command(cmd) + def _paths_from_ls(recs): """The xenstore-ls command returns a listing that isn't terribly useful. This method cleans that up into a dict with each path @@ -137,13 +156,15 @@ def _paths_from_ls(recs): last_nm = barename return ret + def _run_command(cmd): """Abstracts out the basics of issuing system commands. If the command returns anything in stderr, a PluginError is raised with that information. Otherwise, the output from stdout is returned. """ pipe = subprocess.PIPE - proc = subprocess.Popen([cmd], shell=True, stdin=pipe, stdout=pipe, stderr=pipe, close_fds=True) + proc = subprocess.Popen([cmd], shell=True, stdin=pipe, stdout=pipe, + stderr=pipe, close_fds=True) proc.wait() err = proc.stderr.read() if err: From e97cb0f19f66ee4d28685575cea57b1eb32c4ed3 Mon Sep 17 00:00:00 2001 From: Eric Day Date: Tue, 4 Jan 2011 13:56:36 -0800 Subject: [PATCH 62/86] Moved __init__ api code to api.py and changed allowed_instances quota method argument to accept all type data, not just vcpu count. --- nova/compute/__init__.py | 369 +------------------------------------ nova/compute/api.py | 385 +++++++++++++++++++++++++++++++++++++++ nova/network/__init__.py | 70 +------ nova/network/api.py | 87 +++++++++ nova/quota.py | 6 +- nova/tests/test_quota.py | 10 +- nova/volume/__init__.py | 83 +-------- nova/volume/api.py | 100 ++++++++++ 8 files changed, 585 insertions(+), 525 deletions(-) create mode 100644 nova/compute/api.py create mode 100644 nova/network/api.py create mode 100644 nova/volume/api.py diff --git a/nova/compute/__init__.py b/nova/compute/__init__.py index 9efd90909827..b94f971d1d93 100644 --- a/nova/compute/__init__.py +++ b/nova/compute/__init__.py @@ -16,371 +16,4 @@ # License for the specific language governing permissions and limitations # under the License. -""" -Handles all requests relating to instances (guest vms). -""" - -import datetime -import logging -import time - -from nova import db -from nova import exception -from nova import flags -from nova import network -from nova import quota -from nova import rpc -from nova import utils -from nova import volume -from nova.compute import instance_types -from nova.db import base - -FLAGS = flags.FLAGS - - -def generate_default_hostname(instance_id): - """Default function to generate a hostname given an instance reference.""" - return str(instance_id) - - -class API(base.Base): - """API for interacting with the compute manager.""" - - def __init__(self, image_service=None, network_api=None, volume_api=None, - **kwargs): - if not image_service: - image_service = utils.import_object(FLAGS.image_service) - self.image_service = image_service - if not network_api: - network_api = network.API() - self.network_api = network_api - if not volume_api: - volume_api = volume.API() - self.volume_api = volume_api - super(API, self).__init__(**kwargs) - - def get_network_topic(self, context, instance_id): - try: - instance = self.get(context, instance_id) - except exception.NotFound as e: - logging.warning("Instance %d was not found in get_network_topic", - instance_id) - raise e - - host = instance['host'] - if not host: - raise exception.Error("Instance %d has no host" % instance_id) - topic = self.db.queue_get_for(context, FLAGS.compute_topic, host) - return rpc.call(context, - topic, - {"method": "get_network_topic", "args": {'fake': 1}}) - - def create(self, context, instance_type, - image_id, kernel_id=None, ramdisk_id=None, - min_count=1, max_count=1, - display_name='', display_description='', - key_name=None, key_data=None, security_group='default', - availability_zone=None, user_data=None, - generate_hostname=generate_default_hostname): - """Create the number of instances requested if quota and - other arguments check out ok.""" - - type_data = instance_types.INSTANCE_TYPES[instance_type] - num_instances = quota.allowed_instances(context, max_count, - type_data['vcpus']) - if num_instances < min_count: - logging.warn("Quota exceeeded for %s, tried to run %s instances", - context.project_id, min_count) - raise quota.QuotaError("Instance quota exceeded. You can only " - "run %s more instances of this type." % - num_instances, "InstanceLimitExceeded") - - is_vpn = image_id == FLAGS.vpn_image_id - if not is_vpn: - image = self.image_service.show(context, image_id) - if kernel_id is None: - kernel_id = image.get('kernelId', None) - if ramdisk_id is None: - ramdisk_id = image.get('ramdiskId', None) - # No kernel and ramdisk for raw images - if kernel_id == str(FLAGS.null_kernel): - kernel_id = None - ramdisk_id = None - logging.debug("Creating a raw instance") - # Make sure we have access to kernel and ramdisk (if not raw) - if kernel_id: - self.image_service.show(context, kernel_id) - if ramdisk_id: - self.image_service.show(context, ramdisk_id) - - if security_group is None: - security_group = ['default'] - if not type(security_group) is list: - security_group = [security_group] - - security_groups = [] - self.ensure_default_security_group(context) - for security_group_name in security_group: - group = db.security_group_get_by_name(context, - context.project_id, - security_group_name) - security_groups.append(group['id']) - - if key_data is None and key_name: - key_pair = db.key_pair_get(context, context.user_id, key_name) - key_data = key_pair['public_key'] - - base_options = { - 'reservation_id': utils.generate_uid('r'), - 'image_id': image_id, - 'kernel_id': kernel_id or '', - 'ramdisk_id': ramdisk_id or '', - 'state_description': 'scheduling', - 'user_id': context.user_id, - 'project_id': context.project_id, - 'launch_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()), - 'instance_type': instance_type, - 'memory_mb': type_data['memory_mb'], - 'vcpus': type_data['vcpus'], - 'local_gb': type_data['local_gb'], - 'display_name': display_name, - 'display_description': display_description, - 'user_data': user_data or '', - 'key_name': key_name, - 'key_data': key_data, - 'availability_zone': availability_zone} - - elevated = context.elevated() - instances = [] - logging.debug(_("Going to run %s instances..."), num_instances) - for num in range(num_instances): - instance = dict(mac_address=utils.generate_mac(), - launch_index=num, - **base_options) - instance = self.db.instance_create(context, instance) - instance_id = instance['id'] - - elevated = context.elevated() - if not security_groups: - security_groups = [] - for security_group_id in security_groups: - self.db.instance_add_security_group(elevated, - instance_id, - security_group_id) - - # Set sane defaults if not specified - updates = dict(hostname=generate_hostname(instance_id)) - if 'display_name' not in instance: - updates['display_name'] = "Server %s" % instance_id - - instance = self.update(context, instance_id, **updates) - instances.append(instance) - - logging.debug(_("Casting to scheduler for %s/%s's instance %s"), - context.project_id, context.user_id, instance_id) - rpc.cast(context, - FLAGS.scheduler_topic, - {"method": "run_instance", - "args": {"topic": FLAGS.compute_topic, - "instance_id": instance_id}}) - - return instances - - def ensure_default_security_group(self, context): - """ Create security group for the security context if it - does not already exist - - :param context: the security context - - """ - try: - db.security_group_get_by_name(context, context.project_id, - 'default') - except exception.NotFound: - values = {'name': 'default', - 'description': 'default', - 'user_id': context.user_id, - 'project_id': context.project_id} - db.security_group_create(context, values) - - def update(self, context, instance_id, **kwargs): - """Updates the instance in the datastore. - - :param context: The security context - :param instance_id: ID of the instance to update - :param kwargs: All additional keyword args are treated - as data fields of the instance to be - updated - - :retval None - - """ - return self.db.instance_update(context, instance_id, kwargs) - - def delete(self, context, instance_id): - logging.debug("Going to try and terminate %s" % instance_id) - try: - instance = self.get(context, instance_id) - except exception.NotFound as e: - logging.warning(_("Instance %s was not found during terminate"), - instance_id) - raise e - - if (instance['state_description'] == 'terminating'): - logging.warning(_("Instance %s is already being terminated"), - instance_id) - return - - self.update(context, - instance['id'], - state_description='terminating', - state=0, - terminated_at=datetime.datetime.utcnow()) - - host = instance['host'] - if host: - rpc.cast(context, - self.db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "terminate_instance", - "args": {"instance_id": instance_id}}) - else: - self.db.instance_destroy(context, instance_id) - - def get(self, context, instance_id=None, project_id=None, - reservation_id=None, fixed_ip=None): - """Get one or more instances, possibly filtered by one of the - given parameters. If there is no filter and the context is - an admin, it will retreive all instances in the system.""" - if instance_id is not None: - return self.db.instance_get_by_id(context, instance_id) - if reservation_id is not None: - return self.db.instance_get_all_by_reservation(context, - reservation_id) - if fixed_ip is not None: - return self.db.fixed_ip_get_instance(context, fixed_ip) - if project_id or not context.is_admin: - if not context.project: - return self.db.instance_get_all_by_user(context, - context.user_id) - if project_id is None: - project_id = context.project_id - return self.db.instance_get_all_by_project(context, - project_id) - return self.db.instance_get_all(context) - - def snapshot(self, context, instance_id, name): - """Snapshot the given instance.""" - instance = self.get(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.get(context, instance_id) - host = instance['host'] - rpc.cast(context, - self.db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "reboot_instance", - "args": {"instance_id": instance_id}}) - - def pause(self, context, instance_id): - """Pause the given instance.""" - instance = self.get(context, instance_id) - host = instance['host'] - rpc.cast(context, - self.db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "pause_instance", - "args": {"instance_id": instance_id}}) - - def unpause(self, context, instance_id): - """Unpause the given instance.""" - instance = self.get(context, instance_id) - host = instance['host'] - rpc.cast(context, - self.db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "unpause_instance", - "args": {"instance_id": instance_id}}) - - def get_diagnostics(self, context, instance_id): - """Retrieve diagnostics for the given instance.""" - instance = self.get(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 get_actions(self, context, instance_id): - """Retrieve actions for the given instance.""" - return self.db.instance_get_actions(context, instance_id) - - def suspend(self, context, instance_id): - """suspend the instance with instance_id""" - instance = self.get(context, instance_id) - host = instance['host'] - rpc.cast(context, - self.db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "suspend_instance", - "args": {"instance_id": instance_id}}) - - def resume(self, context, instance_id): - """resume the instance with instance_id""" - instance = self.get(context, instance_id) - host = instance['host'] - rpc.cast(context, - self.db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "resume_instance", - "args": {"instance_id": instance_id}}) - - def rescue(self, context, instance_id): - """Rescue the given instance.""" - instance = self.get(context, instance_id) - host = instance['host'] - rpc.cast(context, - self.db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "rescue_instance", - "args": {"instance_id": instance_id}}) - - def unrescue(self, context, instance_id): - """Unrescue the given instance.""" - instance = self.get(context, instance_id) - host = instance['host'] - rpc.cast(context, - self.db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "unrescue_instance", - "args": {"instance_id": instance_id}}) - - def attach_volume(self, context, instance_id, volume_id, device): - if not re.match("^/dev/[a-z]d[a-z]+$", device): - raise exception.ApiError(_("Invalid device specified: %s. " - "Example device: /dev/vdb") % device) - self.volume_api.check_attach(context, volume_id) - instance = self.get(context, instance_id) - host = instance['host'] - rpc.cast(context, - self.db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "attach_volume", - "args": {"volume_id": volume_id, - "instance_id": instance_id, - "mountpoint": device}}) - - def detach_volume(self, context, volume_id): - instance = self.db.volume_get_instance(context.elevated(), volume_id) - if not instance: - raise exception.ApiError(_("Volume isn't attached to anything!")) - self.volume_api.check_detach(context, volume_id) - host = instance['host'] - rpc.cast(context, - self.db.queue_get_for(context, FLAGS.compute_topic, host), - {"method": "detach_volume", - "args": {"instance_id": instance['id'], - "volume_id": volume_id}}) - return instance - - def associate_floating_ip(self, context, instance_id, address): - instance = self.get(context, instance_id) - self.network_api.associate_floating_ip(context, address, - instance['fixed_ip']) +from nova.compute.api import API diff --git a/nova/compute/api.py b/nova/compute/api.py new file mode 100644 index 000000000000..74d030c4d8ce --- /dev/null +++ b/nova/compute/api.py @@ -0,0 +1,385 @@ +# 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. + +""" +Handles all requests relating to instances (guest vms). +""" + +import datetime +import logging +import time + +from nova import db +from nova import exception +from nova import flags +from nova import network +from nova import quota +from nova import rpc +from nova import utils +from nova import volume +from nova.compute import instance_types +from nova.db import base + +FLAGS = flags.FLAGS + + +def generate_default_hostname(instance_id): + """Default function to generate a hostname given an instance reference.""" + return str(instance_id) + + +class API(base.Base): + """API for interacting with the compute manager.""" + + def __init__(self, image_service=None, network_api=None, volume_api=None, + **kwargs): + if not image_service: + image_service = utils.import_object(FLAGS.image_service) + self.image_service = image_service + if not network_api: + network_api = network.API() + self.network_api = network_api + if not volume_api: + volume_api = volume.API() + self.volume_api = volume_api + super(API, self).__init__(**kwargs) + + def get_network_topic(self, context, instance_id): + try: + instance = self.get(context, instance_id) + except exception.NotFound as e: + logging.warning("Instance %d was not found in get_network_topic", + instance_id) + raise e + + host = instance['host'] + if not host: + raise exception.Error("Instance %d has no host" % instance_id) + topic = self.db.queue_get_for(context, FLAGS.compute_topic, host) + return rpc.call(context, + topic, + {"method": "get_network_topic", "args": {'fake': 1}}) + + def create(self, context, instance_type, + image_id, kernel_id=None, ramdisk_id=None, + min_count=1, max_count=1, + display_name='', display_description='', + key_name=None, key_data=None, security_group='default', + availability_zone=None, user_data=None, + generate_hostname=generate_default_hostname): + """Create the number of instances requested if quota and + other arguments check out ok.""" + + type_data = instance_types.INSTANCE_TYPES[instance_type] + num_instances = quota.allowed_instances(context, max_count, type_data) + if num_instances < min_count: + logging.warn("Quota exceeeded for %s, tried to run %s instances", + context.project_id, min_count) + raise quota.QuotaError("Instance quota exceeded. You can only " + "run %s more instances of this type." % + num_instances, "InstanceLimitExceeded") + + is_vpn = image_id == FLAGS.vpn_image_id + if not is_vpn: + image = self.image_service.show(context, image_id) + if kernel_id is None: + kernel_id = image.get('kernelId', None) + if ramdisk_id is None: + ramdisk_id = image.get('ramdiskId', None) + # No kernel and ramdisk for raw images + if kernel_id == str(FLAGS.null_kernel): + kernel_id = None + ramdisk_id = None + logging.debug("Creating a raw instance") + # Make sure we have access to kernel and ramdisk (if not raw) + if kernel_id: + self.image_service.show(context, kernel_id) + if ramdisk_id: + self.image_service.show(context, ramdisk_id) + + if security_group is None: + security_group = ['default'] + if not type(security_group) is list: + security_group = [security_group] + + security_groups = [] + self.ensure_default_security_group(context) + for security_group_name in security_group: + group = db.security_group_get_by_name(context, + context.project_id, + security_group_name) + security_groups.append(group['id']) + + if key_data is None and key_name: + key_pair = db.key_pair_get(context, context.user_id, key_name) + key_data = key_pair['public_key'] + + base_options = { + 'reservation_id': utils.generate_uid('r'), + 'image_id': image_id, + 'kernel_id': kernel_id or '', + 'ramdisk_id': ramdisk_id or '', + 'state_description': 'scheduling', + 'user_id': context.user_id, + 'project_id': context.project_id, + 'launch_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()), + 'instance_type': instance_type, + 'memory_mb': type_data['memory_mb'], + 'vcpus': type_data['vcpus'], + 'local_gb': type_data['local_gb'], + 'display_name': display_name, + 'display_description': display_description, + 'user_data': user_data or '', + 'key_name': key_name, + 'key_data': key_data, + 'availability_zone': availability_zone} + + elevated = context.elevated() + instances = [] + logging.debug(_("Going to run %s instances..."), num_instances) + for num in range(num_instances): + instance = dict(mac_address=utils.generate_mac(), + launch_index=num, + **base_options) + instance = self.db.instance_create(context, instance) + instance_id = instance['id'] + + elevated = context.elevated() + if not security_groups: + security_groups = [] + for security_group_id in security_groups: + self.db.instance_add_security_group(elevated, + instance_id, + security_group_id) + + # Set sane defaults if not specified + updates = dict(hostname=generate_hostname(instance_id)) + if 'display_name' not in instance: + updates['display_name'] = "Server %s" % instance_id + + instance = self.update(context, instance_id, **updates) + instances.append(instance) + + logging.debug(_("Casting to scheduler for %s/%s's instance %s"), + context.project_id, context.user_id, instance_id) + rpc.cast(context, + FLAGS.scheduler_topic, + {"method": "run_instance", + "args": {"topic": FLAGS.compute_topic, + "instance_id": instance_id}}) + + return instances + + def ensure_default_security_group(self, context): + """ Create security group for the security context if it + does not already exist + + :param context: the security context + + """ + try: + db.security_group_get_by_name(context, context.project_id, + 'default') + except exception.NotFound: + values = {'name': 'default', + 'description': 'default', + 'user_id': context.user_id, + 'project_id': context.project_id} + db.security_group_create(context, values) + + def update(self, context, instance_id, **kwargs): + """Updates the instance in the datastore. + + :param context: The security context + :param instance_id: ID of the instance to update + :param kwargs: All additional keyword args are treated + as data fields of the instance to be + updated + + :retval None + + """ + return self.db.instance_update(context, instance_id, kwargs) + + def delete(self, context, instance_id): + logging.debug("Going to try and terminate %s" % instance_id) + try: + instance = self.get(context, instance_id) + except exception.NotFound as e: + logging.warning(_("Instance %s was not found during terminate"), + instance_id) + raise e + + if (instance['state_description'] == 'terminating'): + logging.warning(_("Instance %s is already being terminated"), + instance_id) + return + + self.update(context, + instance['id'], + state_description='terminating', + state=0, + terminated_at=datetime.datetime.utcnow()) + + host = instance['host'] + if host: + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "terminate_instance", + "args": {"instance_id": instance_id}}) + else: + self.db.instance_destroy(context, instance_id) + + def get(self, context, instance_id=None, project_id=None, + reservation_id=None, fixed_ip=None): + """Get one or more instances, possibly filtered by one of the + given parameters. If there is no filter and the context is + an admin, it will retreive all instances in the system.""" + if instance_id is not None: + return self.db.instance_get_by_id(context, instance_id) + if reservation_id is not None: + return self.db.instance_get_all_by_reservation(context, + reservation_id) + if fixed_ip is not None: + return self.db.fixed_ip_get_instance(context, fixed_ip) + if project_id or not context.is_admin: + if not context.project: + return self.db.instance_get_all_by_user(context, + context.user_id) + if project_id is None: + project_id = context.project_id + return self.db.instance_get_all_by_project(context, + project_id) + return self.db.instance_get_all(context) + + def snapshot(self, context, instance_id, name): + """Snapshot the given instance.""" + instance = self.get(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.get(context, instance_id) + host = instance['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "reboot_instance", + "args": {"instance_id": instance_id}}) + + def pause(self, context, instance_id): + """Pause the given instance.""" + instance = self.get(context, instance_id) + host = instance['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "pause_instance", + "args": {"instance_id": instance_id}}) + + def unpause(self, context, instance_id): + """Unpause the given instance.""" + instance = self.get(context, instance_id) + host = instance['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "unpause_instance", + "args": {"instance_id": instance_id}}) + + def get_diagnostics(self, context, instance_id): + """Retrieve diagnostics for the given instance.""" + instance = self.get(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 get_actions(self, context, instance_id): + """Retrieve actions for the given instance.""" + return self.db.instance_get_actions(context, instance_id) + + def suspend(self, context, instance_id): + """suspend the instance with instance_id""" + instance = self.get(context, instance_id) + host = instance['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "suspend_instance", + "args": {"instance_id": instance_id}}) + + def resume(self, context, instance_id): + """resume the instance with instance_id""" + instance = self.get(context, instance_id) + host = instance['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "resume_instance", + "args": {"instance_id": instance_id}}) + + def rescue(self, context, instance_id): + """Rescue the given instance.""" + instance = self.get(context, instance_id) + host = instance['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "rescue_instance", + "args": {"instance_id": instance_id}}) + + def unrescue(self, context, instance_id): + """Unrescue the given instance.""" + instance = self.get(context, instance_id) + host = instance['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "unrescue_instance", + "args": {"instance_id": instance_id}}) + + def attach_volume(self, context, instance_id, volume_id, device): + if not re.match("^/dev/[a-z]d[a-z]+$", device): + raise exception.ApiError(_("Invalid device specified: %s. " + "Example device: /dev/vdb") % device) + self.volume_api.check_attach(context, volume_id) + instance = self.get(context, instance_id) + host = instance['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "attach_volume", + "args": {"volume_id": volume_id, + "instance_id": instance_id, + "mountpoint": device}}) + + def detach_volume(self, context, volume_id): + instance = self.db.volume_get_instance(context.elevated(), volume_id) + if not instance: + raise exception.ApiError(_("Volume isn't attached to anything!")) + self.volume_api.check_detach(context, volume_id) + host = instance['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.compute_topic, host), + {"method": "detach_volume", + "args": {"instance_id": instance['id'], + "volume_id": volume_id}}) + return instance + + def associate_floating_ip(self, context, instance_id, address): + instance = self.get(context, instance_id) + self.network_api.associate_floating_ip(context, address, + instance['fixed_ip']) diff --git a/nova/network/__init__.py b/nova/network/__init__.py index cbd912047cab..6eb3e3ef6cbb 100644 --- a/nova/network/__init__.py +++ b/nova/network/__init__.py @@ -16,72 +16,4 @@ # License for the specific language governing permissions and limitations # under the License. -""" -Handles all requests relating to instances (guest vms). -""" - -import logging - -from nova import db -from nova import flags -from nova import quota -from nova import rpc -from nova.db import base - -FLAGS = flags.FLAGS - - -class API(base.Base): - """API for interacting with the network manager.""" - - def allocate_floating_ip(self, context): - if quota.allowed_floating_ips(context, 1) < 1: - logging.warn(_("Quota exceeeded for %s, tried to allocate " - "address"), - context.project_id) - raise quota.QuotaError(_("Address quota exceeded. You cannot " - "allocate any more addresses")) - # NOTE(vish): We don't know which network host should get the ip - # when we allocate, so just send it to any one. This - # will probably need to move into a network supervisor - # at some point. - return rpc.call(context, - FLAGS.network_topic, - {"method": "allocate_floating_ip", - "args": {"project_id": context.project_id}}) - - def release_floating_ip(self, context, address): - floating_ip = self.db.floating_ip_get_by_address(context, address) - # NOTE(vish): We don't know which network host should get the ip - # when we deallocate, so just send it to any one. This - # will probably need to move into a network supervisor - # at some point. - rpc.cast(context, - FLAGS.network_topic, - {"method": "deallocate_floating_ip", - "args": {"floating_address": floating_ip['address']}}) - - def associate_floating_ip(self, context, floating_ip, fixed_ip): - if isinstance(fixed_ip, str) or isinstance(fixed_ip, unicode): - fixed_ip = self.db.fixed_ip_get_by_address(context, fixed_ip) - floating_ip = self.db.floating_ip_get_by_address(context, floating_ip) - # NOTE(vish): Perhaps we should just pass this on to compute and - # let compute communicate with network. - host = fixed_ip['network']['host'] - rpc.cast(context, - self.db.queue_get_for(context, FLAGS.network_topic, host), - {"method": "associate_floating_ip", - "args": {"floating_address": floating_ip['address'], - "fixed_address": fixed_ip['address']}}) - - def disassociate_floating_ip(self, context, address): - floating_ip = self.db.floating_ip_get_by_address(context, address) - if not floating_ip.get('fixed_ip'): - raise exception.ApiError('Address is not associated.') - # NOTE(vish): Get the topic from the host name of the network of - # the associated fixed ip. - host = floating_ip['fixed_ip']['network']['host'] - rpc.cast(context, - self.db.queue_get_for(context, FLAGS.network_topic, host), - {"method": "disassociate_floating_ip", - "args": {"floating_address": floating_ip['address']}}) +from nova.network.api import API diff --git a/nova/network/api.py b/nova/network/api.py new file mode 100644 index 000000000000..cbd912047cab --- /dev/null +++ b/nova/network/api.py @@ -0,0 +1,87 @@ +# 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. + +""" +Handles all requests relating to instances (guest vms). +""" + +import logging + +from nova import db +from nova import flags +from nova import quota +from nova import rpc +from nova.db import base + +FLAGS = flags.FLAGS + + +class API(base.Base): + """API for interacting with the network manager.""" + + def allocate_floating_ip(self, context): + if quota.allowed_floating_ips(context, 1) < 1: + logging.warn(_("Quota exceeeded for %s, tried to allocate " + "address"), + context.project_id) + raise quota.QuotaError(_("Address quota exceeded. You cannot " + "allocate any more addresses")) + # NOTE(vish): We don't know which network host should get the ip + # when we allocate, so just send it to any one. This + # will probably need to move into a network supervisor + # at some point. + return rpc.call(context, + FLAGS.network_topic, + {"method": "allocate_floating_ip", + "args": {"project_id": context.project_id}}) + + def release_floating_ip(self, context, address): + floating_ip = self.db.floating_ip_get_by_address(context, address) + # NOTE(vish): We don't know which network host should get the ip + # when we deallocate, so just send it to any one. This + # will probably need to move into a network supervisor + # at some point. + rpc.cast(context, + FLAGS.network_topic, + {"method": "deallocate_floating_ip", + "args": {"floating_address": floating_ip['address']}}) + + def associate_floating_ip(self, context, floating_ip, fixed_ip): + if isinstance(fixed_ip, str) or isinstance(fixed_ip, unicode): + fixed_ip = self.db.fixed_ip_get_by_address(context, fixed_ip) + floating_ip = self.db.floating_ip_get_by_address(context, floating_ip) + # NOTE(vish): Perhaps we should just pass this on to compute and + # let compute communicate with network. + host = fixed_ip['network']['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.network_topic, host), + {"method": "associate_floating_ip", + "args": {"floating_address": floating_ip['address'], + "fixed_address": fixed_ip['address']}}) + + def disassociate_floating_ip(self, context, address): + floating_ip = self.db.floating_ip_get_by_address(context, address) + if not floating_ip.get('fixed_ip'): + raise exception.ApiError('Address is not associated.') + # NOTE(vish): Get the topic from the host name of the network of + # the associated fixed ip. + host = floating_ip['fixed_ip']['network']['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.network_topic, host), + {"method": "disassociate_floating_ip", + "args": {"floating_address": floating_ip['address']}}) diff --git a/nova/quota.py b/nova/quota.py index 12dbfcf43862..3884eb3081a0 100644 --- a/nova/quota.py +++ b/nova/quota.py @@ -53,7 +53,7 @@ def get_quota(context, project_id): return rval -def allowed_instances(context, num_instances, cores_per_instance): +def allowed_instances(context, num_instances, instance_type): """Check quota and return min(num_instances, allowed_instances)""" project_id = context.project_id context = context.elevated() @@ -62,9 +62,9 @@ def allowed_instances(context, num_instances, cores_per_instance): quota = get_quota(context, project_id) allowed_instances = quota['instances'] - used_instances allowed_cores = quota['cores'] - used_cores - num_cores = num_instances * cores_per_instance + num_cores = num_instances * instance_type['vcpus'] allowed_instances = min(allowed_instances, - int(allowed_cores // cores_per_instance)) + int(allowed_cores // instance_type['vcpus'])) return min(num_instances, allowed_instances) diff --git a/nova/tests/test_quota.py b/nova/tests/test_quota.py index c158187746b3..b5f9f30ef286 100644 --- a/nova/tests/test_quota.py +++ b/nova/tests/test_quota.py @@ -27,6 +27,7 @@ from nova import test from nova import utils from nova.auth import manager from nova.api.ec2 import cloud +from nova.compute import instance_types FLAGS = flags.FLAGS @@ -78,14 +79,17 @@ class QuotaTestCase(test.TestCase): def test_quota_overrides(self): """Make sure overriding a projects quotas works""" - num_instances = quota.allowed_instances(self.context, 100, 1) + num_instances = quota.allowed_instances(self.context, 100, + instance_types.INSTANCE_TYPES['m1.small']) self.assertEqual(num_instances, 2) db.quota_create(self.context, {'project_id': self.project.id, 'instances': 10}) - num_instances = quota.allowed_instances(self.context, 100, 1) + num_instances = quota.allowed_instances(self.context, 100, + instance_types.INSTANCE_TYPES['m1.small']) self.assertEqual(num_instances, 4) db.quota_update(self.context, self.project.id, {'cores': 100}) - num_instances = quota.allowed_instances(self.context, 100, 1) + num_instances = quota.allowed_instances(self.context, 100, + instance_types.INSTANCE_TYPES['m1.small']) self.assertEqual(num_instances, 10) db.quota_destroy(self.context, self.project.id) diff --git a/nova/volume/__init__.py b/nova/volume/__init__.py index 48ecdbe68b17..56ef9332e37e 100644 --- a/nova/volume/__init__.py +++ b/nova/volume/__init__.py @@ -16,85 +16,4 @@ # License for the specific language governing permissions and limitations # under the License. -""" -Handles all requests relating to volumes. -""" - -import datetime -import logging - -from nova import db -from nova import exception -from nova import flags -from nova import quota -from nova import rpc -from nova.db import base - -FLAGS = flags.FLAGS -flags.DECLARE('storage_availability_zone', 'nova.volume.manager') - - -class API(base.Base): - """API for interacting with the volume manager.""" - - def create(self, context, size, name, description): - if quota.allowed_volumes(context, 1, size) < 1: - logging.warn("Quota exceeeded for %s, tried to create %sG volume", - context.project_id, size) - raise quota.QuotaError("Volume quota exceeded. You cannot " - "create a volume of size %s" % size) - - options = { - 'size': size, - 'user_id': context.user.id, - 'project_id': context.project_id, - 'availability_zone': FLAGS.storage_availability_zone, - 'status': "creating", - 'attach_status': "detached", - 'display_name': name, - 'display_description': description} - - volume = self.db.volume_create(context, options) - rpc.cast(context, - FLAGS.scheduler_topic, - {"method": "create_volume", - "args": {"topic": FLAGS.volume_topic, - "volume_id": volume['id']}}) - return volume - - def delete(self, context, volume_id): - volume = self.db.volume_get(context, volume_id) - if volume['status'] != "available": - raise exception.ApiError(_("Volume status must be available")) - now = datetime.datetime.utcnow() - self.db.volume_update(context, volume_id, {'status': 'deleting', - 'terminated_at': now}) - host = volume['host'] - rpc.cast(context, - self.db.queue_get_for(context, FLAGS.volume_topic, host), - {"method": "delete_volume", - "args": {"volume_id": volume_id}}) - - def update(self, context, volume_id, fields): - self.db.volume_update(context, volume_id, fields) - - def get(self, context, volume_id=None): - if volume_id is not None: - return self.db.volume_get(context, volume_id) - if context.user.is_admin(): - return self.db.volume_get_all(context) - return self.db.volume_get_all_by_project(context, context.project_id) - - def check_attach(self, context, volume_id): - volume = self.db.volume_get(context, volume_id) - # TODO(vish): abstract status checking? - if volume['status'] != "available": - raise exception.ApiError(_("Volume status must be available")) - if volume['attach_status'] == "attached": - raise exception.ApiError(_("Volume is already attached")) - - def check_detach(self, context, volume_id): - volume = self.db.volume_get(context, volume_id) - # TODO(vish): abstract status checking? - if volume['status'] == "available": - raise exception.ApiError(_("Volume is already detached")) +from nova.volume.api import API diff --git a/nova/volume/api.py b/nova/volume/api.py new file mode 100644 index 000000000000..48ecdbe68b17 --- /dev/null +++ b/nova/volume/api.py @@ -0,0 +1,100 @@ +# 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. + +""" +Handles all requests relating to volumes. +""" + +import datetime +import logging + +from nova import db +from nova import exception +from nova import flags +from nova import quota +from nova import rpc +from nova.db import base + +FLAGS = flags.FLAGS +flags.DECLARE('storage_availability_zone', 'nova.volume.manager') + + +class API(base.Base): + """API for interacting with the volume manager.""" + + def create(self, context, size, name, description): + if quota.allowed_volumes(context, 1, size) < 1: + logging.warn("Quota exceeeded for %s, tried to create %sG volume", + context.project_id, size) + raise quota.QuotaError("Volume quota exceeded. You cannot " + "create a volume of size %s" % size) + + options = { + 'size': size, + 'user_id': context.user.id, + 'project_id': context.project_id, + 'availability_zone': FLAGS.storage_availability_zone, + 'status': "creating", + 'attach_status': "detached", + 'display_name': name, + 'display_description': description} + + volume = self.db.volume_create(context, options) + rpc.cast(context, + FLAGS.scheduler_topic, + {"method": "create_volume", + "args": {"topic": FLAGS.volume_topic, + "volume_id": volume['id']}}) + return volume + + def delete(self, context, volume_id): + volume = self.db.volume_get(context, volume_id) + if volume['status'] != "available": + raise exception.ApiError(_("Volume status must be available")) + now = datetime.datetime.utcnow() + self.db.volume_update(context, volume_id, {'status': 'deleting', + 'terminated_at': now}) + host = volume['host'] + rpc.cast(context, + self.db.queue_get_for(context, FLAGS.volume_topic, host), + {"method": "delete_volume", + "args": {"volume_id": volume_id}}) + + def update(self, context, volume_id, fields): + self.db.volume_update(context, volume_id, fields) + + def get(self, context, volume_id=None): + if volume_id is not None: + return self.db.volume_get(context, volume_id) + if context.user.is_admin(): + return self.db.volume_get_all(context) + return self.db.volume_get_all_by_project(context, context.project_id) + + def check_attach(self, context, volume_id): + volume = self.db.volume_get(context, volume_id) + # TODO(vish): abstract status checking? + if volume['status'] != "available": + raise exception.ApiError(_("Volume status must be available")) + if volume['attach_status'] == "attached": + raise exception.ApiError(_("Volume is already attached")) + + def check_detach(self, context, volume_id): + volume = self.db.volume_get(context, volume_id) + # TODO(vish): abstract status checking? + if volume['status'] == "available": + raise exception.ApiError(_("Volume is already detached")) From 468bc4745f002b521f21c5d621bdcb596b8ddfcd Mon Sep 17 00:00:00 2001 From: Chiradeep Vittal Date: Tue, 4 Jan 2011 15:18:28 -0800 Subject: [PATCH 63/86] Merge from trunk again -- get rid of twistd dependencies --- nova/twistd.py | 39 +++++++++++++++++++-------------------- nova/virt/hyperv.py | 30 +++++++++++++++--------------- nova/virt/images.py | 6 ++++-- 3 files changed, 38 insertions(+), 37 deletions(-) diff --git a/nova/twistd.py b/nova/twistd.py index 22e2d06d3ed8..1dd10dbb592f 100644 --- a/nova/twistd.py +++ b/nova/twistd.py @@ -237,26 +237,25 @@ def serve(filename): logging.getLogger('amqplib').setLevel(logging.WARN) FLAGS.python = filename FLAGS.no_save = True - if sys.platform != 'win32': - if not FLAGS.pidfile: - FLAGS.pidfile = '%s.pid' % name - elif FLAGS.pidfile.endswith('twistd.pid'): - FLAGS.pidfile = FLAGS.pidfile.replace('twistd.pid', - '%s.pid' % name) - # NOTE(vish): if we're running nodaemon, redirect the log to stdout - if FLAGS.nodaemon and not FLAGS.logfile: - FLAGS.logfile = "-" - if not FLAGS.logfile: - FLAGS.logfile = '%s.log' % name - elif FLAGS.logfile.endswith('twistd.log'): - FLAGS.logfile = FLAGS.logfile.replace('twistd.log', - '%s.log' % name) - if FLAGS.logdir: - FLAGS.logfile = os.path.join(FLAGS.logdir, FLAGS.logfile) - if not FLAGS.prefix: - FLAGS.prefix = name - elif FLAGS.prefix.endswith('twisted'): - FLAGS.prefix = FLAGS.prefix.replace('twisted', name) + if not FLAGS.pidfile: + FLAGS.pidfile = '%s.pid' % name + elif FLAGS.pidfile.endswith('twistd.pid'): + FLAGS.pidfile = FLAGS.pidfile.replace('twistd.pid', + '%s.pid' % name) + # NOTE(vish): if we're running nodaemon, redirect the log to stdout + if FLAGS.nodaemon and not FLAGS.logfile: + FLAGS.logfile = "-" + if not FLAGS.logfile: + FLAGS.logfile = '%s.log' % name + elif FLAGS.logfile.endswith('twistd.log'): + FLAGS.logfile = FLAGS.logfile.replace('twistd.log', + '%s.log' % name) + if FLAGS.logdir: + FLAGS.logfile = os.path.join(FLAGS.logdir, FLAGS.logfile) + if not FLAGS.prefix: + FLAGS.prefix = name + elif FLAGS.prefix.endswith('twisted'): + FLAGS.prefix = FLAGS.prefix.replace('twisted', name) action = 'start' if len(argv) > 1: diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index a254aaf8a1ae..6aeeb0837f23 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -64,8 +64,6 @@ import os import logging import time -from twisted.internet import defer - from nova import exception from nova import flags from nova.auth import manager @@ -112,16 +110,20 @@ class HyperVConnection(object): self._conn = wmi.WMI(moniker='//./root/virtualization') self._cim_conn = wmi.WMI(moniker='//./root/cimv2') + def init_host(self): + #FIXME(chiradeep): implement this + logging.debug('In init host') + pass + def list_instances(self): """ Return the names of all the instances known to Hyper-V. """ vms = [v.ElementName \ for v in self._conn.Msvm_ComputerSystem(['ElementName'])] return vms - @defer.inlineCallbacks def spawn(self, instance): """ Create a new VM and start it.""" - vm = yield self._lookup(instance.name) + vm = self._lookup(instance.name) if vm is not None: raise exception.Duplicate('Attempted to create duplicate name %s' % instance.name) @@ -130,18 +132,18 @@ class HyperVConnection(object): project = manager.AuthManager().get_project(instance['project_id']) #Fetch the file, assume it is a VHD file. base_vhd_filename = os.path.join(FLAGS.instances_path, - instance['str_id']) + instance.name) vhdfile = "%s.vhd" % (base_vhd_filename) - yield images.fetch(instance['image_id'], vhdfile, user, project) + images.fetch(instance['image_id'], vhdfile, user, project) try: - yield self._create_vm(instance) + self._create_vm(instance) - yield self._create_disk(instance['name'], vhdfile) - yield self._create_nic(instance['name'], instance['mac_address']) + self._create_disk(instance['name'], vhdfile) + self._create_nic(instance['name'], instance['mac_address']) logging.debug('Starting VM %s ', instance.name) - yield self._set_vm_state(instance['name'], 'Enabled') + self._set_vm_state(instance['name'], 'Enabled') logging.info('Started VM %s ', instance.name) except Exception as exn: logging.error('spawn vm failed: %s', exn) @@ -341,21 +343,19 @@ class HyperVConnection(object): wmi_obj.Properties_.Item(prop).Value return newinst - @defer.inlineCallbacks def reboot(self, instance): """Reboot the specified instance.""" - vm = yield self._lookup(instance.name) + vm = self._lookup(instance.name) if vm is None: raise exception.NotFound('instance not present %s' % instance.name) self._set_vm_state(instance.name, 'Reboot') - @defer.inlineCallbacks def destroy(self, instance): """Destroy the VM. Also destroy the associated VHD disk files""" logging.debug("Got request to destroy vm %s", instance.name) - vm = yield self._lookup(instance.name) + vm = self._lookup(instance.name) if vm is None: - defer.returnValue(None) + return vm = self._conn.Msvm_ComputerSystem(ElementName=instance.name)[0] vs_man_svc = self._conn.Msvm_VirtualSystemManagementService()[0] #Stop the VM first. diff --git a/nova/virt/images.py b/nova/virt/images.py index bd7426ad0416..41719753866c 100644 --- a/nova/virt/images.py +++ b/nova/virt/images.py @@ -60,7 +60,9 @@ def _fetch_image_no_curl(url, path, headers): while 1: data = urlfile.read(chunk) if not data: - break f.write(data) + break + f.write(data) + urlopened = urllib2.urlopen(request) urlretrieve(urlopened, path) logging.debug("Finished retreving %s -- placed in %s", url, path) @@ -68,7 +70,6 @@ def _fetch_image_no_curl(url, path, headers): def _fetch_s3_image(image, path, user, project): url = image_url(image) - # This should probably move somewhere else, like e.g. a download_as # method on User objects and at the same time get rewritten to use # a web client. @@ -88,6 +89,7 @@ def _fetch_s3_image(image, path, user, project): cmd = ['/usr/bin/curl', '--fail', '--silent', url] for (k, v) in headers.iteritems(): cmd += ['-H', '%s: %s' % (k, v)] + cmd += ['-o', path] cmd_out = ' '.join(cmd) return utils.execute(cmd_out) From 91e44607d1454a9c2e258910f009a034fb9cff1c Mon Sep 17 00:00:00 2001 From: Chiradeep Vittal Date: Tue, 4 Jan 2011 15:42:29 -0800 Subject: [PATCH 64/86] i18n logging and exception strings --- nova/virt/hyperv.py | 62 ++++++++++++++++++++++----------------------- nova/virt/images.py | 2 +- 2 files changed, 32 insertions(+), 32 deletions(-) diff --git a/nova/virt/hyperv.py b/nova/virt/hyperv.py index 6aeeb0837f23..4b9f6f946154 100644 --- a/nova/virt/hyperv.py +++ b/nova/virt/hyperv.py @@ -112,7 +112,7 @@ class HyperVConnection(object): def init_host(self): #FIXME(chiradeep): implement this - logging.debug('In init host') + logging.debug(_('In init host')) pass def list_instances(self): @@ -125,7 +125,7 @@ class HyperVConnection(object): """ Create a new VM and start it.""" vm = self._lookup(instance.name) if vm is not None: - raise exception.Duplicate('Attempted to create duplicate name %s' % + raise exception.Duplicate(_('Attempt to create duplicate vm %s') % instance.name) user = manager.AuthManager().get_user(instance['user_id']) @@ -142,11 +142,11 @@ class HyperVConnection(object): self._create_disk(instance['name'], vhdfile) self._create_nic(instance['name'], instance['mac_address']) - logging.debug('Starting VM %s ', instance.name) + logging.debug(_('Starting VM %s '), instance.name) self._set_vm_state(instance['name'], 'Enabled') - logging.info('Started VM %s ', instance.name) + logging.info(_('Started VM %s '), instance.name) except Exception as exn: - logging.error('spawn vm failed: %s', exn) + logging.error(_('spawn vm failed: %s'), exn) self.destroy(instance) def _create_vm(self, instance): @@ -163,9 +163,9 @@ class HyperVConnection(object): success = (ret_val == 0) if not success: - raise Exception('Failed to create VM %s', instance.name) + raise Exception(_('Failed to create VM %s'), instance.name) - logging.debug('Created VM %s...', instance.name) + logging.debug(_('Created VM %s...'), instance.name) vm = self._conn.Msvm_ComputerSystem(ElementName=instance.name)[0] vmsettings = vm.associators( @@ -182,7 +182,7 @@ class HyperVConnection(object): (job, ret_val) = vs_man_svc.ModifyVirtualSystemResources( vm.path_(), [memsetting.GetText_(1)]) - logging.debug('Set memory for vm %s...', instance.name) + logging.debug(_('Set memory for vm %s...'), instance.name) procsetting = vmsetting.associators( wmi_result_class='Msvm_ProcessorSettingData')[0] vcpus = long(instance['vcpus']) @@ -192,11 +192,11 @@ class HyperVConnection(object): (job, ret_val) = vs_man_svc.ModifyVirtualSystemResources( vm.path_(), [procsetting.GetText_(1)]) - logging.debug('Set vcpus for vm %s...', instance.name) + logging.debug(_('Set vcpus for vm %s...'), instance.name) def _create_disk(self, vm_name, vhdfile): """Create a disk and attach it to the vm""" - logging.debug("Creating disk for %s by attaching disk file %s", + logging.debug(_('Creating disk for %s by attaching disk file %s'), vm_name, vhdfile) #Find the IDE controller for the vm. vms = self._conn.MSVM_ComputerSystem(ElementName=vm_name) @@ -221,10 +221,10 @@ class HyperVConnection(object): #Add the cloned disk drive object to the vm. new_resources = self._add_virt_resource(diskdrive, vm) if new_resources is None: - raise Exception('Failed to add diskdrive to VM %s', + raise Exception(_('Failed to add diskdrive to VM %s'), vm_name) diskdrive_path = new_resources[0] - logging.debug("New disk drive path is %s", diskdrive_path) + logging.debug(_('New disk drive path is %s'), diskdrive_path) #Find the default VHD disk object. vhddefault = self._conn.query( "SELECT * FROM Msvm_ResourceAllocationSettingData \ @@ -241,13 +241,13 @@ class HyperVConnection(object): #Add the new vhd object as a virtual hard disk to the vm. new_resources = self._add_virt_resource(vhddisk, vm) if new_resources is None: - raise Exception('Failed to add vhd file to VM %s', + raise Exception(_('Failed to add vhd file to VM %s'), vm_name) - logging.info("Created disk for %s ", vm_name) + logging.info(_('Created disk for %s'), vm_name) def _create_nic(self, vm_name, mac): """Create a (emulated) nic and attach it to the vm""" - logging.debug("Creating nic for %s ", vm_name) + logging.debug(_('Creating nic for %s '), vm_name) #Find the vswitch that is connected to the physical nic. vms = self._conn.Msvm_ComputerSystem(ElementName=vm_name) extswitch = self._find_external_network() @@ -266,10 +266,10 @@ class HyperVConnection(object): (new_port, ret_val) = switch_svc.CreateSwitchPort(vm_name, vm_name, "", extswitch.path_()) if ret_val != 0: - logging.error("Failed creating a new port on the external vswitch") - raise Exception('Failed creating port for %s', + logging.error(_('Failed creating a port on the external vswitch')) + raise Exception(_('Failed creating port for %s'), vm_name) - logging.debug("Created switch port %s on switch %s", + logging.debug(_("Created switch port %s on switch %s"), vm_name, extswitch.path_()) #Connect the new nic to the new port. new_nic_data.Connection = [new_port] @@ -279,9 +279,9 @@ class HyperVConnection(object): #Add the new nic to the vm. new_resources = self._add_virt_resource(new_nic_data, vm) if new_resources is None: - raise Exception('Failed to add nic to VM %s', + raise Exception(_('Failed to add nic to VM %s'), vm_name) - logging.info("Created nic for %s ", vm_name) + logging.info(_("Created nic for %s "), vm_name) def _add_virt_resource(self, res_setting_data, target_vm): """Add a new resource (disk/nic) to the VM""" @@ -314,9 +314,9 @@ class HyperVConnection(object): time.sleep(0.1) job = self._conn.Msvm_ConcreteJob(InstanceID=inst_id)[0] if job.JobState != WMI_JOB_STATE_COMPLETED: - logging.debug("WMI job failed: %s", job.ErrorSummaryDescription) + logging.debug(_("WMI job failed: %s"), job.ErrorSummaryDescription) return False - logging.debug("WMI job succeeded: %s, Elapsed=%s ", job.Description, + logging.debug(_("WMI job succeeded: %s, Elapsed=%s "), job.Description, job.ElapsedTime) return True @@ -352,7 +352,7 @@ class HyperVConnection(object): def destroy(self, instance): """Destroy the VM. Also destroy the associated VHD disk files""" - logging.debug("Got request to destroy vm %s", instance.name) + logging.debug(_("Got request to destroy vm %s"), instance.name) vm = self._lookup(instance.name) if vm is None: return @@ -377,13 +377,13 @@ class HyperVConnection(object): elif ret_val == 0: success = True if not success: - raise Exception('Failed to destroy vm %s' % instance.name) + raise Exception(_('Failed to destroy vm %s') % instance.name) #Delete associated vhd disk files. for disk in diskfiles: vhdfile = self._cim_conn.CIM_DataFile(Name=disk) for vf in vhdfile: vf.Delete() - logging.debug("Deleted disk %s vm %s", vhdfile, instance.name) + logging.debug(_("Del: disk %s vm %s"), vhdfile, instance.name) def get_info(self, instance_id): """Get information about the VM""" @@ -399,8 +399,8 @@ class HyperVConnection(object): summary_info = vs_man_svc.GetSummaryInformation( [4, 100, 103, 105], settings_paths)[1] info = summary_info[0] - logging.debug("Got Info for vm %s: state=%s, mem=%s, num_cpu=%s, \ - cpu_time=%s", instance_id, + logging.debug(_("Got Info for vm %s: state=%s, mem=%s, num_cpu=%s, \ + cpu_time=%s"), instance_id, str(HYPERV_POWER_STATE[info.EnabledState]), str(info.MemoryUsage), str(info.NumberOfProcessors), @@ -418,7 +418,7 @@ class HyperVConnection(object): if n == 0: return None elif n > 1: - raise Exception('duplicate name found: %s' % i) + raise Exception(_('duplicate name found: %s') % i) else: return vms[0].ElementName @@ -438,12 +438,12 @@ class HyperVConnection(object): #already in the state requested success = True if success: - logging.info("Successfully changed vm state of %s to %s", + logging.info(_("Successfully changed vm state of %s to %s"), vm_name, req_state) else: - logging.error("Failed to change vm state of %s to %s", + logging.error(_("Failed to change vm state of %s to %s"), vm_name, req_state) - raise Exception("Failed to change vm state of %s to %s", + raise Exception(_("Failed to change vm state of %s to %s"), vm_name, req_state) def attach_volume(self, instance_name, device_path, mountpoint): diff --git a/nova/virt/images.py b/nova/virt/images.py index 41719753866c..be162b5b1317 100644 --- a/nova/virt/images.py +++ b/nova/virt/images.py @@ -65,7 +65,7 @@ def _fetch_image_no_curl(url, path, headers): urlopened = urllib2.urlopen(request) urlretrieve(urlopened, path) - logging.debug("Finished retreving %s -- placed in %s", url, path) + logging.debug(_("Finished retreving %s -- placed in %s"), url, path) def _fetch_s3_image(image, path, user, project): From 46a249eaa1db7d0f5b765cff701bb13005e3db49 Mon Sep 17 00:00:00 2001 From: Chiradeep Vittal Date: Tue, 4 Jan 2011 16:06:03 -0800 Subject: [PATCH 65/86] Revert some unneeded formatting since twistd is no longer used --- nova/twistd.py | 6 ++---- nova/virt/images.py | 1 + 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/nova/twistd.py b/nova/twistd.py index 1dd10dbb592f..29be9c4e1457 100644 --- a/nova/twistd.py +++ b/nova/twistd.py @@ -240,16 +240,14 @@ def serve(filename): if not FLAGS.pidfile: FLAGS.pidfile = '%s.pid' % name elif FLAGS.pidfile.endswith('twistd.pid'): - FLAGS.pidfile = FLAGS.pidfile.replace('twistd.pid', - '%s.pid' % name) + FLAGS.pidfile = FLAGS.pidfile.replace('twistd.pid', '%s.pid' % name) # NOTE(vish): if we're running nodaemon, redirect the log to stdout if FLAGS.nodaemon and not FLAGS.logfile: FLAGS.logfile = "-" if not FLAGS.logfile: FLAGS.logfile = '%s.log' % name elif FLAGS.logfile.endswith('twistd.log'): - FLAGS.logfile = FLAGS.logfile.replace('twistd.log', - '%s.log' % name) + FLAGS.logfile = FLAGS.logfile.replace('twistd.log', '%s.log' % name) if FLAGS.logdir: FLAGS.logfile = os.path.join(FLAGS.logdir, FLAGS.logfile) if not FLAGS.prefix: diff --git a/nova/virt/images.py b/nova/virt/images.py index be162b5b1317..2d03da4b4fed 100644 --- a/nova/virt/images.py +++ b/nova/virt/images.py @@ -70,6 +70,7 @@ def _fetch_image_no_curl(url, path, headers): def _fetch_s3_image(image, path, user, project): url = image_url(image) + # This should probably move somewhere else, like e.g. a download_as # method on User objects and at the same time get rewritten to use # a web client. From 7924b211f23dcd687612b32341e2be0b57fd386e Mon Sep 17 00:00:00 2001 From: Chiradeep Vittal Date: Tue, 4 Jan 2011 16:20:14 -0800 Subject: [PATCH 66/86] Redis dependency no longer needed --- nova/tests/hyperv_unittest.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/nova/tests/hyperv_unittest.py b/nova/tests/hyperv_unittest.py index 6346ce4c75f3..27a41f19eaa2 100644 --- a/nova/tests/hyperv_unittest.py +++ b/nova/tests/hyperv_unittest.py @@ -27,10 +27,6 @@ from nova.virt import hyperv FLAGS = flags.FLAGS FLAGS.connection_type = 'hyperv' -# Redis is probably not running on Hyper-V host. -# Change this to the actual Redis host -FLAGS.redis_host = '127.0.0.1' - class HyperVTestCase(test.TrialTestCase): """Test cases for the Hyper-V driver""" From f7543cdf973f4ddb5718255e9671530fc98fc756 Mon Sep 17 00:00:00 2001 From: Chiradeep Vittal Date: Tue, 4 Jan 2011 16:21:27 -0800 Subject: [PATCH 67/86] need one more newline --- nova/tests/hyperv_unittest.py | 1 + 1 file changed, 1 insertion(+) diff --git a/nova/tests/hyperv_unittest.py b/nova/tests/hyperv_unittest.py index 27a41f19eaa2..7044db43c2f9 100644 --- a/nova/tests/hyperv_unittest.py +++ b/nova/tests/hyperv_unittest.py @@ -28,6 +28,7 @@ from nova.virt import hyperv FLAGS = flags.FLAGS FLAGS.connection_type = 'hyperv' + class HyperVTestCase(test.TrialTestCase): """Test cases for the Hyper-V driver""" def setUp(self): # pylint: disable-msg=C0103 From 472af7e750f369e3b999d2b1ac48f74369975ba6 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Tue, 4 Jan 2011 18:34:47 -0600 Subject: [PATCH 68/86] Recover from a lost data store connection --- nova/service.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/nova/service.py b/nova/service.py index f1f90742f845..7203430c6f82 100644 --- a/nova/service.py +++ b/nova/service.py @@ -24,17 +24,21 @@ import inspect import logging import os import sys +import time from eventlet import event from eventlet import greenthread from eventlet import greenpool +from sqlalchemy.exc import OperationalError + from nova import context from nova import db from nova import exception from nova import flags from nova import rpc from nova import utils +from nova.db.sqlalchemy import models FLAGS = flags.FLAGS @@ -204,6 +208,14 @@ class Service(object): self.model_disconnected = True logging.exception(_("model server went away")) + try: + models.register_models() + except OperationalError: + logging.exception(_("Data store is unreachable." + " Trying again in %d seconds.") % + FLAGS.sql_retry_interval) + time.sleep(FLAGS.sql_retry_interval) + def serve(*services): argv = FLAGS(sys.argv) From 5912176111ccb37d3d26dd7b415de83e544d9c54 Mon Sep 17 00:00:00 2001 From: Todd Willey Date: Wed, 5 Jan 2011 02:56:02 -0500 Subject: [PATCH 69/86] Remove stale doc files. --- doc/.autogenerated | 97 ------------------ doc/source/api/autoindex.rst | 99 ------------------- doc/source/api/nova..adminclient.rst | 6 -- doc/source/api/nova..api.cloud.rst | 6 -- doc/source/api/nova..api.ec2.admin.rst | 6 -- doc/source/api/nova..api.ec2.apirequest.rst | 6 -- doc/source/api/nova..api.ec2.cloud.rst | 6 -- doc/source/api/nova..api.ec2.images.rst | 6 -- .../nova..api.ec2.metadatarequesthandler.rst | 6 -- doc/source/api/nova..api.openstack.auth.rst | 6 -- .../nova..api.openstack.backup_schedules.rst | 6 -- doc/source/api/nova..api.openstack.faults.rst | 6 -- .../api/nova..api.openstack.flavors.rst | 6 -- doc/source/api/nova..api.openstack.images.rst | 6 -- .../api/nova..api.openstack.servers.rst | 6 -- .../nova..api.openstack.sharedipgroups.rst | 6 -- doc/source/api/nova..auth.dbdriver.rst | 6 -- doc/source/api/nova..auth.fakeldap.rst | 6 -- doc/source/api/nova..auth.ldapdriver.rst | 6 -- doc/source/api/nova..auth.manager.rst | 6 -- doc/source/api/nova..auth.signer.rst | 6 -- doc/source/api/nova..cloudpipe.pipelib.rst | 6 -- doc/source/api/nova..compute.disk.rst | 6 -- .../api/nova..compute.instance_types.rst | 6 -- doc/source/api/nova..compute.manager.rst | 6 -- doc/source/api/nova..compute.monitor.rst | 6 -- doc/source/api/nova..compute.power_state.rst | 6 -- doc/source/api/nova..context.rst | 6 -- doc/source/api/nova..crypto.rst | 6 -- doc/source/api/nova..db.api.rst | 6 -- doc/source/api/nova..db.sqlalchemy.api.rst | 6 -- doc/source/api/nova..db.sqlalchemy.models.rst | 6 -- .../api/nova..db.sqlalchemy.session.rst | 6 -- doc/source/api/nova..exception.rst | 6 -- doc/source/api/nova..fakerabbit.rst | 6 -- doc/source/api/nova..flags.rst | 6 -- doc/source/api/nova..image.service.rst | 6 -- doc/source/api/nova..manager.rst | 6 -- doc/source/api/nova..network.linux_net.rst | 6 -- doc/source/api/nova..network.manager.rst | 6 -- doc/source/api/nova..objectstore.bucket.rst | 6 -- doc/source/api/nova..objectstore.handler.rst | 6 -- doc/source/api/nova..objectstore.image.rst | 6 -- doc/source/api/nova..objectstore.stored.rst | 6 -- doc/source/api/nova..process.rst | 6 -- doc/source/api/nova..quota.rst | 6 -- doc/source/api/nova..rpc.rst | 6 -- doc/source/api/nova..scheduler.chance.rst | 6 -- doc/source/api/nova..scheduler.driver.rst | 6 -- doc/source/api/nova..scheduler.manager.rst | 6 -- doc/source/api/nova..scheduler.simple.rst | 6 -- doc/source/api/nova..server.rst | 6 -- doc/source/api/nova..service.rst | 6 -- doc/source/api/nova..test.rst | 6 -- .../api/nova..tests.access_unittest.rst | 6 -- doc/source/api/nova..tests.api.fakes.rst | 6 -- .../api/nova..tests.api.openstack.fakes.rst | 6 -- .../nova..tests.api.openstack.test_api.rst | 6 -- .../nova..tests.api.openstack.test_auth.rst | 6 -- .../nova..tests.api.openstack.test_faults.rst | 6 -- ...nova..tests.api.openstack.test_flavors.rst | 6 -- .../nova..tests.api.openstack.test_images.rst | 6 -- ....tests.api.openstack.test_ratelimiting.rst | 6 -- ...nova..tests.api.openstack.test_servers.rst | 6 -- ...ests.api.openstack.test_sharedipgroups.rst | 6 -- doc/source/api/nova..tests.api.test_wsgi.rst | 6 -- .../api/nova..tests.api_integration.rst | 6 -- doc/source/api/nova..tests.api_unittest.rst | 6 -- doc/source/api/nova..tests.auth_unittest.rst | 6 -- doc/source/api/nova..tests.cloud_unittest.rst | 6 -- .../api/nova..tests.compute_unittest.rst | 6 -- doc/source/api/nova..tests.declare_flags.rst | 6 -- doc/source/api/nova..tests.fake_flags.rst | 6 -- doc/source/api/nova..tests.flags_unittest.rst | 6 -- .../api/nova..tests.network_unittest.rst | 6 -- .../api/nova..tests.objectstore_unittest.rst | 6 -- .../api/nova..tests.process_unittest.rst | 6 -- doc/source/api/nova..tests.quota_unittest.rst | 6 -- doc/source/api/nova..tests.real_flags.rst | 6 -- doc/source/api/nova..tests.rpc_unittest.rst | 6 -- doc/source/api/nova..tests.runtime_flags.rst | 6 -- .../api/nova..tests.scheduler_unittest.rst | 6 -- .../api/nova..tests.service_unittest.rst | 6 -- .../api/nova..tests.twistd_unittest.rst | 6 -- .../api/nova..tests.validator_unittest.rst | 6 -- doc/source/api/nova..tests.virt_unittest.rst | 6 -- .../api/nova..tests.volume_unittest.rst | 6 -- doc/source/api/nova..twistd.rst | 6 -- doc/source/api/nova..utils.rst | 6 -- doc/source/api/nova..validate.rst | 6 -- doc/source/api/nova..virt.connection.rst | 6 -- doc/source/api/nova..virt.fake.rst | 6 -- doc/source/api/nova..virt.images.rst | 6 -- doc/source/api/nova..virt.libvirt_conn.rst | 6 -- doc/source/api/nova..virt.xenapi.rst | 6 -- doc/source/api/nova..volume.driver.rst | 6 -- doc/source/api/nova..volume.manager.rst | 6 -- doc/source/api/nova..wsgi.rst | 6 -- 98 files changed, 772 deletions(-) delete mode 100644 doc/.autogenerated delete mode 100644 doc/source/api/autoindex.rst delete mode 100644 doc/source/api/nova..adminclient.rst delete mode 100644 doc/source/api/nova..api.cloud.rst delete mode 100644 doc/source/api/nova..api.ec2.admin.rst delete mode 100644 doc/source/api/nova..api.ec2.apirequest.rst delete mode 100644 doc/source/api/nova..api.ec2.cloud.rst delete mode 100644 doc/source/api/nova..api.ec2.images.rst delete mode 100644 doc/source/api/nova..api.ec2.metadatarequesthandler.rst delete mode 100644 doc/source/api/nova..api.openstack.auth.rst delete mode 100644 doc/source/api/nova..api.openstack.backup_schedules.rst delete mode 100644 doc/source/api/nova..api.openstack.faults.rst delete mode 100644 doc/source/api/nova..api.openstack.flavors.rst delete mode 100644 doc/source/api/nova..api.openstack.images.rst delete mode 100644 doc/source/api/nova..api.openstack.servers.rst delete mode 100644 doc/source/api/nova..api.openstack.sharedipgroups.rst delete mode 100644 doc/source/api/nova..auth.dbdriver.rst delete mode 100644 doc/source/api/nova..auth.fakeldap.rst delete mode 100644 doc/source/api/nova..auth.ldapdriver.rst delete mode 100644 doc/source/api/nova..auth.manager.rst delete mode 100644 doc/source/api/nova..auth.signer.rst delete mode 100644 doc/source/api/nova..cloudpipe.pipelib.rst delete mode 100644 doc/source/api/nova..compute.disk.rst delete mode 100644 doc/source/api/nova..compute.instance_types.rst delete mode 100644 doc/source/api/nova..compute.manager.rst delete mode 100644 doc/source/api/nova..compute.monitor.rst delete mode 100644 doc/source/api/nova..compute.power_state.rst delete mode 100644 doc/source/api/nova..context.rst delete mode 100644 doc/source/api/nova..crypto.rst delete mode 100644 doc/source/api/nova..db.api.rst delete mode 100644 doc/source/api/nova..db.sqlalchemy.api.rst delete mode 100644 doc/source/api/nova..db.sqlalchemy.models.rst delete mode 100644 doc/source/api/nova..db.sqlalchemy.session.rst delete mode 100644 doc/source/api/nova..exception.rst delete mode 100644 doc/source/api/nova..fakerabbit.rst delete mode 100644 doc/source/api/nova..flags.rst delete mode 100644 doc/source/api/nova..image.service.rst delete mode 100644 doc/source/api/nova..manager.rst delete mode 100644 doc/source/api/nova..network.linux_net.rst delete mode 100644 doc/source/api/nova..network.manager.rst delete mode 100644 doc/source/api/nova..objectstore.bucket.rst delete mode 100644 doc/source/api/nova..objectstore.handler.rst delete mode 100644 doc/source/api/nova..objectstore.image.rst delete mode 100644 doc/source/api/nova..objectstore.stored.rst delete mode 100644 doc/source/api/nova..process.rst delete mode 100644 doc/source/api/nova..quota.rst delete mode 100644 doc/source/api/nova..rpc.rst delete mode 100644 doc/source/api/nova..scheduler.chance.rst delete mode 100644 doc/source/api/nova..scheduler.driver.rst delete mode 100644 doc/source/api/nova..scheduler.manager.rst delete mode 100644 doc/source/api/nova..scheduler.simple.rst delete mode 100644 doc/source/api/nova..server.rst delete mode 100644 doc/source/api/nova..service.rst delete mode 100644 doc/source/api/nova..test.rst delete mode 100644 doc/source/api/nova..tests.access_unittest.rst delete mode 100644 doc/source/api/nova..tests.api.fakes.rst delete mode 100644 doc/source/api/nova..tests.api.openstack.fakes.rst delete mode 100644 doc/source/api/nova..tests.api.openstack.test_api.rst delete mode 100644 doc/source/api/nova..tests.api.openstack.test_auth.rst delete mode 100644 doc/source/api/nova..tests.api.openstack.test_faults.rst delete mode 100644 doc/source/api/nova..tests.api.openstack.test_flavors.rst delete mode 100644 doc/source/api/nova..tests.api.openstack.test_images.rst delete mode 100644 doc/source/api/nova..tests.api.openstack.test_ratelimiting.rst delete mode 100644 doc/source/api/nova..tests.api.openstack.test_servers.rst delete mode 100644 doc/source/api/nova..tests.api.openstack.test_sharedipgroups.rst delete mode 100644 doc/source/api/nova..tests.api.test_wsgi.rst delete mode 100644 doc/source/api/nova..tests.api_integration.rst delete mode 100644 doc/source/api/nova..tests.api_unittest.rst delete mode 100644 doc/source/api/nova..tests.auth_unittest.rst delete mode 100644 doc/source/api/nova..tests.cloud_unittest.rst delete mode 100644 doc/source/api/nova..tests.compute_unittest.rst delete mode 100644 doc/source/api/nova..tests.declare_flags.rst delete mode 100644 doc/source/api/nova..tests.fake_flags.rst delete mode 100644 doc/source/api/nova..tests.flags_unittest.rst delete mode 100644 doc/source/api/nova..tests.network_unittest.rst delete mode 100644 doc/source/api/nova..tests.objectstore_unittest.rst delete mode 100644 doc/source/api/nova..tests.process_unittest.rst delete mode 100644 doc/source/api/nova..tests.quota_unittest.rst delete mode 100644 doc/source/api/nova..tests.real_flags.rst delete mode 100644 doc/source/api/nova..tests.rpc_unittest.rst delete mode 100644 doc/source/api/nova..tests.runtime_flags.rst delete mode 100644 doc/source/api/nova..tests.scheduler_unittest.rst delete mode 100644 doc/source/api/nova..tests.service_unittest.rst delete mode 100644 doc/source/api/nova..tests.twistd_unittest.rst delete mode 100644 doc/source/api/nova..tests.validator_unittest.rst delete mode 100644 doc/source/api/nova..tests.virt_unittest.rst delete mode 100644 doc/source/api/nova..tests.volume_unittest.rst delete mode 100644 doc/source/api/nova..twistd.rst delete mode 100644 doc/source/api/nova..utils.rst delete mode 100644 doc/source/api/nova..validate.rst delete mode 100644 doc/source/api/nova..virt.connection.rst delete mode 100644 doc/source/api/nova..virt.fake.rst delete mode 100644 doc/source/api/nova..virt.images.rst delete mode 100644 doc/source/api/nova..virt.libvirt_conn.rst delete mode 100644 doc/source/api/nova..virt.xenapi.rst delete mode 100644 doc/source/api/nova..volume.driver.rst delete mode 100644 doc/source/api/nova..volume.manager.rst delete mode 100644 doc/source/api/nova..wsgi.rst diff --git a/doc/.autogenerated b/doc/.autogenerated deleted file mode 100644 index 3a70f87808d1..000000000000 --- a/doc/.autogenerated +++ /dev/null @@ -1,97 +0,0 @@ -source/api/nova..adminclient.rst -source/api/nova..api.cloud.rst -source/api/nova..api.ec2.admin.rst -source/api/nova..api.ec2.apirequest.rst -source/api/nova..api.ec2.cloud.rst -source/api/nova..api.ec2.images.rst -source/api/nova..api.ec2.metadatarequesthandler.rst -source/api/nova..api.openstack.auth.rst -source/api/nova..api.openstack.backup_schedules.rst -source/api/nova..api.openstack.faults.rst -source/api/nova..api.openstack.flavors.rst -source/api/nova..api.openstack.images.rst -source/api/nova..api.openstack.servers.rst -source/api/nova..api.openstack.sharedipgroups.rst -source/api/nova..auth.dbdriver.rst -source/api/nova..auth.fakeldap.rst -source/api/nova..auth.ldapdriver.rst -source/api/nova..auth.manager.rst -source/api/nova..auth.signer.rst -source/api/nova..cloudpipe.pipelib.rst -source/api/nova..compute.disk.rst -source/api/nova..compute.instance_types.rst -source/api/nova..compute.manager.rst -source/api/nova..compute.monitor.rst -source/api/nova..compute.power_state.rst -source/api/nova..context.rst -source/api/nova..crypto.rst -source/api/nova..db.api.rst -source/api/nova..db.sqlalchemy.api.rst -source/api/nova..db.sqlalchemy.models.rst -source/api/nova..db.sqlalchemy.session.rst -source/api/nova..exception.rst -source/api/nova..fakerabbit.rst -source/api/nova..flags.rst -source/api/nova..image.service.rst -source/api/nova..manager.rst -source/api/nova..network.linux_net.rst -source/api/nova..network.manager.rst -source/api/nova..objectstore.bucket.rst -source/api/nova..objectstore.handler.rst -source/api/nova..objectstore.image.rst -source/api/nova..objectstore.stored.rst -source/api/nova..process.rst -source/api/nova..quota.rst -source/api/nova..rpc.rst -source/api/nova..scheduler.chance.rst -source/api/nova..scheduler.driver.rst -source/api/nova..scheduler.manager.rst -source/api/nova..scheduler.simple.rst -source/api/nova..server.rst -source/api/nova..service.rst -source/api/nova..test.rst -source/api/nova..tests.access_unittest.rst -source/api/nova..tests.api.fakes.rst -source/api/nova..tests.api.openstack.fakes.rst -source/api/nova..tests.api.openstack.test_api.rst -source/api/nova..tests.api.openstack.test_auth.rst -source/api/nova..tests.api.openstack.test_faults.rst -source/api/nova..tests.api.openstack.test_flavors.rst -source/api/nova..tests.api.openstack.test_images.rst -source/api/nova..tests.api.openstack.test_ratelimiting.rst -source/api/nova..tests.api.openstack.test_servers.rst -source/api/nova..tests.api.openstack.test_sharedipgroups.rst -source/api/nova..tests.api.test_wsgi.rst -source/api/nova..tests.api_integration.rst -source/api/nova..tests.api_unittest.rst -source/api/nova..tests.auth_unittest.rst -source/api/nova..tests.cloud_unittest.rst -source/api/nova..tests.compute_unittest.rst -source/api/nova..tests.declare_flags.rst -source/api/nova..tests.fake_flags.rst -source/api/nova..tests.flags_unittest.rst -source/api/nova..tests.network_unittest.rst -source/api/nova..tests.objectstore_unittest.rst -source/api/nova..tests.process_unittest.rst -source/api/nova..tests.quota_unittest.rst -source/api/nova..tests.real_flags.rst -source/api/nova..tests.rpc_unittest.rst -source/api/nova..tests.runtime_flags.rst -source/api/nova..tests.scheduler_unittest.rst -source/api/nova..tests.service_unittest.rst -source/api/nova..tests.twistd_unittest.rst -source/api/nova..tests.validator_unittest.rst -source/api/nova..tests.virt_unittest.rst -source/api/nova..tests.volume_unittest.rst -source/api/nova..twistd.rst -source/api/nova..utils.rst -source/api/nova..validate.rst -source/api/nova..virt.connection.rst -source/api/nova..virt.fake.rst -source/api/nova..virt.images.rst -source/api/nova..virt.libvirt_conn.rst -source/api/nova..virt.xenapi.rst -source/api/nova..volume.driver.rst -source/api/nova..volume.manager.rst -source/api/nova..wsgi.rst -source/api/autoindex.rst diff --git a/doc/source/api/autoindex.rst b/doc/source/api/autoindex.rst deleted file mode 100644 index 6265b082be52..000000000000 --- a/doc/source/api/autoindex.rst +++ /dev/null @@ -1,99 +0,0 @@ -.. toctree:: - :maxdepth: 1 - - nova..adminclient.rst - nova..api.cloud.rst - nova..api.ec2.admin.rst - nova..api.ec2.apirequest.rst - nova..api.ec2.cloud.rst - nova..api.ec2.images.rst - nova..api.ec2.metadatarequesthandler.rst - nova..api.openstack.auth.rst - nova..api.openstack.backup_schedules.rst - nova..api.openstack.faults.rst - nova..api.openstack.flavors.rst - nova..api.openstack.images.rst - nova..api.openstack.servers.rst - nova..api.openstack.sharedipgroups.rst - nova..auth.dbdriver.rst - nova..auth.fakeldap.rst - nova..auth.ldapdriver.rst - nova..auth.manager.rst - nova..auth.signer.rst - nova..cloudpipe.pipelib.rst - nova..compute.disk.rst - nova..compute.instance_types.rst - nova..compute.manager.rst - nova..compute.monitor.rst - nova..compute.power_state.rst - nova..context.rst - nova..crypto.rst - nova..db.api.rst - nova..db.sqlalchemy.api.rst - nova..db.sqlalchemy.models.rst - nova..db.sqlalchemy.session.rst - nova..exception.rst - nova..fakerabbit.rst - nova..flags.rst - nova..image.service.rst - nova..manager.rst - nova..network.linux_net.rst - nova..network.manager.rst - nova..objectstore.bucket.rst - nova..objectstore.handler.rst - nova..objectstore.image.rst - nova..objectstore.stored.rst - nova..process.rst - nova..quota.rst - nova..rpc.rst - nova..scheduler.chance.rst - nova..scheduler.driver.rst - nova..scheduler.manager.rst - nova..scheduler.simple.rst - nova..server.rst - nova..service.rst - nova..test.rst - nova..tests.access_unittest.rst - nova..tests.api.fakes.rst - nova..tests.api.openstack.fakes.rst - nova..tests.api.openstack.test_api.rst - nova..tests.api.openstack.test_auth.rst - nova..tests.api.openstack.test_faults.rst - nova..tests.api.openstack.test_flavors.rst - nova..tests.api.openstack.test_images.rst - nova..tests.api.openstack.test_ratelimiting.rst - nova..tests.api.openstack.test_servers.rst - nova..tests.api.openstack.test_sharedipgroups.rst - nova..tests.api.test_wsgi.rst - nova..tests.api_integration.rst - nova..tests.api_unittest.rst - nova..tests.auth_unittest.rst - nova..tests.cloud_unittest.rst - nova..tests.compute_unittest.rst - nova..tests.declare_flags.rst - nova..tests.fake_flags.rst - nova..tests.flags_unittest.rst - nova..tests.network_unittest.rst - nova..tests.objectstore_unittest.rst - nova..tests.process_unittest.rst - nova..tests.quota_unittest.rst - nova..tests.real_flags.rst - nova..tests.rpc_unittest.rst - nova..tests.runtime_flags.rst - nova..tests.scheduler_unittest.rst - nova..tests.service_unittest.rst - nova..tests.twistd_unittest.rst - nova..tests.validator_unittest.rst - nova..tests.virt_unittest.rst - nova..tests.volume_unittest.rst - nova..twistd.rst - nova..utils.rst - nova..validate.rst - nova..virt.connection.rst - nova..virt.fake.rst - nova..virt.images.rst - nova..virt.libvirt_conn.rst - nova..virt.xenapi.rst - nova..volume.driver.rst - nova..volume.manager.rst - nova..wsgi.rst diff --git a/doc/source/api/nova..adminclient.rst b/doc/source/api/nova..adminclient.rst deleted file mode 100644 index 35fa839e1b1b..000000000000 --- a/doc/source/api/nova..adminclient.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..adminclient` Module -============================================================================== -.. automodule:: nova..adminclient - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..api.cloud.rst b/doc/source/api/nova..api.cloud.rst deleted file mode 100644 index 4138401858ea..000000000000 --- a/doc/source/api/nova..api.cloud.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..api.cloud` Module -============================================================================== -.. automodule:: nova..api.cloud - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..api.ec2.admin.rst b/doc/source/api/nova..api.ec2.admin.rst deleted file mode 100644 index 4e9ab308b4dd..000000000000 --- a/doc/source/api/nova..api.ec2.admin.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..api.ec2.admin` Module -============================================================================== -.. automodule:: nova..api.ec2.admin - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..api.ec2.apirequest.rst b/doc/source/api/nova..api.ec2.apirequest.rst deleted file mode 100644 index c17a2ff3ad29..000000000000 --- a/doc/source/api/nova..api.ec2.apirequest.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..api.ec2.apirequest` Module -============================================================================== -.. automodule:: nova..api.ec2.apirequest - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..api.ec2.cloud.rst b/doc/source/api/nova..api.ec2.cloud.rst deleted file mode 100644 index f6145c217f8c..000000000000 --- a/doc/source/api/nova..api.ec2.cloud.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..api.ec2.cloud` Module -============================================================================== -.. automodule:: nova..api.ec2.cloud - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..api.ec2.images.rst b/doc/source/api/nova..api.ec2.images.rst deleted file mode 100644 index 012d800e47fb..000000000000 --- a/doc/source/api/nova..api.ec2.images.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..api.ec2.images` Module -============================================================================== -.. automodule:: nova..api.ec2.images - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..api.ec2.metadatarequesthandler.rst b/doc/source/api/nova..api.ec2.metadatarequesthandler.rst deleted file mode 100644 index 75f5169e5f27..000000000000 --- a/doc/source/api/nova..api.ec2.metadatarequesthandler.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..api.ec2.metadatarequesthandler` Module -============================================================================== -.. automodule:: nova..api.ec2.metadatarequesthandler - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..api.openstack.auth.rst b/doc/source/api/nova..api.openstack.auth.rst deleted file mode 100644 index 8c3f8f2da626..000000000000 --- a/doc/source/api/nova..api.openstack.auth.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..api.openstack.auth` Module -============================================================================== -.. automodule:: nova..api.openstack.auth - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..api.openstack.backup_schedules.rst b/doc/source/api/nova..api.openstack.backup_schedules.rst deleted file mode 100644 index 6b406f12db4b..000000000000 --- a/doc/source/api/nova..api.openstack.backup_schedules.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..api.openstack.backup_schedules` Module -============================================================================== -.. automodule:: nova..api.openstack.backup_schedules - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..api.openstack.faults.rst b/doc/source/api/nova..api.openstack.faults.rst deleted file mode 100644 index 7b25561f74b7..000000000000 --- a/doc/source/api/nova..api.openstack.faults.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..api.openstack.faults` Module -============================================================================== -.. automodule:: nova..api.openstack.faults - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..api.openstack.flavors.rst b/doc/source/api/nova..api.openstack.flavors.rst deleted file mode 100644 index 0deb724deba1..000000000000 --- a/doc/source/api/nova..api.openstack.flavors.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..api.openstack.flavors` Module -============================================================================== -.. automodule:: nova..api.openstack.flavors - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..api.openstack.images.rst b/doc/source/api/nova..api.openstack.images.rst deleted file mode 100644 index 82bd5f1e86a3..000000000000 --- a/doc/source/api/nova..api.openstack.images.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..api.openstack.images` Module -============================================================================== -.. automodule:: nova..api.openstack.images - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..api.openstack.servers.rst b/doc/source/api/nova..api.openstack.servers.rst deleted file mode 100644 index c36856ea2bc5..000000000000 --- a/doc/source/api/nova..api.openstack.servers.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..api.openstack.servers` Module -============================================================================== -.. automodule:: nova..api.openstack.servers - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..api.openstack.sharedipgroups.rst b/doc/source/api/nova..api.openstack.sharedipgroups.rst deleted file mode 100644 index 07632acc8e49..000000000000 --- a/doc/source/api/nova..api.openstack.sharedipgroups.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..api.openstack.sharedipgroups` Module -============================================================================== -.. automodule:: nova..api.openstack.sharedipgroups - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..auth.dbdriver.rst b/doc/source/api/nova..auth.dbdriver.rst deleted file mode 100644 index 7de68b6e08bc..000000000000 --- a/doc/source/api/nova..auth.dbdriver.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..auth.dbdriver` Module -============================================================================== -.. automodule:: nova..auth.dbdriver - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..auth.fakeldap.rst b/doc/source/api/nova..auth.fakeldap.rst deleted file mode 100644 index ca8a3ad4d560..000000000000 --- a/doc/source/api/nova..auth.fakeldap.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..auth.fakeldap` Module -============================================================================== -.. automodule:: nova..auth.fakeldap - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..auth.ldapdriver.rst b/doc/source/api/nova..auth.ldapdriver.rst deleted file mode 100644 index c444635228f8..000000000000 --- a/doc/source/api/nova..auth.ldapdriver.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..auth.ldapdriver` Module -============================================================================== -.. automodule:: nova..auth.ldapdriver - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..auth.manager.rst b/doc/source/api/nova..auth.manager.rst deleted file mode 100644 index bc5ce2ec31e8..000000000000 --- a/doc/source/api/nova..auth.manager.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..auth.manager` Module -============================================================================== -.. automodule:: nova..auth.manager - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..auth.signer.rst b/doc/source/api/nova..auth.signer.rst deleted file mode 100644 index aad824eada39..000000000000 --- a/doc/source/api/nova..auth.signer.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..auth.signer` Module -============================================================================== -.. automodule:: nova..auth.signer - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..cloudpipe.pipelib.rst b/doc/source/api/nova..cloudpipe.pipelib.rst deleted file mode 100644 index 054aaf484fe2..000000000000 --- a/doc/source/api/nova..cloudpipe.pipelib.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..cloudpipe.pipelib` Module -============================================================================== -.. automodule:: nova..cloudpipe.pipelib - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..compute.disk.rst b/doc/source/api/nova..compute.disk.rst deleted file mode 100644 index 6410af6f3899..000000000000 --- a/doc/source/api/nova..compute.disk.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..compute.disk` Module -============================================================================== -.. automodule:: nova..compute.disk - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..compute.instance_types.rst b/doc/source/api/nova..compute.instance_types.rst deleted file mode 100644 index d206ff3a4a1f..000000000000 --- a/doc/source/api/nova..compute.instance_types.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..compute.instance_types` Module -============================================================================== -.. automodule:: nova..compute.instance_types - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..compute.manager.rst b/doc/source/api/nova..compute.manager.rst deleted file mode 100644 index 33a337c39843..000000000000 --- a/doc/source/api/nova..compute.manager.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..compute.manager` Module -============================================================================== -.. automodule:: nova..compute.manager - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..compute.monitor.rst b/doc/source/api/nova..compute.monitor.rst deleted file mode 100644 index a91169ecd309..000000000000 --- a/doc/source/api/nova..compute.monitor.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..compute.monitor` Module -============================================================================== -.. automodule:: nova..compute.monitor - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..compute.power_state.rst b/doc/source/api/nova..compute.power_state.rst deleted file mode 100644 index 41b1080e5a0e..000000000000 --- a/doc/source/api/nova..compute.power_state.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..compute.power_state` Module -============================================================================== -.. automodule:: nova..compute.power_state - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..context.rst b/doc/source/api/nova..context.rst deleted file mode 100644 index 9de1adb24827..000000000000 --- a/doc/source/api/nova..context.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..context` Module -============================================================================== -.. automodule:: nova..context - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..crypto.rst b/doc/source/api/nova..crypto.rst deleted file mode 100644 index af9f636344ab..000000000000 --- a/doc/source/api/nova..crypto.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..crypto` Module -============================================================================== -.. automodule:: nova..crypto - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..db.api.rst b/doc/source/api/nova..db.api.rst deleted file mode 100644 index 6d998fbb2f90..000000000000 --- a/doc/source/api/nova..db.api.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..db.api` Module -============================================================================== -.. automodule:: nova..db.api - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..db.sqlalchemy.api.rst b/doc/source/api/nova..db.sqlalchemy.api.rst deleted file mode 100644 index 76d0c1bd376f..000000000000 --- a/doc/source/api/nova..db.sqlalchemy.api.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..db.sqlalchemy.api` Module -============================================================================== -.. automodule:: nova..db.sqlalchemy.api - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..db.sqlalchemy.models.rst b/doc/source/api/nova..db.sqlalchemy.models.rst deleted file mode 100644 index 9c795d7f51f5..000000000000 --- a/doc/source/api/nova..db.sqlalchemy.models.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..db.sqlalchemy.models` Module -============================================================================== -.. automodule:: nova..db.sqlalchemy.models - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..db.sqlalchemy.session.rst b/doc/source/api/nova..db.sqlalchemy.session.rst deleted file mode 100644 index cbfd6416a888..000000000000 --- a/doc/source/api/nova..db.sqlalchemy.session.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..db.sqlalchemy.session` Module -============================================================================== -.. automodule:: nova..db.sqlalchemy.session - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..exception.rst b/doc/source/api/nova..exception.rst deleted file mode 100644 index 97ac6b752d27..000000000000 --- a/doc/source/api/nova..exception.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..exception` Module -============================================================================== -.. automodule:: nova..exception - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..fakerabbit.rst b/doc/source/api/nova..fakerabbit.rst deleted file mode 100644 index f1e27c2664fb..000000000000 --- a/doc/source/api/nova..fakerabbit.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..fakerabbit` Module -============================================================================== -.. automodule:: nova..fakerabbit - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..flags.rst b/doc/source/api/nova..flags.rst deleted file mode 100644 index 08165be44f46..000000000000 --- a/doc/source/api/nova..flags.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..flags` Module -============================================================================== -.. automodule:: nova..flags - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..image.service.rst b/doc/source/api/nova..image.service.rst deleted file mode 100644 index 78ef1eccadac..000000000000 --- a/doc/source/api/nova..image.service.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..image.service` Module -============================================================================== -.. automodule:: nova..image.service - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..manager.rst b/doc/source/api/nova..manager.rst deleted file mode 100644 index 576902491c7e..000000000000 --- a/doc/source/api/nova..manager.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..manager` Module -============================================================================== -.. automodule:: nova..manager - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..network.linux_net.rst b/doc/source/api/nova..network.linux_net.rst deleted file mode 100644 index 7af78d5ade7f..000000000000 --- a/doc/source/api/nova..network.linux_net.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..network.linux_net` Module -============================================================================== -.. automodule:: nova..network.linux_net - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..network.manager.rst b/doc/source/api/nova..network.manager.rst deleted file mode 100644 index 0ea70553325e..000000000000 --- a/doc/source/api/nova..network.manager.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..network.manager` Module -============================================================================== -.. automodule:: nova..network.manager - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..objectstore.bucket.rst b/doc/source/api/nova..objectstore.bucket.rst deleted file mode 100644 index 3bfdf639ccd4..000000000000 --- a/doc/source/api/nova..objectstore.bucket.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..objectstore.bucket` Module -============================================================================== -.. automodule:: nova..objectstore.bucket - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..objectstore.handler.rst b/doc/source/api/nova..objectstore.handler.rst deleted file mode 100644 index 0eb8c4efb497..000000000000 --- a/doc/source/api/nova..objectstore.handler.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..objectstore.handler` Module -============================================================================== -.. automodule:: nova..objectstore.handler - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..objectstore.image.rst b/doc/source/api/nova..objectstore.image.rst deleted file mode 100644 index fa4c971f119a..000000000000 --- a/doc/source/api/nova..objectstore.image.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..objectstore.image` Module -============================================================================== -.. automodule:: nova..objectstore.image - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..objectstore.stored.rst b/doc/source/api/nova..objectstore.stored.rst deleted file mode 100644 index 2b1d997a3fd0..000000000000 --- a/doc/source/api/nova..objectstore.stored.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..objectstore.stored` Module -============================================================================== -.. automodule:: nova..objectstore.stored - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..process.rst b/doc/source/api/nova..process.rst deleted file mode 100644 index 91eff8379070..000000000000 --- a/doc/source/api/nova..process.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..process` Module -============================================================================== -.. automodule:: nova..process - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..quota.rst b/doc/source/api/nova..quota.rst deleted file mode 100644 index 4140d95d66ac..000000000000 --- a/doc/source/api/nova..quota.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..quota` Module -============================================================================== -.. automodule:: nova..quota - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..rpc.rst b/doc/source/api/nova..rpc.rst deleted file mode 100644 index 5b2a9b8e2333..000000000000 --- a/doc/source/api/nova..rpc.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..rpc` Module -============================================================================== -.. automodule:: nova..rpc - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..scheduler.chance.rst b/doc/source/api/nova..scheduler.chance.rst deleted file mode 100644 index 89c074c8f6c7..000000000000 --- a/doc/source/api/nova..scheduler.chance.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..scheduler.chance` Module -============================================================================== -.. automodule:: nova..scheduler.chance - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..scheduler.driver.rst b/doc/source/api/nova..scheduler.driver.rst deleted file mode 100644 index 793ed9c7b3ae..000000000000 --- a/doc/source/api/nova..scheduler.driver.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..scheduler.driver` Module -============================================================================== -.. automodule:: nova..scheduler.driver - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..scheduler.manager.rst b/doc/source/api/nova..scheduler.manager.rst deleted file mode 100644 index d0fc7c4230db..000000000000 --- a/doc/source/api/nova..scheduler.manager.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..scheduler.manager` Module -============================================================================== -.. automodule:: nova..scheduler.manager - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..scheduler.simple.rst b/doc/source/api/nova..scheduler.simple.rst deleted file mode 100644 index dacc2cf3088c..000000000000 --- a/doc/source/api/nova..scheduler.simple.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..scheduler.simple` Module -============================================================================== -.. automodule:: nova..scheduler.simple - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..server.rst b/doc/source/api/nova..server.rst deleted file mode 100644 index 7cb2cfa5465d..000000000000 --- a/doc/source/api/nova..server.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..server` Module -============================================================================== -.. automodule:: nova..server - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..service.rst b/doc/source/api/nova..service.rst deleted file mode 100644 index 2d2dfcf2e52a..000000000000 --- a/doc/source/api/nova..service.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..service` Module -============================================================================== -.. automodule:: nova..service - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..test.rst b/doc/source/api/nova..test.rst deleted file mode 100644 index a6bdb6f1fffa..000000000000 --- a/doc/source/api/nova..test.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..test` Module -============================================================================== -.. automodule:: nova..test - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.access_unittest.rst b/doc/source/api/nova..tests.access_unittest.rst deleted file mode 100644 index 89554e430a4f..000000000000 --- a/doc/source/api/nova..tests.access_unittest.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.access_unittest` Module -============================================================================== -.. automodule:: nova..tests.access_unittest - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.api.fakes.rst b/doc/source/api/nova..tests.api.fakes.rst deleted file mode 100644 index 5728b18f3616..000000000000 --- a/doc/source/api/nova..tests.api.fakes.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.api.fakes` Module -============================================================================== -.. automodule:: nova..tests.api.fakes - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.api.openstack.fakes.rst b/doc/source/api/nova..tests.api.openstack.fakes.rst deleted file mode 100644 index 4a9ff593899f..000000000000 --- a/doc/source/api/nova..tests.api.openstack.fakes.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.api.openstack.fakes` Module -============================================================================== -.. automodule:: nova..tests.api.openstack.fakes - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.api.openstack.test_api.rst b/doc/source/api/nova..tests.api.openstack.test_api.rst deleted file mode 100644 index 68106d221f83..000000000000 --- a/doc/source/api/nova..tests.api.openstack.test_api.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.api.openstack.test_api` Module -============================================================================== -.. automodule:: nova..tests.api.openstack.test_api - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.api.openstack.test_auth.rst b/doc/source/api/nova..tests.api.openstack.test_auth.rst deleted file mode 100644 index 9f0011669a88..000000000000 --- a/doc/source/api/nova..tests.api.openstack.test_auth.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.api.openstack.test_auth` Module -============================================================================== -.. automodule:: nova..tests.api.openstack.test_auth - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.api.openstack.test_faults.rst b/doc/source/api/nova..tests.api.openstack.test_faults.rst deleted file mode 100644 index b839ae8a3c77..000000000000 --- a/doc/source/api/nova..tests.api.openstack.test_faults.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.api.openstack.test_faults` Module -============================================================================== -.. automodule:: nova..tests.api.openstack.test_faults - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.api.openstack.test_flavors.rst b/doc/source/api/nova..tests.api.openstack.test_flavors.rst deleted file mode 100644 index 471fac56ec84..000000000000 --- a/doc/source/api/nova..tests.api.openstack.test_flavors.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.api.openstack.test_flavors` Module -============================================================================== -.. automodule:: nova..tests.api.openstack.test_flavors - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.api.openstack.test_images.rst b/doc/source/api/nova..tests.api.openstack.test_images.rst deleted file mode 100644 index 57ae93c8c63e..000000000000 --- a/doc/source/api/nova..tests.api.openstack.test_images.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.api.openstack.test_images` Module -============================================================================== -.. automodule:: nova..tests.api.openstack.test_images - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.api.openstack.test_ratelimiting.rst b/doc/source/api/nova..tests.api.openstack.test_ratelimiting.rst deleted file mode 100644 index 9a857f795941..000000000000 --- a/doc/source/api/nova..tests.api.openstack.test_ratelimiting.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.api.openstack.test_ratelimiting` Module -============================================================================== -.. automodule:: nova..tests.api.openstack.test_ratelimiting - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.api.openstack.test_servers.rst b/doc/source/api/nova..tests.api.openstack.test_servers.rst deleted file mode 100644 index ea602e6ab16c..000000000000 --- a/doc/source/api/nova..tests.api.openstack.test_servers.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.api.openstack.test_servers` Module -============================================================================== -.. automodule:: nova..tests.api.openstack.test_servers - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.api.openstack.test_sharedipgroups.rst b/doc/source/api/nova..tests.api.openstack.test_sharedipgroups.rst deleted file mode 100644 index 1fad49147d62..000000000000 --- a/doc/source/api/nova..tests.api.openstack.test_sharedipgroups.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.api.openstack.test_sharedipgroups` Module -============================================================================== -.. automodule:: nova..tests.api.openstack.test_sharedipgroups - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.api.test_wsgi.rst b/doc/source/api/nova..tests.api.test_wsgi.rst deleted file mode 100644 index 8e79caa4dd73..000000000000 --- a/doc/source/api/nova..tests.api.test_wsgi.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.api.test_wsgi` Module -============================================================================== -.. automodule:: nova..tests.api.test_wsgi - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.api_integration.rst b/doc/source/api/nova..tests.api_integration.rst deleted file mode 100644 index fd217acf73c0..000000000000 --- a/doc/source/api/nova..tests.api_integration.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.api_integration` Module -============================================================================== -.. automodule:: nova..tests.api_integration - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.api_unittest.rst b/doc/source/api/nova..tests.api_unittest.rst deleted file mode 100644 index 44a65d48c463..000000000000 --- a/doc/source/api/nova..tests.api_unittest.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.api_unittest` Module -============================================================================== -.. automodule:: nova..tests.api_unittest - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.auth_unittest.rst b/doc/source/api/nova..tests.auth_unittest.rst deleted file mode 100644 index 5805dcf38b8e..000000000000 --- a/doc/source/api/nova..tests.auth_unittest.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.auth_unittest` Module -============================================================================== -.. automodule:: nova..tests.auth_unittest - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.cloud_unittest.rst b/doc/source/api/nova..tests.cloud_unittest.rst deleted file mode 100644 index d2ca3b013579..000000000000 --- a/doc/source/api/nova..tests.cloud_unittest.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.cloud_unittest` Module -============================================================================== -.. automodule:: nova..tests.cloud_unittest - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.compute_unittest.rst b/doc/source/api/nova..tests.compute_unittest.rst deleted file mode 100644 index 6a30bf7441f7..000000000000 --- a/doc/source/api/nova..tests.compute_unittest.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.compute_unittest` Module -============================================================================== -.. automodule:: nova..tests.compute_unittest - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.declare_flags.rst b/doc/source/api/nova..tests.declare_flags.rst deleted file mode 100644 index 524e72e9133d..000000000000 --- a/doc/source/api/nova..tests.declare_flags.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.declare_flags` Module -============================================================================== -.. automodule:: nova..tests.declare_flags - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.fake_flags.rst b/doc/source/api/nova..tests.fake_flags.rst deleted file mode 100644 index a8dc3df36efe..000000000000 --- a/doc/source/api/nova..tests.fake_flags.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.fake_flags` Module -============================================================================== -.. automodule:: nova..tests.fake_flags - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.flags_unittest.rst b/doc/source/api/nova..tests.flags_unittest.rst deleted file mode 100644 index 61087e683336..000000000000 --- a/doc/source/api/nova..tests.flags_unittest.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.flags_unittest` Module -============================================================================== -.. automodule:: nova..tests.flags_unittest - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.network_unittest.rst b/doc/source/api/nova..tests.network_unittest.rst deleted file mode 100644 index df057d813efb..000000000000 --- a/doc/source/api/nova..tests.network_unittest.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.network_unittest` Module -============================================================================== -.. automodule:: nova..tests.network_unittest - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.objectstore_unittest.rst b/doc/source/api/nova..tests.objectstore_unittest.rst deleted file mode 100644 index 0ae252f049c7..000000000000 --- a/doc/source/api/nova..tests.objectstore_unittest.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.objectstore_unittest` Module -============================================================================== -.. automodule:: nova..tests.objectstore_unittest - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.process_unittest.rst b/doc/source/api/nova..tests.process_unittest.rst deleted file mode 100644 index 30d1e129c942..000000000000 --- a/doc/source/api/nova..tests.process_unittest.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.process_unittest` Module -============================================================================== -.. automodule:: nova..tests.process_unittest - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.quota_unittest.rst b/doc/source/api/nova..tests.quota_unittest.rst deleted file mode 100644 index 6ab81310489d..000000000000 --- a/doc/source/api/nova..tests.quota_unittest.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.quota_unittest` Module -============================================================================== -.. automodule:: nova..tests.quota_unittest - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.real_flags.rst b/doc/source/api/nova..tests.real_flags.rst deleted file mode 100644 index e9c0d1abdc66..000000000000 --- a/doc/source/api/nova..tests.real_flags.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.real_flags` Module -============================================================================== -.. automodule:: nova..tests.real_flags - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.rpc_unittest.rst b/doc/source/api/nova..tests.rpc_unittest.rst deleted file mode 100644 index e6c7ceb2e04a..000000000000 --- a/doc/source/api/nova..tests.rpc_unittest.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.rpc_unittest` Module -============================================================================== -.. automodule:: nova..tests.rpc_unittest - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.runtime_flags.rst b/doc/source/api/nova..tests.runtime_flags.rst deleted file mode 100644 index 984e21199b4c..000000000000 --- a/doc/source/api/nova..tests.runtime_flags.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.runtime_flags` Module -============================================================================== -.. automodule:: nova..tests.runtime_flags - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.scheduler_unittest.rst b/doc/source/api/nova..tests.scheduler_unittest.rst deleted file mode 100644 index ae3a0661658d..000000000000 --- a/doc/source/api/nova..tests.scheduler_unittest.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.scheduler_unittest` Module -============================================================================== -.. automodule:: nova..tests.scheduler_unittest - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.service_unittest.rst b/doc/source/api/nova..tests.service_unittest.rst deleted file mode 100644 index c7c746d17e10..000000000000 --- a/doc/source/api/nova..tests.service_unittest.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.service_unittest` Module -============================================================================== -.. automodule:: nova..tests.service_unittest - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.twistd_unittest.rst b/doc/source/api/nova..tests.twistd_unittest.rst deleted file mode 100644 index ce88202e102a..000000000000 --- a/doc/source/api/nova..tests.twistd_unittest.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.twistd_unittest` Module -============================================================================== -.. automodule:: nova..tests.twistd_unittest - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.validator_unittest.rst b/doc/source/api/nova..tests.validator_unittest.rst deleted file mode 100644 index 980284327f89..000000000000 --- a/doc/source/api/nova..tests.validator_unittest.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.validator_unittest` Module -============================================================================== -.. automodule:: nova..tests.validator_unittest - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.virt_unittest.rst b/doc/source/api/nova..tests.virt_unittest.rst deleted file mode 100644 index 2189be41ef96..000000000000 --- a/doc/source/api/nova..tests.virt_unittest.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.virt_unittest` Module -============================================================================== -.. automodule:: nova..tests.virt_unittest - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..tests.volume_unittest.rst b/doc/source/api/nova..tests.volume_unittest.rst deleted file mode 100644 index 791e192f5339..000000000000 --- a/doc/source/api/nova..tests.volume_unittest.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..tests.volume_unittest` Module -============================================================================== -.. automodule:: nova..tests.volume_unittest - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..twistd.rst b/doc/source/api/nova..twistd.rst deleted file mode 100644 index d4145396db02..000000000000 --- a/doc/source/api/nova..twistd.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..twistd` Module -============================================================================== -.. automodule:: nova..twistd - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..utils.rst b/doc/source/api/nova..utils.rst deleted file mode 100644 index 1131d1080942..000000000000 --- a/doc/source/api/nova..utils.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..utils` Module -============================================================================== -.. automodule:: nova..utils - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..validate.rst b/doc/source/api/nova..validate.rst deleted file mode 100644 index 1d142f103259..000000000000 --- a/doc/source/api/nova..validate.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..validate` Module -============================================================================== -.. automodule:: nova..validate - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..virt.connection.rst b/doc/source/api/nova..virt.connection.rst deleted file mode 100644 index caf76676514e..000000000000 --- a/doc/source/api/nova..virt.connection.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..virt.connection` Module -============================================================================== -.. automodule:: nova..virt.connection - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..virt.fake.rst b/doc/source/api/nova..virt.fake.rst deleted file mode 100644 index 06ecdbf7d688..000000000000 --- a/doc/source/api/nova..virt.fake.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..virt.fake` Module -============================================================================== -.. automodule:: nova..virt.fake - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..virt.images.rst b/doc/source/api/nova..virt.images.rst deleted file mode 100644 index 4fdeb7af8dc7..000000000000 --- a/doc/source/api/nova..virt.images.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..virt.images` Module -============================================================================== -.. automodule:: nova..virt.images - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..virt.libvirt_conn.rst b/doc/source/api/nova..virt.libvirt_conn.rst deleted file mode 100644 index 7fb8aed5fe89..000000000000 --- a/doc/source/api/nova..virt.libvirt_conn.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..virt.libvirt_conn` Module -============================================================================== -.. automodule:: nova..virt.libvirt_conn - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..virt.xenapi.rst b/doc/source/api/nova..virt.xenapi.rst deleted file mode 100644 index 2e396bf06ccf..000000000000 --- a/doc/source/api/nova..virt.xenapi.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..virt.xenapi` Module -============================================================================== -.. automodule:: nova..virt.xenapi - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..volume.driver.rst b/doc/source/api/nova..volume.driver.rst deleted file mode 100644 index 51f5c07295c0..000000000000 --- a/doc/source/api/nova..volume.driver.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..volume.driver` Module -============================================================================== -.. automodule:: nova..volume.driver - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..volume.manager.rst b/doc/source/api/nova..volume.manager.rst deleted file mode 100644 index 91a192a8f5ef..000000000000 --- a/doc/source/api/nova..volume.manager.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..volume.manager` Module -============================================================================== -.. automodule:: nova..volume.manager - :members: - :undoc-members: - :show-inheritance: diff --git a/doc/source/api/nova..wsgi.rst b/doc/source/api/nova..wsgi.rst deleted file mode 100644 index 0bff1c332399..000000000000 --- a/doc/source/api/nova..wsgi.rst +++ /dev/null @@ -1,6 +0,0 @@ -The :mod:`nova..wsgi` Module -============================================================================== -.. automodule:: nova..wsgi - :members: - :undoc-members: - :show-inheritance: From def5583469bd265c9107ed54d461441bc6303151 Mon Sep 17 00:00:00 2001 From: Eric Day Date: Wed, 5 Jan 2011 09:50:19 -0800 Subject: [PATCH 70/86] Split internal API get calls to get and get_all, where the former takes an ID and returns one resource, and the latter can optionally take a filter and return a list of resources. --- nova/api/ec2/cloud.py | 9 +++++---- nova/api/openstack/servers.py | 2 +- nova/compute/api.py | 12 +++++++----- nova/volume/api.py | 13 +++++++------ 4 files changed, 20 insertions(+), 16 deletions(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index b1eaafc8ba2a..0c0027287228 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -119,7 +119,8 @@ class CloudController(object): def _get_mpi_data(self, context, project_id): result = {} - for instance in self.compute_api.get(context, project_id=project_id): + for instance in self.compute_api.get_all(context, + project_id=project_id): if instance['fixed_ip']: line = '%s slots=%d' % (instance['fixed_ip']['address'], instance['vcpus']) @@ -141,7 +142,7 @@ class CloudController(object): def get_metadata(self, address): ctxt = context.get_admin_context() - instance_ref = self.compute_api.get(ctxt, fixed_ip=address) + instance_ref = self.compute_api.get_all(ctxt, fixed_ip=address) if instance_ref is None: return None mpi = self._get_mpi_data(ctxt, instance_ref['project_id']) @@ -493,7 +494,7 @@ class CloudController(object): "output": base64.b64encode(output)} def describe_volumes(self, context, volume_id=None, **kwargs): - volumes = self.volume_api.get(context) + volumes = self.volume_api.get_all(context) # NOTE(vish): volume_id is an optional list of volume ids to filter by. volumes = [self._format_volume(context, v) for v in volumes if volume_id is None or v['id'] in volume_id] @@ -597,7 +598,7 @@ class CloudController(object): def _format_instances(self, context, **kwargs): reservations = {} - instances = self.compute_api.get(context, **kwargs) + instances = self.compute_api.get_all(context, **kwargs) for instance in instances: if not context.user.is_admin(): if instance['image_id'] == FLAGS.vpn_image_id: diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 6be24629a5b8..ce64ac7ad2fb 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -96,7 +96,7 @@ class Controller(wsgi.Controller): entity_maker - either _translate_detail_keys or _translate_keys """ - instance_list = self.compute_api.get(req.environ['nova.context']) + instance_list = self.compute_api.get_all(req.environ['nova.context']) limited_list = common.limited(instance_list, req) res = [entity_maker(inst)['server'] for inst in limited_list] return dict(servers=res) diff --git a/nova/compute/api.py b/nova/compute/api.py index 74d030c4d8ce..64d47b1ce093 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -245,13 +245,15 @@ class API(base.Base): else: self.db.instance_destroy(context, instance_id) - def get(self, context, instance_id=None, project_id=None, - reservation_id=None, fixed_ip=None): - """Get one or more instances, possibly filtered by one of the + def get(self, context, instance_id): + """Get a single instance with the given ID.""" + return self.db.instance_get_by_id(context, instance_id) + + def get_all(self, context, project_id=None, reservation_id=None, + fixed_ip=None): + """Get all instances, possibly filtered by one of the given parameters. If there is no filter and the context is an admin, it will retreive all instances in the system.""" - if instance_id is not None: - return self.db.instance_get_by_id(context, instance_id) if reservation_id is not None: return self.db.instance_get_all_by_reservation(context, reservation_id) diff --git a/nova/volume/api.py b/nova/volume/api.py index 48ecdbe68b17..2d7fe3762fe3 100644 --- a/nova/volume/api.py +++ b/nova/volume/api.py @@ -63,7 +63,7 @@ class API(base.Base): return volume def delete(self, context, volume_id): - volume = self.db.volume_get(context, volume_id) + volume = self.get(context, volume_id) if volume['status'] != "available": raise exception.ApiError(_("Volume status must be available")) now = datetime.datetime.utcnow() @@ -78,15 +78,16 @@ class API(base.Base): def update(self, context, volume_id, fields): self.db.volume_update(context, volume_id, fields) - def get(self, context, volume_id=None): - if volume_id is not None: - return self.db.volume_get(context, volume_id) + def get(self, context, volume_id): + return self.db.volume_get(context, volume_id) + + def get_all(self, context): if context.user.is_admin(): return self.db.volume_get_all(context) return self.db.volume_get_all_by_project(context, context.project_id) def check_attach(self, context, volume_id): - volume = self.db.volume_get(context, volume_id) + volume = self.get(context, volume_id) # TODO(vish): abstract status checking? if volume['status'] != "available": raise exception.ApiError(_("Volume status must be available")) @@ -94,7 +95,7 @@ class API(base.Base): raise exception.ApiError(_("Volume is already attached")) def check_detach(self, context, volume_id): - volume = self.db.volume_get(context, volume_id) + volume = self.get(context, volume_id) # TODO(vish): abstract status checking? if volume['status'] == "available": raise exception.ApiError(_("Volume is already detached")) From 11d5e914044583882384ffd462991ef4f678b28e Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Wed, 5 Jan 2011 16:04:16 -0600 Subject: [PATCH 71/86] Updated register_models() docstring --- nova/db/sqlalchemy/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/nova/db/sqlalchemy/models.py b/nova/db/sqlalchemy/models.py index 0c9c387fc711..6367d1ff8fa9 100644 --- a/nova/db/sqlalchemy/models.py +++ b/nova/db/sqlalchemy/models.py @@ -545,7 +545,8 @@ def register_models(): """Register Models and create metadata. Called from nova.db.sqlalchemy.__init__ as part of loading the driver, - it will never need to be called explicitly elsewhere. + it will never need to be called explicitly elsewhere unless the + connection is lost and needs to be reestablished. """ from sqlalchemy import create_engine models = (Service, Instance, InstanceActions, From b4e57fe01778d7e3f115a369eebaeb9ee328895e Mon Sep 17 00:00:00 2001 From: Chiradeep Vittal Date: Wed, 5 Jan 2011 15:02:09 -0800 Subject: [PATCH 72/86] Make test case work again --- nova/tests/hyperv_unittest.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/nova/tests/hyperv_unittest.py b/nova/tests/hyperv_unittest.py index 7044db43c2f9..3980ae3cb2f9 100644 --- a/nova/tests/hyperv_unittest.py +++ b/nova/tests/hyperv_unittest.py @@ -19,31 +19,36 @@ Tests For Hyper-V driver import random +from nova import context from nova import db from nova import flags from nova import test - +from nova.auth import manager from nova.virt import hyperv FLAGS = flags.FLAGS FLAGS.connection_type = 'hyperv' -class HyperVTestCase(test.TrialTestCase): +class HyperVTestCase(test.TestCase): """Test cases for the Hyper-V driver""" - def setUp(self): # pylint: disable-msg=C0103 - pass + def setUp(self): + super(HyperVTestCase, self).setUp() + self.manager = manager.AuthManager() + self.user = self.manager.create_user('fake', 'fake', 'fake', + admin=True) + self.project = self.manager.create_project('fake', 'fake', 'fake') + self.context = context.RequestContext(self.user, self.project) def test_create_destroy(self): """Create a VM and destroy it""" instance = {'internal_id': random.randint(1, 1000000), 'memory_mb': '1024', 'mac_address': '02:12:34:46:56:67', - 'vcpu': 2, + 'vcpus': 2, 'project_id': 'fake', 'instance_type': 'm1.small'} - - instance_ref = db.instance_create(None, instance) + instance_ref = db.instance_create(self.context, instance) conn = hyperv.get_connection(False) conn._create_vm(instance_ref) # pylint: disable-msg=W0212 @@ -60,5 +65,7 @@ class HyperVTestCase(test.TrialTestCase): if n == instance_ref['name']] self.assertTrue(len(found) == 0) - def tearDown(self): # pylint: disable-msg=C0103 - pass + def tearDown(self): + super(HyperVTestCase, self).tearDown() + self.manager.delete_project(self.project) + self.manager.delete_user(self.user) From 80abf5306c7dcc08e63c9af182b31007b9de677c Mon Sep 17 00:00:00 2001 From: Chiradeep Vittal Date: Wed, 5 Jan 2011 15:04:51 -0800 Subject: [PATCH 73/86] Add to Authors and mailmap --- .mailmap | 1 + Authors | 1 + 2 files changed, 2 insertions(+) diff --git a/.mailmap b/.mailmap index 010678569a24..2af2d7cd9b63 100644 --- a/.mailmap +++ b/.mailmap @@ -30,3 +30,4 @@ + diff --git a/Authors b/Authors index 639e68a59e10..8dfaf955740a 100644 --- a/Authors +++ b/Authors @@ -3,6 +3,7 @@ Anne Gentle Anthony Young Antony Messerli Armando Migliaccio +Chiradeep Vittal Chris Behrens Chmouel Boudjnah Cory Wright From 4a7105898e45cf5b6393f68d8d2d921dd218724b Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 6 Jan 2011 18:35:18 +0000 Subject: [PATCH 74/86] Fix format_run_instances to pass in reservation id as a kwarg --- nova/api/ec2/cloud.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/api/ec2/cloud.py b/nova/api/ec2/cloud.py index 0c0027287228..6619b545292c 100644 --- a/nova/api/ec2/cloud.py +++ b/nova/api/ec2/cloud.py @@ -592,7 +592,7 @@ class CloudController(object): return {'reservationSet': self._format_instances(context)} def _format_run_instances(self, context, reservation_id): - i = self._format_instances(context, reservation_id) + i = self._format_instances(context, reservation_id=reservation_id) assert len(i) == 1 return i[0] From 6c01a842493079fdff9d5887562aec1a6fe8033b Mon Sep 17 00:00:00 2001 From: Vishvananda Ishaya Date: Thu, 6 Jan 2011 18:46:28 +0000 Subject: [PATCH 75/86] fix the broken tests that allowed the breakage in format to happen --- nova/tests/test_cloud.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/nova/tests/test_cloud.py b/nova/tests/test_cloud.py index 42344af1cb06..ba58fab59532 100644 --- a/nova/tests/test_cloud.py +++ b/nova/tests/test_cloud.py @@ -140,15 +140,16 @@ class CloudTestCase(test.TestCase): kwargs = {'image_id': image_id, 'instance_type': instance_type, 'max_count': max_count} - rv = yield self.cloud.run_instances(self.context, **kwargs) + rv = self.cloud.run_instances(self.context, **kwargs) + print rv instance_id = rv['instancesSet'][0]['instanceId'] - output = yield self.cloud.get_console_output(context=self.context, + output = self.cloud.get_console_output(context=self.context, instance_id=[instance_id]) self.assertEquals(b64decode(output['output']), 'FAKE CONSOLE OUTPUT') # TODO(soren): We need this until we can stop polling in the rpc code # for unit tests. greenthread.sleep(0.3) - rv = yield self.cloud.terminate_instances(self.context, [instance_id]) + rv = self.cloud.terminate_instances(self.context, [instance_id]) def test_key_generation(self): result = self._create_key('test') @@ -186,7 +187,7 @@ class CloudTestCase(test.TestCase): kwargs = {'image_id': image_id, 'instance_type': instance_type, 'max_count': max_count} - rv = yield self.cloud.run_instances(self.context, **kwargs) + rv = self.cloud.run_instances(self.context, **kwargs) # TODO: check for proper response instance_id = rv['reservationSet'][0].keys()[0] instance = rv['reservationSet'][0][instance_id][0] @@ -209,7 +210,7 @@ class CloudTestCase(test.TestCase): for instance in reservations[reservations.keys()[0]]: instance_id = instance['instance_id'] logging.debug("Terminating instance %s" % instance_id) - rv = yield self.compute.terminate_instance(instance_id) + rv = self.compute.terminate_instance(instance_id) def test_instance_update_state(self): def instance(num): From 19db369868b2f4a200624cb67d72740eabaab699 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 6 Jan 2011 15:33:01 -0600 Subject: [PATCH 76/86] Include date in action query --- nova/db/sqlalchemy/api.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index aaa07e3c9291..003a96b75d31 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -840,12 +840,15 @@ def instance_action_create(context, values): def instance_get_actions(context, instance_id): """Return the actions associated to the given instance id""" session = get_session() - actions = {} + actions = [] for action in session.query(models.InstanceActions).\ filter_by(instance_id=instance_id).\ all(): - actions[action.action] = action.error - return actions + actions.append(dict( + date=action.created_at.ctime(), + action=action.action, + error=action.error)) + return dict(actions=actions) ################### From 006a3c43093ce3324173e0aed172a3be1396d5dc Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Thu, 6 Jan 2011 16:21:59 -0600 Subject: [PATCH 77/86] altered argument handling --- nova/compute/manager.py | 42 ++++++++++++++--------------------------- 1 file changed, 14 insertions(+), 28 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 2671d401c367..a5343ff221b8 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -62,39 +62,25 @@ def checks_instance_lock(function): """ @functools.wraps(function) - def decorated_function(*args, **kwargs): + def decorated_function(self, context, instance_id, *args, **kwargs): - logging.info(_("check_instance_locks decorating |%s|"), function) - logging.info(_("check_instance_locks: arguments: |%s| |%s|"), args, - kwargs) - # assume worst case (have to declare so they are in scope) - admin = False - locked = True - instance_id = False - - # grab args to function - try: - if 'context' in kwargs: - context = kwargs['context'] - else: - context = args[1] - if 'instance_id' in kwargs: - instance_id = kwargs['instance_id'] - else: - instance_id = args[2] - locked = args[0].get_lock(context, instance_id) - admin = context.is_admin - except Exception as e: - logging.error(_("check_instance_lock fail! args: |%s| |%s|"), args, - kwargs) - raise e + logging.info(_("check_instance_lock: decorating: |%s|"), function) + logging.info(_("check_instance_lock: arguments: |%s| |%s| |%s|"), + self, + context, + instance_id) + unlocked = not self.get_lock(context, instance_id) + admin = context.is_admin + logging.info(_("check_instance_lock: locked: |%s|"), locked) + logging.info(_("check_instance_lock: admin: |%s|"), admin) # if admin or unlocked call function otherwise log error - if admin or not locked: + if admin or unlocked: + logging.info(_("check_instance_lock: executing: |%s|"), function) function(*args, **kwargs) else: - logging.error(_("Instance |%s| is locked, cannot execute |%s|"), - instance_id, function) + logging.error(_("check_instance_lock: not executing |%s|"), + function) return False return decorated_function From ba245da7a339cb769451b67f27cd801c0ce12120 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Thu, 6 Jan 2011 16:30:45 -0600 Subject: [PATCH 78/86] pep8 --- nova/tests/test_compute.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/tests/test_compute.py b/nova/tests/test_compute.py index 993c4fd3cb42..062f37f27c0b 100644 --- a/nova/tests/test_compute.py +++ b/nova/tests/test_compute.py @@ -180,12 +180,12 @@ class ComputeTestCase(test.TestCase): # decorator should return False (fail) with locked nonadmin context self.compute.lock_instance(self.context, instance_id) - ret_val = self.compute.reboot_instance(non_admin_context,instance_id) + ret_val = self.compute.reboot_instance(non_admin_context, instance_id) self.assertEqual(ret_val, False) # decorator should return None (success) with unlocked nonadmin context self.compute.unlock_instance(self.context, instance_id) - ret_val = self.compute.reboot_instance(non_admin_context,instance_id) + ret_val = self.compute.reboot_instance(non_admin_context, instance_id) self.assertEqual(ret_val, None) self.compute.terminate_instance(self.context, instance_id) From 11d6e9d2f917d124946d0fa47c1512a1f8ab940d Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Thu, 6 Jan 2011 17:02:20 -0600 Subject: [PATCH 79/86] accidentally left unlocked in there, it should have been locked --- nova/compute/manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 1ed1eb40aea4..3c20b7c73d0b 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -69,13 +69,13 @@ def checks_instance_lock(function): self, context, instance_id) - unlocked = not self.get_lock(context, instance_id) + locked = self.get_lock(context, instance_id) admin = context.is_admin logging.info(_("check_instance_lock: locked: |%s|"), locked) logging.info(_("check_instance_lock: admin: |%s|"), admin) # if admin or unlocked call function otherwise log error - if admin or unlocked: + if admin or not locked: logging.info(_("check_instance_lock: executing: |%s|"), function) function(*args, **kwargs) else: From 7450f84e0f491f8a24273135432e105677c4a589 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Thu, 6 Jan 2011 17:23:00 -0600 Subject: [PATCH 80/86] passing the correct parameters to decorated function --- nova/compute/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 3c20b7c73d0b..b7ec2e93b1d2 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -77,7 +77,7 @@ def checks_instance_lock(function): # if admin or unlocked call function otherwise log error if admin or not locked: logging.info(_("check_instance_lock: executing: |%s|"), function) - function(*args, **kwargs) + function(self, context, isntance_id, *args, **kwargs) else: logging.error(_("check_instance_lock: not executing |%s|"), function) From ebec92778bdaf4af58029f9977697865c53f881d Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Thu, 6 Jan 2011 17:25:17 -0600 Subject: [PATCH 81/86] refers to instance_id instead of instance_ref[instance_id] --- nova/compute/manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index b7ec2e93b1d2..24c32113060d 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -411,7 +411,7 @@ class ComputeManager(manager.Manager): context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) - logging.debug(_('instance %s: locking'), instance_ref['internal_id']) + logging.debug(_('instance %s: locking'), instance_id) self.db.instance_update(context, instance_id, {'locked': True}) @exception.wrap_exception @@ -423,7 +423,7 @@ class ComputeManager(manager.Manager): context = context.elevated() instance_ref = self.db.instance_get(context, instance_id) - logging.debug(_('instance %s: unlocking'), instance_ref['internal_id']) + logging.debug(_('instance %s: unlocking'), instance_id) self.db.instance_update(context, instance_id, {'locked': False}) @exception.wrap_exception From 76e3923c40dff2f754b045847d8ad19ea9a7cef1 Mon Sep 17 00:00:00 2001 From: Trey Morris Date: Thu, 6 Jan 2011 17:27:57 -0600 Subject: [PATCH 82/86] typo --- nova/compute/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 24c32113060d..d5136eb26088 100644 --- a/nova/compute/manager.py +++ b/nova/compute/manager.py @@ -77,7 +77,7 @@ def checks_instance_lock(function): # if admin or unlocked call function otherwise log error if admin or not locked: logging.info(_("check_instance_lock: executing: |%s|"), function) - function(self, context, isntance_id, *args, **kwargs) + function(self, context, instance_id, *args, **kwargs) else: logging.error(_("check_instance_lock: not executing |%s|"), function) From 3dd9c56477078114bcd9b20a49a3413615539103 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 6 Jan 2011 17:50:44 -0600 Subject: [PATCH 83/86] Review feedback --- nova/api/openstack/servers.py | 3 ++- nova/compute/api.py | 9 ++++++++- nova/db/sqlalchemy/api.py | 7 ++----- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index ce64ac7ad2fb..ca2e5888be2a 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -222,4 +222,5 @@ class Controller(wsgi.Controller): def actions(self, req, id): """Permit Admins to retrieve server actions.""" ctxt = req.environ["nova.context"] - return self.compute_api.get_actions(ctxt, id) + items = self.compute_api.get_actions(ctxt, id) + return dict(actions=items) diff --git a/nova/compute/api.py b/nova/compute/api.py index 64d47b1ce093..46d921204f99 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -316,7 +316,14 @@ class API(base.Base): def get_actions(self, context, instance_id): """Retrieve actions for the given instance.""" - return self.db.instance_get_actions(context, instance_id) + items = self.db.instance_get_actions(context, instance_id) + actions = [] + for item in items: + actions.append(dict( + date=str(item[0]), + action=item[1], + error=item[2])) + return actions def suspend(self, context, instance_id): """suspend the instance with instance_id""" diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 003a96b75d31..0796a7f25676 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -844,11 +844,8 @@ def instance_get_actions(context, instance_id): for action in session.query(models.InstanceActions).\ filter_by(instance_id=instance_id).\ all(): - actions.append(dict( - date=action.created_at.ctime(), - action=action.action, - error=action.error)) - return dict(actions=actions) + actions.append((action.created_at, action.action, action.error)) + return actions ################### From 6a162512cac5eafdbe46ba4df6117bfed6f40e4b Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 6 Jan 2011 18:12:22 -0600 Subject: [PATCH 84/86] Review feedback --- nova/api/openstack/servers.py | 4 ++++ nova/compute/api.py | 9 +-------- nova/db/sqlalchemy/api.py | 5 ++++- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index ca2e5888be2a..9a62f2b96415 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -223,4 +223,8 @@ class Controller(wsgi.Controller): """Permit Admins to retrieve server actions.""" ctxt = req.environ["nova.context"] items = self.compute_api.get_actions(ctxt, id) + # TODO(jk0): Do not do pre-serialization here once the default + # serializer is updated + for item in items: + item["date"] = str(item["date"]) return dict(actions=items) diff --git a/nova/compute/api.py b/nova/compute/api.py index 46d921204f99..64d47b1ce093 100644 --- a/nova/compute/api.py +++ b/nova/compute/api.py @@ -316,14 +316,7 @@ class API(base.Base): def get_actions(self, context, instance_id): """Retrieve actions for the given instance.""" - items = self.db.instance_get_actions(context, instance_id) - actions = [] - for item in items: - actions.append(dict( - date=str(item[0]), - action=item[1], - error=item[2])) - return actions + 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/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 0796a7f25676..797dacb73948 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -844,7 +844,10 @@ def instance_get_actions(context, instance_id): for action in session.query(models.InstanceActions).\ filter_by(instance_id=instance_id).\ all(): - actions.append((action.created_at, action.action, action.error)) + actions.append(dict( + date=action.created_at, + action=action.action, + error=action.error)) return actions From a0edf5a7ba372419ebfed987b8585171e7167e48 Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 6 Jan 2011 18:18:28 -0600 Subject: [PATCH 85/86] Review feedback --- nova/api/openstack/servers.py | 2 +- nova/db/sqlalchemy/api.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 9a62f2b96415..3ee161502fcd 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -226,5 +226,5 @@ class Controller(wsgi.Controller): # TODO(jk0): Do not do pre-serialization here once the default # serializer is updated for item in items: - item["date"] = str(item["date"]) + item["created_at"] = str(item["created_at"]) return dict(actions=items) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 797dacb73948..1b6e89541f22 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -845,7 +845,7 @@ def instance_get_actions(context, instance_id): filter_by(instance_id=instance_id).\ all(): actions.append(dict( - date=action.created_at, + created_at=action.created_at, action=action.action, error=action.error)) return actions From 59f8986df4d78f61528162e65f560064febef7af Mon Sep 17 00:00:00 2001 From: Josh Kearney Date: Thu, 6 Jan 2011 18:59:15 -0600 Subject: [PATCH 86/86] Review feedback --- nova/api/openstack/servers.py | 8 ++++++-- nova/db/sqlalchemy/api.py | 10 ++-------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/nova/api/openstack/servers.py b/nova/api/openstack/servers.py index 3ee161502fcd..9a0c417ae28c 100644 --- a/nova/api/openstack/servers.py +++ b/nova/api/openstack/servers.py @@ -223,8 +223,12 @@ class Controller(wsgi.Controller): """Permit Admins to retrieve server actions.""" ctxt = req.environ["nova.context"] items = self.compute_api.get_actions(ctxt, id) + actions = [] # TODO(jk0): Do not do pre-serialization here once the default # serializer is updated for item in items: - item["created_at"] = str(item["created_at"]) - return dict(actions=items) + actions.append(dict( + created_at=str(item.created_at), + action=item.action, + error=item.error)) + return dict(actions=actions) diff --git a/nova/db/sqlalchemy/api.py b/nova/db/sqlalchemy/api.py index 1b6e89541f22..45427597a268 100644 --- a/nova/db/sqlalchemy/api.py +++ b/nova/db/sqlalchemy/api.py @@ -840,15 +840,9 @@ def instance_action_create(context, values): def instance_get_actions(context, instance_id): """Return the actions associated to the given instance id""" session = get_session() - actions = [] - for action in session.query(models.InstanceActions).\ + return session.query(models.InstanceActions).\ filter_by(instance_id=instance_id).\ - all(): - actions.append(dict( - created_at=action.created_at, - action=action.action, - error=action.error)) - return actions + all() ###################