diff options
| author | Chiradeep Vittal <chiradeep@chiradeep-lt2> | 2010-10-13 23:21:22 -0700 |
|---|---|---|
| committer | Chiradeep Vittal <chiradeep@chiradeep-lt2> | 2010-10-13 23:21:22 -0700 |
| commit | 9caed7b34d9b953bb8ecd306509443d076d1e4fe (patch) | |
| tree | 592a571eefbcc9980886dac33dfb7e7236c256d4 | |
| parent | 273f5c1c5a3f2ae1f540ba2432cc8a2d0a9c1826 (diff) | |
| download | nova-9caed7b34d9b953bb8ecd306509443d076d1e4fe.tar.gz nova-9caed7b34d9b953bb8ecd306509443d076d1e4fe.tar.xz nova-9caed7b34d9b953bb8ecd306509443d076d1e4fe.zip | |
review comments
| -rw-r--r-- | nova/compute/manager.py | 2 | ||||
| -rw-r--r-- | nova/virt/hyperv.py | 248 | ||||
| -rw-r--r-- | nova/virt/images.py | 11 |
3 files changed, 146 insertions, 115 deletions
diff --git a/nova/compute/manager.py b/nova/compute/manager.py index 8d2705da2..131fac406 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 388e833b2..7451cac97 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. - - 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 + 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 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) - + 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] + vm = self._conn.Msvm_ComputerSystem(ElementName=instance.name)[0] - vmsettings = vm.associators(wmi_result_class= - 'Msvm_VirtualSystemSettingData') + 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 a68d856a1..6ef652e4a 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) |
