From f952992f035ec130b7608e9851ef9c3becc2047a Mon Sep 17 00:00:00 2001 From: sateesh Date: Wed, 2 Mar 2011 00:43:29 +0530 Subject: Updated the code to include support for guest consoles, VLAN networking for guest machines on ESX/ESXi servers as compute providers in OpenStack. Removed dependency on ZSI and now using suds-0.4 to generate the required stubs for VMware Virtual Infrastructure API on the fly for calls by vmwareapi module. --- nova/virt/vmwareapi/__init__.py | 9 - nova/virt/vmwareapi/fake.py | 684 +++++++++++++++++++++++++++++++++ nova/virt/vmwareapi/io_util.py | 52 +-- nova/virt/vmwareapi/network_utils.py | 117 ++++++ nova/virt/vmwareapi/read_write_util.py | 110 ++---- nova/virt/vmwareapi/vim.py | 117 +++--- nova/virt/vmwareapi/vim_util.py | 303 +++++++-------- nova/virt/vmwareapi/vm_util.py | 335 ++++++++-------- nova/virt/vmwareapi/vmops.py | 435 ++++++++++----------- nova/virt/vmwareapi/vmware_images.py | 96 +---- nova/virt/vmwareapi_conn.py | 134 +++---- 11 files changed, 1490 insertions(+), 902 deletions(-) create mode 100644 nova/virt/vmwareapi/fake.py create mode 100644 nova/virt/vmwareapi/network_utils.py (limited to 'nova') diff --git a/nova/virt/vmwareapi/__init__.py b/nova/virt/vmwareapi/__init__.py index 0cf70150c..27e212a30 100644 --- a/nova/virt/vmwareapi/__init__.py +++ b/nova/virt/vmwareapi/__init__.py @@ -14,16 +14,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. - """ :mod:`vmwareapi` -- Nova support for VMware ESX/ESXi Server through vSphere API -=============================================================================== """ - -class HelperBase(object): - """The base for helper classes. This adds the VMwareAPI class attribute""" - VMwareAPI = None - - def __init__(self): - return diff --git a/nova/virt/vmwareapi/fake.py b/nova/virt/vmwareapi/fake.py new file mode 100644 index 000000000..43a2f1943 --- /dev/null +++ b/nova/virt/vmwareapi/fake.py @@ -0,0 +1,684 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2011 Citrix Systems, Inc. +# Copyright 2011 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 +# 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 fake VMWare VI API implementation. +""" + +from pprint import pformat +import uuid + +from nova import exception +from nova import log as logging +from nova.virt.vmwareapi import vim +from nova.virt.vmwareapi.vim import SessionFaultyException + +_CLASSES = ['Datacenter', 'Datastore', 'ResourcePool', 'VirtualMachine', + 'Network', 'HostSystem', 'Task', 'session', 'files'] + +_FAKE_FILE_SIZE = 1024 + +_db_content = {} + +LOG = logging.getLogger("nova.virt.vmwareapi.fake") + + +def log_db_contents(msg=None): + """ Log DB Contents""" + text = msg or "" + content = pformat(_db_content) + LOG.debug(_("%(text)s: _db_content => %(content)s") % locals()) + + +def reset(): + """ Resets the db contents """ + for c in _CLASSES: + #We fake the datastore by keeping the file references as a list of + #names in the db + if c == 'files': + _db_content[c] = [] + else: + _db_content[c] = {} + create_network() + create_host() + create_datacenter() + create_datastore() + create_res_pool() + + +def cleanup(): + """ Clear the db contents """ + for c in _CLASSES: + _db_content[c] = {} + + +def _create_object(table, obj): + """ Create an object in the db """ + _db_content[table][obj.obj] = obj + + +def _get_objects(type): + """ Get objects of the type """ + lst_objs = [] + for key in _db_content[type]: + lst_objs.append(_db_content[type][key]) + return lst_objs + + +class Prop(object): + """ Property Object base class """ + + def __init__(self): + self.name = None + self.val = None + + def setVal(self, val): + self.val = val + + def setName(self, name): + self.name = name + + +class ManagedObject(object): + """ Managed Data Object base class """ + + def __init__(self, name="ManagedObject", obj_ref=None): + """ Sets the obj property which acts as a reference to the object""" + object.__setattr__(self, 'objName', name) + if obj_ref is None: + obj_ref = str(uuid.uuid4()) + object.__setattr__(self, 'obj', obj_ref) + object.__setattr__(self, 'propSet', []) + + def set(self, attr, val): + """ Sets an attribute value. Not using the __setattr__ directly for we + want to set attributes of the type 'a.b.c' and using this function + class we set the same """ + self.__setattr__(attr, val) + + def get(self, attr): + """ Gets an attribute. Used as an intermediary to get nested + property like 'a.b.c' value """ + return self.__getattr__(attr) + + def __setattr__(self, attr, val): + for prop in self.propSet: + if prop.name == attr: + prop.val = val + return + elem = Prop() + elem.setName(attr) + elem.setVal(val) + self.propSet.append(elem) + + def __getattr__(self, attr): + for elem in self.propSet: + if elem.name == attr: + return elem.val + raise Exception(_("Property %(attr)s not set for the managed " + "object %(objName)s") % + {'attr': attr, + 'objName': self.objName}) + + +class DataObject(object): + """ Data object base class """ + + def __init__(self): + pass + + def __getattr__(self, attr): + return object.__getattribute__(self, attr) + + def __setattr__(self, attr, value): + object.__setattr__(self, attr, value) + + +class VirtualDisk(DataObject): + """ Virtual Disk class. Does nothing special except setting + __class__.__name__ to 'VirtualDisk'. Refer place where __class__.__name__ + is used in the code """ + + def __init__(self): + DataObject.__init__(self) + + +class VirtualDiskFlatVer2BackingInfo(DataObject): + """ VirtualDiskFlatVer2BackingInfo class """ + + def __init__(self): + DataObject.__init__(self) + + +class VirtualLsiLogicController(DataObject): + """ VirtualLsiLogicController class """ + + def __init__(self): + DataObject.__init__(self) + + +class VirtualMachine(ManagedObject): + """ Virtual Machine class """ + + def __init__(self, **kwargs): + ManagedObject.__init__(self, "VirtualMachine") + self.set("name", kwargs.get("name")) + self.set("runtime.connectionState", + kwargs.get("conn_state", "connected")) + self.set("summary.config.guestId", kwargs.get("guest", "otherGuest")) + ds_do = DataObject() + ds_do.ManagedObjectReference = [kwargs.get("ds").obj] + self.set("datastore", ds_do) + self.set("summary.guest.toolsStatus", kwargs.get("toolsstatus", + "toolsOk")) + self.set("runtime.powerState", kwargs.get("powerstate", "poweredOn")) + self.set("config.files.vmPathName", kwargs.get("vmPathName")) + self.set("summary.config.numCpu", kwargs.get("numCpu", 1)) + self.set("summary.config.memorySizeMB", kwargs.get("mem", 1)) + self.set("config.hardware.device", kwargs.get("virtual_disk", None)) + self.set("config.extraConfig", kwargs.get("extra_config", None)) + + def reconfig(self, factory, val): + """ Called to reconfigure the VM. Actually customizes the property + setting of the Virtual Machine object """ + try: + #Case of Reconfig of VM to attach disk + controller_key = val.deviceChange[1].device.controllerKey + filename = val.deviceChange[1].device.backing.fileName + + disk = VirtualDisk() + disk.controllerKey = controller_key + + disk_backing = VirtualDiskFlatVer2BackingInfo() + disk_backing.fileName = filename + disk_backing.key = -101 + disk.backing = disk_backing + + controller = VirtualLsiLogicController() + controller.key = controller_key + + self.set("config.hardware.device", [disk, controller]) + except Exception: + #Case of Reconfig of VM to set extra params + self.set("config.extraConfig", val.extraConfig) + + +class Network(ManagedObject): + """ Network class """ + + def __init__(self): + ManagedObject.__init__(self, "Network") + self.set("summary.name", "vmnet0") + + +class ResourcePool(ManagedObject): + """ Resource Pool class """ + + def __init__(self): + ManagedObject.__init__(self, "ResourcePool") + self.set("name", "ResPool") + + +class Datastore(ManagedObject): + """ Datastore class """ + + def __init__(self): + ManagedObject.__init__(self, "Datastore") + self.set("summary.type", "VMFS") + self.set("summary.name", "fake-ds") + + +class HostSystem(ManagedObject): + """ Host System class """ + + def __init__(self): + ManagedObject.__init__(self, "HostSystem") + self.set("name", "HostSystem") + self.set("configManager.networkSystem", "NetworkSystem") + + vswitch_do = DataObject() + vswitch_do.pnic = ["vmnic0"] + vswitch_do.name = "vSwitch0" + vswitch_do.portgroup = ["PortGroup-vmnet0"] + + net_swicth = DataObject() + net_swicth.HostVirtualSwitch = [vswitch_do] + self.set("config.network.vswitch", net_swicth) + + host_pg_do = DataObject() + host_pg_do.key = "PortGroup-vmnet0" + + pg_spec = DataObject() + pg_spec.vlanId = 0 + pg_spec.name = "vmnet0" + + host_pg_do.spec = pg_spec + + host_pg = DataObject() + host_pg.HostPortGroup = [host_pg_do] + self.set("config.network.portgroup", host_pg) + + def _add_port_group(self, spec): + """ Adds a port group to the host system object in the db """ + pg_name = spec.name + vswitch_name = spec.vswitchName + vlanid = spec.vlanId + + vswitch_do = DataObject() + vswitch_do.pnic = ["vmnic0"] + vswitch_do.name = vswitch_name + vswitch_do.portgroup = ["PortGroup-%s" % pg_name] + + vswitches = self.get("config.network.vswitch").HostVirtualSwitch + vswitches.append(vswitch_do) + + host_pg_do = DataObject() + host_pg_do.key = "PortGroup-%s" % pg_name + + pg_spec = DataObject() + pg_spec.vlanId = vlanid + pg_spec.name = pg_name + + host_pg_do.spec = pg_spec + host_pgrps = self.get("config.network.portgroup").HostPortGroup + host_pgrps.append(host_pg_do) + + +class Datacenter(ManagedObject): + """ Datacenter class """ + + def __init__(self): + ManagedObject.__init__(self, "Datacenter") + self.set("name", "ha-datacenter") + self.set("vmFolder", "vm_folder_ref") + if _db_content.get("Network", None) is None: + create_network() + net_ref = _db_content["Network"][_db_content["Network"].keys()[0]].obj + network_do = DataObject() + network_do.ManagedObjectReference = [net_ref] + self.set("network", network_do) + + +class Task(ManagedObject): + """ Task class """ + + def __init__(self, task_name, state="running"): + ManagedObject.__init__(self, "Task") + info = DataObject + info.name = task_name + info.state = state + self.set("info", info) + + +def create_host(): + host_system = HostSystem() + _create_object('HostSystem', host_system) + + +def create_datacenter(): + data_center = Datacenter() + _create_object('Datacenter', data_center) + + +def create_datastore(): + data_store = Datastore() + _create_object('Datastore', data_store) + + +def create_res_pool(): + res_pool = ResourcePool() + _create_object('ResourcePool', res_pool) + + +def create_network(): + network = Network() + _create_object('Network', network) + + +def create_task(task_name, state="running"): + task = Task(task_name, state) + _create_object("Task", task) + return task + + +def _add_file(file_path): + """ Adds a file reference to the db """ + _db_content["files"].append(file_path) + + +def _remove_file(file_path): + """ Removes a file reference from the db """ + if _db_content.get("files", None) is None: + raise Exception(_("No files have been added yet")) + #Check if the remove is for a single file object or for a folder + if file_path.find(".vmdk") != -1: + if file_path not in _db_content.get("files"): + raise Exception(_("File- '%s' is not there in the datastore") %\ + file_path) + _db_content.get("files").remove(file_path) + else: + #Removes the files in the folder and the folder too from the db + for file in _db_content.get("files"): + if file.find(file_path) != -1: + try: + _db_content.get("files").remove(file) + except Exception: + pass + + +def fake_fetch_image(image, instance, **kwargs): + """Fakes fetch image call. Just adds a reference to the db for the file """ + ds_name = kwargs.get("datastore_name") + file_path = kwargs.get("file_path") + ds_file_path = "[" + ds_name + "] " + file_path + _add_file(ds_file_path) + + +def fake_upload_image(image, instance, **kwargs): + """Fakes the upload of an image """ + pass + + +def fake_get_vmdk_size_and_properties(image_id, instance): + """ Fakes the file size and properties fetch for the image file """ + props = {"vmware_ostype": "otherGuest", + "vmware_adaptertype": "lsiLogic"} + return _FAKE_FILE_SIZE, props + + +def _get_vm_mdo(vm_ref): + """ Gets the Virtual Machine with the ref from the db """ + if _db_content.get("VirtualMachine", None) is None: + raise Exception(_("There is no VM registered")) + if vm_ref not in _db_content.get("VirtualMachine"): + raise Exception(_("Virtual Machine with ref %s is not there") %\ + vm_ref) + return _db_content.get("VirtualMachine")[vm_ref] + + +class FakeFactory(object): + """ Fake factory class for the suds client """ + + def __init__(self): + pass + + def create(self, obj_name): + """ Creates a namespace object """ + return DataObject() + + +class FakeVim(object): + """Fake VIM Class""" + + def __init__(self, protocol="https", host="localhost", trace=None): + """ Initializes the suds client object, sets the service content + contents and the cookies for the session """ + self._session = None + self.client = DataObject() + self.client.factory = FakeFactory() + + transport = DataObject() + transport.cookiejar = "Fake-CookieJar" + options = DataObject() + options.transport = transport + + self.client.options = options + + service_content = self.client.factory.create('ns0:ServiceContent') + service_content.propertyCollector = "PropCollector" + service_content.virtualDiskManager = "VirtualDiskManager" + service_content.fileManager = "FileManager" + service_content.rootFolder = "RootFolder" + service_content.sessionManager = "SessionManager" + self._service_content = service_content + + def get_service_content(self): + return self._service_content + + def __repr__(self): + return "Fake VIM Object" + + def __str__(self): + return "Fake VIM Object" + + def _login(self): + """ Logs in and sets the session object in the db """ + self._session = str(uuid.uuid4()) + session = DataObject() + session.key = self._session + _db_content['session'][self._session] = session + return session + + def _logout(self): + """ Logs out and remove the session object ref from the db """ + s = self._session + self._session = None + if s not in _db_content['session']: + raise exception.Error( + _("Logging out a session that is invalid or already logged " + "out: %s") % s) + del _db_content['session'][s] + + def _terminate(self, *args, **kwargs): + """ Terminates a session """ + s = kwargs.get("sessionId")[0] + if s not in _db_content['session']: + return + del _db_content['session'][s] + + def _check_session(self): + """ Checks if the session is active """ + if (self._session is None or self._session not in + _db_content['session']): + LOG.debug(_("Session is faulty")) + raise SessionFaultyException(_("Session Invalid")) + + def _create_vm(self, method, *args, **kwargs): + """ Creates and registers a VM object with the Host System """ + config_spec = kwargs.get("config") + ds = _db_content["Datastore"][_db_content["Datastore"].keys()[0]] + vm_dict = {"name": config_spec.name, + "ds": ds, + "powerstate": "poweredOff", + "vmPathName": config_spec.files.vmPathName, + "numCpu": config_spec.numCPUs, + "mem": config_spec.memoryMB} + virtual_machine = VirtualMachine(**vm_dict) + _create_object("VirtualMachine", virtual_machine) + task_mdo = create_task(method, "success") + return task_mdo.obj + + def _reconfig_vm(self, method, *args, **kwargs): + """ Reconfigures a VM and sets the properties supplied """ + vm_ref = args[0] + vm_mdo = _get_vm_mdo(vm_ref) + vm_mdo.reconfig(self.client.factory, kwargs.get("spec")) + task_mdo = create_task(method, "success") + return task_mdo.obj + + def _create_copy_disk(self, method, vmdk_file_path): + """ Creates/copies a vmdk file object in the datastore """ + # We need to add/create both .vmdk and .-flat.vmdk files + flat_vmdk_file_path = \ + vmdk_file_path.replace(".vmdk", "-flat.vmdk") + _add_file(vmdk_file_path) + _add_file(flat_vmdk_file_path) + task_mdo = create_task(method, "success") + return task_mdo.obj + + def _snapshot_vm(self, method): + """ Snapshots a VM. Here we do nothing for faking sake """ + task_mdo = create_task(method, "success") + return task_mdo.obj + + def _delete_disk(self, method, *args, **kwargs): + """ Deletes .vmdk and -flat.vmdk files corresponding to the VM """ + vmdk_file_path = kwargs.get("name") + flat_vmdk_file_path = \ + vmdk_file_path.replace(".vmdk", "-flat.vmdk") + _remove_file(vmdk_file_path) + _remove_file(flat_vmdk_file_path) + task_mdo = create_task(method, "success") + return task_mdo.obj + + def _delete_file(self, method, *args, **kwargs): + """ Deletes a file from the datastore """ + _remove_file(kwargs.get("name")) + task_mdo = create_task(method, "success") + return task_mdo.obj + + def _just_return(self): + """ Fakes a return """ + return + + def _unregister_vm(self, method, *args, **kwargs): + """ Unregisters a VM from the Host System """ + vm_ref = args[0] + _get_vm_mdo(vm_ref) + del _db_content["VirtualMachine"][vm_ref] + + def _search_ds(self, method, *args, **kwargs): + """ Searches the datastore for a file """ + ds_path = kwargs.get("datastorePath") + if _db_content.get("files", None) is None: + raise Exception(_("No files have been added yet")) + for file in _db_content.get("files"): + if file.find(ds_path) != -1: + task_mdo = create_task(method, "success") + return task_mdo.obj + task_mdo = create_task(method, "error") + return task_mdo.obj + + def _make_dir(self, method, *args, **kwargs): + """ Creates a directory in the datastore """ + ds_path = kwargs.get("name") + if _db_content.get("files", None) is None: + raise Exception(_("No files have been added yet")) + _db_content["files"].append(ds_path) + + def _set_power_state(self, method, vm_ref, pwr_state="poweredOn"): + """ Sets power state for the VM """ + if _db_content.get("VirtualMachine", None) is None: + raise Exception(_(" No Virtual Machine has been registered yet")) + if vm_ref not in _db_content.get("VirtualMachine"): + raise Exception(_("Virtual Machine with ref %s is not there") %\ + vm_ref) + vm_mdo = _db_content.get("VirtualMachine").get(vm_ref) + vm_mdo.set("runtime.powerState", pwr_state) + task_mdo = create_task(method, "success") + return task_mdo.obj + + def _retrieve_properties(self, method, *args, **kwargs): + """ Retrieves properties based on the type """ + spec_set = kwargs.get("specSet")[0] + type = spec_set.propSet[0].type + properties = spec_set.propSet[0].pathSet + objs = spec_set.objectSet + lst_ret_objs = [] + for obj in objs: + try: + obj_ref = obj.obj + #This means that we are doing a search for the managed + #dataobects of the type in the inventory + if obj_ref == "RootFolder": + for mdo_ref in _db_content[type]: + mdo = _db_content[type][mdo_ref] + #Create a temp Managed object which has the same ref + #as the parent object and copies just the properties + #asked for. We need .obj along with the propSet of + #just the properties asked for + temp_mdo = ManagedObject(mdo.objName, mdo.obj) + for prop in properties: + temp_mdo.set(prop, mdo.get(prop)) + lst_ret_objs.append(temp_mdo) + else: + if obj_ref in _db_content[type]: + mdo = _db_content[type][obj_ref] + temp_mdo = ManagedObject(mdo.objName, obj_ref) + for prop in properties: + temp_mdo.set(prop, mdo.get(prop)) + lst_ret_objs.append(temp_mdo) + except Exception: + continue + return lst_ret_objs + + def _add_port_group(self, method, *args, **kwargs): + """ Adds a port group to the host system """ + host_mdo = \ + _db_content["HostSystem"][_db_content["HostSystem"].keys()[0]] + host_mdo._add_port_group(kwargs.get("portgrp")) + + def __getattr__(self, attr_name): + if attr_name != "Login": + self._check_session() + if attr_name == "Login": + return lambda *args, **kwargs: self._login() + elif attr_name == "Logout": + self._logout() + elif attr_name == "Terminate": + return lambda *args, **kwargs: self._terminate(*args, **kwargs) + elif attr_name == "CreateVM_Task": + return lambda *args, **kwargs: self._create_vm(attr_name, + *args, **kwargs) + elif attr_name == "ReconfigVM_Task": + return lambda *args, **kwargs: self._reconfig_vm(attr_name, + *args, **kwargs) + elif attr_name == "CreateVirtualDisk_Task": + return lambda *args, **kwargs: self._create_copy_disk(attr_name, + kwargs.get("name")) + elif attr_name == "DeleteDatastoreFile_Task": + return lambda *args, **kwargs: self._delete_file(attr_name, + *args, **kwargs) + elif attr_name == "PowerOnVM_Task": + return lambda *args, **kwargs: self._set_power_state(attr_name, + args[0], "poweredOn") + elif attr_name == "PowerOffVM_Task": + return lambda *args, **kwargs: self._set_power_state(attr_name, + args[0], "poweredOff") + elif attr_name == "RebootGuest": + return lambda *args, **kwargs: self._just_return() + elif attr_name == "ResetVM_Task": + return lambda *args, **kwargs: self._set_power_state(attr_name, + args[0], "poweredOn") + elif attr_name == "SuspendVM_Task": + return lambda *args, **kwargs: self._set_power_state(attr_name, + args[0], "suspended") + elif attr_name == "CreateSnapshot_Task": + return lambda *args, **kwargs: self._snapshot_vm(attr_name) + elif attr_name == "CopyVirtualDisk_Task": + return lambda *args, **kwargs: self._create_copy_disk(attr_name, + kwargs.get("destName")) + elif attr_name == "DeleteVirtualDisk_Task": + return lambda *args, **kwargs: self._delete_disk(attr_name, + *args, **kwargs) + elif attr_name == "UnregisterVM": + return lambda *args, **kwargs: self._unregister_vm(attr_name, + *args, **kwargs) + elif attr_name == "SearchDatastore_Task": + return lambda *args, **kwargs: self._search_ds(attr_name, + *args, **kwargs) + elif attr_name == "MakeDirectory": + return lambda *args, **kwargs: self._make_dir(attr_name, + *args, **kwargs) + elif attr_name == "RetrieveProperties": + return lambda *args, **kwargs: self._retrieve_properties( + attr_name, *args, **kwargs) + elif attr_name == "AcquireCloneTicket": + return lambda *args, **kwargs: self._just_return() + elif attr_name == "AddPortGroup": + return lambda *args, **kwargs: self._add_port_group(attr_name, + *args, **kwargs) diff --git a/nova/virt/vmwareapi/io_util.py b/nova/virt/vmwareapi/io_util.py index b9645e220..edec3eb34 100644 --- a/nova/virt/vmwareapi/io_util.py +++ b/nova/virt/vmwareapi/io_util.py @@ -16,13 +16,8 @@ # under the License. """ -Helper classes for multi-threaded I/O (read/write) in the basis of chunks. - -The class ThreadSafePipe queues the chunk data. - -The class IOThread reads chunks from input file and pipes it to output file -till it reaches the transferable size - +Reads a chunk from input file and writes the same to the output file till +it reaches the transferable size """ from Queue import Empty @@ -32,27 +27,26 @@ from threading import Thread import time import traceback -THREAD_SLEEP_TIME = .01 +THREAD_SLEEP_TIME = 0.01 class ThreadSafePipe(Queue): - """ThreadSafePipe class queue's the chunk data.""" + """ThreadSafePipe class queues the chunk data""" def __init__(self, max_size=None): - """Initializes the queue""" Queue.__init__(self, max_size) self.eof = False def write(self, data): - """Writes the chunk data to the queue.""" + """Writes the chunk data to the queue""" self.put(data, block=False) def read(self): - """Retrieves the chunk data from the queue.""" + """Retrieves the chunk data from the queue""" return self.get(block=False) def set_eof(self, eof): - """Sets EOF to mark reading of input file finishes.""" + """Sets EOF to mark reading of input file finishes""" self.eof = eof def get_eof(self): @@ -61,16 +55,11 @@ class ThreadSafePipe(Queue): class IOThread(Thread): - """ - IOThread reads chunks from input file and pipes it to output file till it - reaches the transferable size + """IOThread reads chunks from input file and pipes it to output file till + it reaches the transferable size """ def __init__(self, input_file, output_file, chunk_size, transfer_size): - """ - Initialize the thread. - inputFile and outputFile should be file like objects. - """ Thread.__init__(self) self.input_file = input_file self.output_file = output_file @@ -83,9 +72,8 @@ class IOThread(Thread): self._exception = None def run(self): - """ - Pipes the input chunk read to the output file till it reaches - transferable size + """Pipes the input chunk read to the output file till it reaches + a transferable size """ try: if self.transfer_size and self.transfer_size <= self.chunk_size: @@ -131,10 +119,10 @@ class IOThread(Thread): if not self.transfer_size is None: if self.read_size < self.transfer_size: - raise IOError(_("Not enough data (%(read_size)d of " - "%(transfer_size)d bytes)") % - {'read_size': self.read_size, - 'transfer_size': self.transfer_size}) + raise IOError(_("Not enough data (%(read_size)d " + "of %(transfer_size)d bytes)") \ + % ({'read_size': self.read_size, + 'transfer_size': self.transfer_size})) except Exception: self._error = True @@ -142,18 +130,20 @@ class IOThread(Thread): self._done = True def stop_io_transfer(self): - """Set stop flag to true, which causes the thread to stop safely.""" + """Set the stop flag to true, which causes the thread to stop + safely + """ self._stop_transfer = True self.join() def get_error(self): - """Returns the error string.""" + """Returns the error string""" return self._error def get_exception(self): - """Returns the traceback exception string.""" + """Returns the traceback exception string""" return self._exception def is_done(self): - """Checks whether transfer is complete.""" + """Checks whether transfer is complete""" return self._done diff --git a/nova/virt/vmwareapi/network_utils.py b/nova/virt/vmwareapi/network_utils.py new file mode 100644 index 000000000..6927b15ca --- /dev/null +++ b/nova/virt/vmwareapi/network_utils.py @@ -0,0 +1,117 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright (c) 2011 Citrix Systems, Inc. +# Copyright 2011 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 +# 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. + +""" +Utility functions for ESX Networking +""" + +from nova import log as logging +from nova.virt.vmwareapi import vim_util +from nova.virt.vmwareapi import vm_util +from nova.virt.vmwareapi.vim import VimException + +LOG = logging.getLogger("nova.virt.vmwareapi.network_utils") + +PORT_GROUP_EXISTS_EXCEPTION = \ + 'The specified key, name, or identifier already exists.' + + +class NetworkHelper: + + @classmethod + def get_network_with_the_name(cls, session, network_name="vmnet0"): + """ Gets reference to the network whose name is passed as the + argument. """ + datacenters = session._call_method(vim_util, "get_objects", + "Datacenter", ["network"]) + vm_networks = datacenters[0].propSet[0].val.ManagedObjectReference + networks = session._call_method(vim_util, + "get_properites_for_a_collection_of_objects", + "Network", vm_networks, ["summary.name"]) + for network in networks: + if network.propSet[0].val == network_name: + return network.obj + return None + + @classmethod + def get_vswitches_for_vlan_interface(cls, session, vlan_interface): + """ Gets the list of vswitches associated with the physical + network adapter with the name supplied""" + #Get the list of vSwicthes on the Host System + host_mor = session._call_method(vim_util, "get_objects", + "HostSystem")[0].obj + vswitches = session._call_method(vim_util, + "get_dynamic_property", host_mor, + "HostSystem", "config.network.vswitch").HostVirtualSwitch + vswicthes_conn_to_physical_nic = [] + #For each vSwitch check if it is associated with the network adapter + for elem in vswitches: + try: + for nic_elem in elem.pnic: + if str(nic_elem).split('-')[-1].find(vlan_interface) != -1: + vswicthes_conn_to_physical_nic.append(elem.name) + except Exception: + pass + return vswicthes_conn_to_physical_nic + + @classmethod + def check_if_vlan_id_is_proper(cls, session, pg_name, vlan_id): + """ Check if the vlan id associated with the port group matches the + vlan tag supplied """ + host_mor = session._call_method(vim_util, "get_objects", + "HostSystem")[0].obj + port_grps_on_host = session._call_method(vim_util, + "get_dynamic_property", host_mor, + "HostSystem", "config.network.portgroup").HostPortGroup + for p_gp in port_grps_on_host: + if p_gp.spec.name == pg_name: + if p_gp.spec.vlanId == vlan_id: + return True, vlan_id + else: + return False, p_gp.spec.vlanId + + @classmethod + def create_port_group(cls, session, pg_name, vswitch_name, vlan_id=0): + """ Creates a port group on the host system with the vlan tags + supplied. VLAN id 0 means no vlan id association """ + client_factory = session._get_vim().client.factory + add_prt_grp_spec = vm_util.get_add_vswitch_port_group_spec( + client_factory, + vswitch_name, + pg_name, + vlan_id) + host_mor = session._call_method(vim_util, "get_objects", + "HostSystem")[0].obj + network_system_mor = session._call_method(vim_util, + "get_dynamic_property", host_mor, + "HostSystem", "configManager.networkSystem") + LOG.debug(_("Creating Port Group with name %s on " + "the ESX host") % pg_name) + try: + session._call_method(session._get_vim(), + "AddPortGroup", network_system_mor, + portgrp=add_prt_grp_spec) + except VimException, exc: + #There can be a race condition when two instances try + #adding port groups at the same time. One succeeds, then + #the other one will get an exception. Since we are + #concerned with the port group being created, which is done + #by the other call, we can ignore the exception. + if str(exc).find(PORT_GROUP_EXISTS_EXCEPTION) == -1: + raise Exception(exc) + LOG.debug(_("Created Port Group with name %s on " + "the ESX host") % pg_name) diff --git a/nova/virt/vmwareapi/read_write_util.py b/nova/virt/vmwareapi/read_write_util.py index 45214be04..37f80c133 100644 --- a/nova/virt/vmwareapi/read_write_util.py +++ b/nova/virt/vmwareapi/read_write_util.py @@ -20,20 +20,15 @@ Collection of classes to handle image upload/download to/from Image service (like Glance image storage and retrieval service) from/to ESX/ESXi server. -Also a class is available that acts as Fake image service. It uses local -file system for storage. - """ import httplib -import json -import logging -import os import urllib import urllib2 import urlparse from nova import flags +from nova import log as logging from nova import utils from nova.auth.manager import AuthManager @@ -47,10 +42,9 @@ LOG = logging.getLogger("nova.virt.vmwareapi.read_write_util") class ImageServiceFile: - """The base image service Class""" + """The base image service class""" def __init__(self, file_handle): - """Initialize the file handle.""" self.eof = False self.file_handle = file_handle @@ -67,15 +61,15 @@ class ImageServiceFile: raise NotImplementedError def set_eof(self, eof): - """Set the end of file marker.""" + """Set the end of file marker""" self.eof = eof def get_eof(self): - """Check if the file end has been reached or not.""" + """Check if the file end has been reached or not""" return self.eof def close(self): - """Close the file handle.""" + """Close the file handle""" try: self.file_handle.close() except Exception: @@ -86,16 +80,15 @@ class ImageServiceFile: raise NotImplementedError def __del__(self): - """Destructor. Close the file handle if the same has been forgotten.""" + """Close the file handle on garbage collection""" self.close() class GlanceHTTPWriteFile(ImageServiceFile): - """Glance file write handler Class""" + """Glance file write handler class""" def __init__(self, host, port, image_id, file_size, os_type, adapter_type, version=1, scheme="http"): - """Initialize with the glance host specifics.""" base_url = "%s://%s:%s/images/%s" % (scheme, host, port, image_id) (scheme, netloc, path, params, query, fragment) = \ urlparse.urlparse(base_url) @@ -126,10 +119,9 @@ class GlanceHTTPWriteFile(ImageServiceFile): class GlanceHTTPReadFile(ImageServiceFile): - """Glance file read handler Class""" + """Glance file read handler class""" def __init__(self, host, port, image_id, scheme="http"): - """Initialize with the glance host specifics.""" base_url = "%s://%s:%s/images/%s" % (scheme, host, port, urllib.pathname2url(image_id)) headers = {'User-Agent': USER_AGENT} @@ -138,15 +130,17 @@ class GlanceHTTPReadFile(ImageServiceFile): ImageServiceFile.__init__(self, conn) def read(self, chunk_size=READ_CHUNKSIZE): - """Read a chunk of data.""" + """Read a chunk of data""" return self.file_handle.read(chunk_size) def get_size(self): - """Get the size of the file to be read.""" + """Get the size of the file to be read""" return self.file_handle.headers.get("X-Image-Meta-Size", -1) def get_image_properties(self): - """Get the image properties like say OS Type and the Adapter Type""" + """Get the image properties like say OS Type and the + Adapter Type + """ return {"vmware_ostype": self.file_handle.headers.get( "X-Image-Meta-Property-Vmware_ostype"), @@ -158,95 +152,58 @@ class GlanceHTTPReadFile(ImageServiceFile): "X-Image-Meta-Property-Vmware_image_version")} -class FakeFileRead(ImageServiceFile): - """Local file read handler class""" - - def __init__(self, path): - """Initialize the file path""" - self.path = path - file_handle = open(path, "rb") - ImageServiceFile.__init__(self, file_handle) - - def get_size(self): - """Get size of the file to be read""" - return os.path.getsize(os.path.abspath(self.path)) - - def read(self, chunk_size=READ_CHUNKSIZE): - """Read a chunk of data""" - return self.file_handle.read(chunk_size) - - def get_image_properties(self): - """Get the image properties like say OS Type and the Adapter Type""" - return {"vmware_ostype": "otherGuest", - "vmware_adaptertype": "lsiLogic", - "vmware_image_version": "1"} - - -class FakeFileWrite(ImageServiceFile): - """Local file write handler Class""" - - def __init__(self, path): - """Initialize the file path.""" - file_handle = open(path, "wb") - ImageServiceFile.__init__(self, file_handle) - - def write(self, data): - """Write data to the file.""" - self.file_handle.write(data) - - class VMwareHTTPFile(object): - """Base Class for HTTP file.""" + """Base class for HTTP file""" def __init__(self, file_handle): - """Intialize the file handle.""" self.eof = False self.file_handle = file_handle def set_eof(self, eof): - """Set the end of file marker.""" + """Set the end of file marker""" self.eof = eof def get_eof(self): - """Check if the end of file has been reached.""" + """Check if the end of file has been reached""" return self.eof def close(self): - """Close the file handle.""" + """Close the file handle""" try: self.file_handle.close() except Exception: pass def __del__(self): - """Destructor. Close the file handle if the same has been forgotten.""" + """Close the file handle on garbage collection""" self.close() def _build_vim_cookie_headers(self, vim_cookies): - """Build ESX host session cookie headers.""" - cookie = str(vim_cookies).split(":")[1].strip() - cookie = cookie[:cookie.find(';')] - return cookie + """Build ESX host session cookie headers""" + cookie_header = "" + for vim_cookie in vim_cookies: + cookie_header = vim_cookie.name + "=" + vim_cookie.value + break + return cookie_header def write(self, data): - """Write data to the file.""" + """Write data to the file""" raise NotImplementedError def read(self, chunk_size=READ_CHUNKSIZE): - """Read a chunk of data.""" + """Read a chunk of data""" raise NotImplementedError def get_size(self): - """Get size of the file to be read.""" + """Get size of the file to be read""" raise NotImplementedError class VMWareHTTPWriteFile(VMwareHTTPFile): - """VMWare file write handler Class""" + """VMWare file write handler class""" def __init__(self, host, data_center_name, datastore_name, cookies, file_path, file_size, scheme="https"): - """Initialize the file specifics.""" base_url = "%s://%s/folder/%s" % (scheme, host, file_path) param_list = {"dcPath": data_center_name, "dsName": datastore_name} base_url = base_url + "?" + urllib.urlencode(param_list) @@ -265,7 +222,7 @@ class VMWareHTTPWriteFile(VMwareHTTPFile): VMwareHTTPFile.__init__(self, conn) def write(self, data): - """Write to the file.""" + """Write to the file""" self.file_handle.send(data) def close(self): @@ -273,17 +230,16 @@ class VMWareHTTPWriteFile(VMwareHTTPFile): try: self.conn.getresponse() except Exception, excep: - LOG.debug(_("Exception during close of connection in " + LOG.debug(_("Exception during HTTP connection close in " "VMWareHTTpWrite. Exception is %s") % excep) super(VMWareHTTPWriteFile, self).close() class VmWareHTTPReadFile(VMwareHTTPFile): - """VMWare file read handler Class""" + """VMWare file read handler class""" def __init__(self, host, data_center_name, datastore_name, cookies, file_path, scheme="https"): - """Initialize the file specifics.""" base_url = "%s://%s/folder/%s" % (scheme, host, urllib.pathname2url(file_path)) param_list = {"dcPath": data_center_name, "dsName": datastore_name} @@ -295,9 +251,9 @@ class VmWareHTTPReadFile(VMwareHTTPFile): VMwareHTTPFile.__init__(self, conn) def read(self, chunk_size=READ_CHUNKSIZE): - """Read a chunk of data.""" + """Read a chunk of data""" return self.file_handle.read(chunk_size) def get_size(self): - """Get size of the file to be read.""" + """Get size of the file to be read""" return self.file_handle.headers.get("Content-Length", -1) diff --git a/nova/virt/vmwareapi/vim.py b/nova/virt/vmwareapi/vim.py index 9aca1b7ae..6a3e4b376 100644 --- a/nova/virt/vmwareapi/vim.py +++ b/nova/virt/vmwareapi/vim.py @@ -16,32 +16,39 @@ # under the License. """ -Class facilitating SOAP calls to ESX/ESXi server - +Classes for making VMware VI SOAP calls """ import httplib -import ZSI +from suds.client import Client +from suds.plugin import MessagePlugin +from suds.sudsobject import Property -from nova.virt.vmwareapi import VimService_services +from nova import flags RESP_NOT_XML_ERROR = 'Response is "text/html", not "text/xml' CONN_ABORT_ERROR = 'Software caused connection abort' ADDRESS_IN_USE_ERROR = 'Address already in use' +FLAGS = flags.FLAGS +flags.DEFINE_string('vmwareapi_wsdl_loc', + None, + 'VIM Service WSDL Location' + 'E.g http:///vimService.wsdl' + 'Due to a bug in vSphere ESX 4.1 default wsdl' + 'Read the readme for vmware to setup') + class VimException(Exception): """The VIM Exception class""" def __init__(self, exception_summary, excep): - """Initializer""" Exception.__init__(self) self.exception_summary = exception_summary self.exception_obj = excep def __str__(self): - """The informal string representation of the object""" return self.exception_summary + str(self.exception_obj) @@ -56,38 +63,53 @@ class SessionFaultyException(VimException): class VimAttributeError(VimException): - """Attribute Error""" + """VI Attribute Error""" pass +class VIMMessagePlugin(MessagePlugin): + + def addAttributeForValue(self, node): + #suds does not handle AnyType properly + #VI SDK requires type attribute to be set when AnyType is used + if node.name == 'value': + node.set('xsi:type', 'xsd:string') + + def marshalled(self, context): + """Suds will send the specified soap envelope. + Provides the plugin with the opportunity to prune empty + nodes and fixup nodes before sending it to the server + """ + #suds builds the entire request object based on the wsdl schema + #VI SDK throws server errors if optional SOAP nodes are sent without + #values. E.g as opposed to test + context.envelope.prune() + context.envelope.walk(self.addAttributeForValue) + + class Vim: """The VIM Object""" def __init__(self, protocol="https", - host="localhost", - trace=None): + host="localhost"): """ - Initializer - protocol: http or https host : ESX IPAddress[:port] or ESX Hostname[:port] - trace : File handle (eg. sys.stdout, sys.stderr , - open("file.txt",w), Use it only for debugging - SOAP Communication Creates the necessary Communication interfaces, Gets the ServiceContent for initiating SOAP transactions """ self._protocol = protocol self._host_name = host - service_locator = VimService_services.VimServiceLocator() - connect_string = "%s://%s/sdk" % (self._protocol, self._host_name) - if trace == None: - self.proxy = \ - service_locator.getVimPortType(url=connect_string) - else: - self.proxy = service_locator.getVimPortType(url=connect_string, - tracefile=trace) + wsdl_url = FLAGS.vmwareapi_wsdl_loc + if wsdl_url is None: + raise Exception(_("Must specify vmwareapi_wsdl_loc")) + #Use this when VMware fixes their faulty wsdl + #wsdl_url = '%s://%s/sdk/vimService.wsdl' % (self._protocol, + # self._host_name) + url = '%s://%s/sdk' % (self._protocol, self._host_name) + self.client = Client(wsdl_url, location=url, + plugins=[VIMMessagePlugin()]) self._service_content = \ self.RetrieveServiceContent("ServiceInstance") @@ -102,45 +124,29 @@ class Vim: except AttributeError: def vim_request_handler(managed_object, **kwargs): - """ - managed_object : Managed Object Reference or Managed + """managed_object : Managed Object Reference or Managed Object Name **kw : Keyword arguments of the call """ #Dynamic handler for VI SDK Calls - response = None try: - request_msg = \ - self._request_message_builder(attr_name, - managed_object, **kwargs) - request = getattr(self.proxy, attr_name) - response = request(request_msg) - if response == None: - return None - else: - try: - return getattr(response, "_returnval") - except AttributeError, excep: - return None + request_mo = \ + self._request_managed_object_builder(managed_object) + request = getattr(self.client.service, attr_name) + return request(request_mo, **kwargs) except AttributeError, excep: raise VimAttributeError(_("No such SOAP method '%s'" " provided by VI SDK") % (attr_name), excep) - except ZSI.FaultException, excep: - raise SessionFaultyException(_(" in" - " %s:") % (attr_name), excep) - except ZSI.EvaluateException, excep: - raise SessionFaultyException(_(" in" - " %s:") % (attr_name), excep) except (httplib.CannotSendRequest, httplib.ResponseNotReady, httplib.CannotSendHeader), excep: - raise SessionOverLoadException(_("httplib errror in" + raise SessionOverLoadException(_("httplib error in" " %s: ") % (attr_name), excep) except Exception, excep: # Socket errors which need special handling for they # might be caused by ESX API call overload if (str(excep).find(ADDRESS_IN_USE_ERROR) != -1 or - str(excep).find(CONN_ABORT_ERROR)): + str(excep).find(CONN_ABORT_ERROR)) != -1: raise SessionOverLoadException(_("Socket error in" " %s: ") % (attr_name), excep) # Type error that needs special handling for it might be @@ -153,25 +159,18 @@ class Vim: _("Exception in %s ") % (attr_name), excep) return vim_request_handler - def _request_message_builder(self, method_name, managed_object, **kwargs): - """Builds the Request Message""" - #Request Message Builder - request_msg = getattr(VimService_services, \ - method_name + "RequestMsg")() - element = request_msg.new__this(managed_object) + def _request_managed_object_builder(self, managed_object): + """Builds the request managed object""" + #Request Managed Object Builder if type(managed_object) == type(""): - element.set_attribute_type(managed_object) + mo = Property(managed_object) + mo._type = managed_object else: - element.set_attribute_type(managed_object.get_attribute_type()) - request_msg.set_element__this(element) - for key in kwargs: - getattr(request_msg, "set_element_" + key)(kwargs[key]) - return request_msg + mo = managed_object + return mo def __repr__(self): - """The official string representation""" return "VIM Object" def __str__(self): - """The informal string representation""" return "VIM Object" diff --git a/nova/virt/vmwareapi/vim_util.py b/nova/virt/vmwareapi/vim_util.py index d07f7c278..619ad3c0b 100644 --- a/nova/virt/vmwareapi/vim_util.py +++ b/nova/virt/vmwareapi/vim_util.py @@ -17,178 +17,163 @@ """ The VMware API utility module - """ -from nova.virt.vmwareapi.VimService_services_types import ns0 - -MAX_CLONE_RETRIES = 1 - -def build_recursive_traversal_spec(): +def build_recursive_traversal_spec(client_factory): """Builds the Traversal Spec""" #Traversal through "hostFolder" branch - visit_folders_select_spec = ns0.SelectionSpec_Def("visitFolders").pyclass() - visit_folders_select_spec.set_element_name("visitFolders") - select_set = [visit_folders_select_spec] - dc_to_hf = ns0.TraversalSpec_Def("dc_to_hf").pyclass() - dc_to_hf.set_element_name("dc_to_hf") - dc_to_hf.set_element_type("Datacenter") - dc_to_hf.set_element_path("hostFolder") - dc_to_hf.set_element_skip(False) - dc_to_hf.set_element_selectSet(select_set) + visit_folders_select_spec = client_factory.create('ns0:SelectionSpec') + visit_folders_select_spec.name = "visitFolders" + dc_to_hf = client_factory.create('ns0:TraversalSpec') + dc_to_hf.name = "dc_to_hf" + dc_to_hf.type = "Datacenter" + dc_to_hf.path = "hostFolder" + dc_to_hf.skip = False + dc_to_hf.selectSet = [visit_folders_select_spec] #Traversal through "vmFolder" branch - visit_folders_select_spec = ns0.SelectionSpec_Def("visitFolders").pyclass() - visit_folders_select_spec.set_element_name("visitFolders") - select_set = [visit_folders_select_spec] - dc_to_vmf = ns0.TraversalSpec_Def("dc_to_vmf").pyclass() - dc_to_vmf.set_element_name("dc_to_vmf") - dc_to_vmf.set_element_type("Datacenter") - dc_to_vmf.set_element_path("vmFolder") - dc_to_vmf.set_element_skip(False) - dc_to_vmf.set_element_selectSet(select_set) + visit_folders_select_spec = client_factory.create('ns0:SelectionSpec') + visit_folders_select_spec.name = "visitFolders" + dc_to_vmf = client_factory.create('ns0:TraversalSpec') + dc_to_vmf.name = "dc_to_vmf" + dc_to_vmf.type = "Datacenter" + dc_to_vmf.path = "vmFolder" + dc_to_vmf.skip = False + dc_to_vmf.selectSet = [visit_folders_select_spec] #Traversal to the DataStore from the DataCenter visit_folders_select_spec = \ - ns0.SelectionSpec_Def("traverseChild").pyclass() - visit_folders_select_spec.set_element_name("traverseChild") - select_set = [visit_folders_select_spec] - dc_to_ds = ns0.TraversalSpec_Def("dc_to_ds").pyclass() - dc_to_ds.set_element_name("dc_to_ds") - dc_to_ds.set_element_type("Datacenter") - dc_to_ds.set_element_path("datastore") - dc_to_ds.set_element_skip(False) - dc_to_ds.set_element_selectSet(select_set) + client_factory.create('ns0:SelectionSpec') + visit_folders_select_spec.name = "traverseChild" + dc_to_ds = client_factory.create('ns0:TraversalSpec') + dc_to_ds.name = "dc_to_ds" + dc_to_ds.type = "Datacenter" + dc_to_ds.path = "datastore" + dc_to_ds.skip = False + dc_to_ds.selectSet = [visit_folders_select_spec] #Traversal through "vm" branch visit_folders_select_spec = \ - ns0.SelectionSpec_Def("visitFolders").pyclass() - visit_folders_select_spec.set_element_name("visitFolders") - select_set = [visit_folders_select_spec] - h_to_vm = ns0.TraversalSpec_Def("h_to_vm").pyclass() - h_to_vm.set_element_name("h_to_vm") - h_to_vm.set_element_type("HostSystem") - h_to_vm.set_element_path("vm") - h_to_vm.set_element_skip(False) - h_to_vm.set_element_selectSet(select_set) + client_factory.create('ns0:SelectionSpec') + visit_folders_select_spec.name = "visitFolders" + h_to_vm = client_factory.create('ns0:TraversalSpec') + h_to_vm.name = "h_to_vm" + h_to_vm.type = "HostSystem" + h_to_vm.path = "vm" + h_to_vm.skip = False + h_to_vm.selectSet = [visit_folders_select_spec] #Traversal through "host" branch - cr_to_h = ns0.TraversalSpec_Def("cr_to_h").pyclass() - cr_to_h.set_element_name("cr_to_h") - cr_to_h.set_element_type("ComputeResource") - cr_to_h.set_element_path("host") - cr_to_h.set_element_skip(False) - cr_to_h.set_element_selectSet([]) - - cr_to_ds = ns0.TraversalSpec_Def("cr_to_ds").pyclass() - cr_to_ds.set_element_name("cr_to_ds") - cr_to_ds.set_element_type("ComputeResource") - cr_to_ds.set_element_path("datastore") - cr_to_ds.set_element_skip(False) + cr_to_h = client_factory.create('ns0:TraversalSpec') + cr_to_h.name = "cr_to_h" + cr_to_h.type = "ComputeResource" + cr_to_h.path = "host" + cr_to_h.skip = False + cr_to_h.selectSet = [] + + cr_to_ds = client_factory.create('ns0:TraversalSpec') + cr_to_ds.name = "cr_to_ds" + cr_to_ds.type = "ComputeResource" + cr_to_ds.path = "datastore" + cr_to_ds.skip = False #Traversal through "resourcePool" branch - rp_to_rp_select_spec = ns0.SelectionSpec_Def("rp_to_rp").pyclass() - rp_to_rp_select_spec.set_element_name("rp_to_rp") - rp_to_vm_select_spec = ns0.SelectionSpec_Def("rp_to_vm").pyclass() - rp_to_vm_select_spec.set_element_name("rp_to_vm") - select_set = [rp_to_rp_select_spec, rp_to_vm_select_spec] - cr_to_rp = ns0.TraversalSpec_Def("cr_to_rp").pyclass() - cr_to_rp.set_element_name("cr_to_rp") - cr_to_rp.set_element_type("ComputeResource") - cr_to_rp.set_element_path("resourcePool") - cr_to_rp.set_element_skip(False) - cr_to_rp.set_element_selectSet(select_set) + rp_to_rp_select_spec = client_factory.create('ns0:SelectionSpec') + rp_to_rp_select_spec.name = "rp_to_rp" + rp_to_vm_select_spec = client_factory.create('ns0:SelectionSpec') + rp_to_vm_select_spec.name = "rp_to_vm" + cr_to_rp = client_factory.create('ns0:TraversalSpec') + cr_to_rp.name = "cr_to_rp" + cr_to_rp.type = "ComputeResource" + cr_to_rp.path = "resourcePool" + cr_to_rp.skip = False + cr_to_rp.selectSet = [rp_to_rp_select_spec, rp_to_vm_select_spec] #Traversal through all ResourcePools - rp_to_rp_select_spec = ns0.SelectionSpec_Def("rp_to_rp").pyclass() - rp_to_rp_select_spec.set_element_name("rp_to_rp") - rp_to_vm_select_spec = ns0.SelectionSpec_Def("rp_to_vm").pyclass() - rp_to_vm_select_spec.set_element_name("rp_to_vm") - select_set = [rp_to_rp_select_spec, rp_to_vm_select_spec] - rp_to_rp = ns0.TraversalSpec_Def("rp_to_rp").pyclass() - rp_to_rp.set_element_name("rp_to_rp") - rp_to_rp.set_element_type("ResourcePool") - rp_to_rp.set_element_path("resourcePool") - rp_to_rp.set_element_skip(False) - rp_to_rp.set_element_selectSet(select_set) + rp_to_rp_select_spec = client_factory.create('ns0:SelectionSpec') + rp_to_rp_select_spec.name = "rp_to_rp" + rp_to_vm_select_spec = client_factory.create('ns0:SelectionSpec') + rp_to_vm_select_spec.name = "rp_to_vm" + rp_to_rp = client_factory.create('ns0:TraversalSpec') + rp_to_rp.name = "rp_to_rp" + rp_to_rp.type = "ResourcePool" + rp_to_rp.path = "resourcePool" + rp_to_rp.skip = False + rp_to_rp.selectSet = [rp_to_rp_select_spec, rp_to_vm_select_spec] #Traversal through ResourcePools vm folders - rp_to_rp_select_spec = ns0.SelectionSpec_Def("rp_to_rp").pyclass() - rp_to_rp_select_spec.set_element_name("rp_to_rp") - rp_to_vm_select_spec = ns0.SelectionSpec_Def("rp_to_vm").pyclass() - rp_to_vm_select_spec.set_element_name("rp_to_vm") - select_set = [rp_to_rp_select_spec, rp_to_vm_select_spec] - rp_to_vm = ns0.TraversalSpec_Def("rp_to_vm").pyclass() - rp_to_vm.set_element_name("rp_to_vm") - rp_to_vm.set_element_type("ResourcePool") - rp_to_vm.set_element_path("vm") - rp_to_vm.set_element_skip(False) - rp_to_vm.set_element_selectSet(select_set) + rp_to_rp_select_spec = client_factory.create('ns0:SelectionSpec') + rp_to_rp_select_spec.name = "rp_to_rp" + rp_to_vm_select_spec = client_factory.create('ns0:SelectionSpec') + rp_to_vm_select_spec.name = "rp_to_vm" + rp_to_vm = client_factory.create('ns0:TraversalSpec') + rp_to_vm.name = "rp_to_vm" + rp_to_vm.type = "ResourcePool" + rp_to_vm.path = "vm" + rp_to_vm.skip = False + rp_to_vm.selectSet = [rp_to_rp_select_spec, rp_to_vm_select_spec] #Include all Traversals and Recurse into them visit_folders_select_spec = \ - ns0.SelectionSpec_Def("visitFolders").pyclass() - visit_folders_select_spec.set_element_name("visitFolders") - select_set = [visit_folders_select_spec, dc_to_hf, dc_to_vmf, + client_factory.create('ns0:SelectionSpec') + visit_folders_select_spec.name = "visitFolders" + traversal_spec = client_factory.create('ns0:TraversalSpec') + traversal_spec.name = "visitFolders" + traversal_spec.type = "Folder" + traversal_spec.path = "childEntity" + traversal_spec.skip = False + traversal_spec.selectSet = [visit_folders_select_spec, dc_to_hf, dc_to_vmf, cr_to_ds, cr_to_h, cr_to_rp, rp_to_rp, h_to_vm, rp_to_vm] - traversal_spec = ns0.TraversalSpec_Def("visitFolders").pyclass() - traversal_spec.set_element_name("visitFolders") - traversal_spec.set_element_type("Folder") - traversal_spec.set_element_path("childEntity") - traversal_spec.set_element_skip(False) - traversal_spec.set_element_selectSet(select_set) return traversal_spec -def build_property_spec(type="VirtualMachine", properties_to_collect=["name"], +def build_property_spec(client_factory, type="VirtualMachine", + properties_to_collect=["name"], all_properties=False): """Builds the Property Spec""" - property_spec = ns0.PropertySpec_Def("propertySpec").pyclass() - property_spec.set_element_type(type) - property_spec.set_element_all(all_properties) - property_spec.set_element_pathSet(properties_to_collect) + property_spec = client_factory.create('ns0:PropertySpec') + property_spec.all = all_properties + property_spec.pathSet = properties_to_collect + property_spec.type = type return property_spec -def build_object_spec(root_folder, traversal_specs): +def build_object_spec(client_factory, root_folder, traversal_specs): """Builds the object Spec""" - object_spec = ns0.ObjectSpec_Def("ObjectSpec").pyclass() - object_spec.set_element_obj(root_folder) - object_spec.set_element_skip(False) - object_spec.set_element_selectSet(traversal_specs) + object_spec = client_factory.create('ns0:ObjectSpec') + object_spec.obj = root_folder + object_spec.skip = False + object_spec.selectSet = traversal_specs return object_spec -def build_property_filter_spec(property_specs, object_specs): +def build_property_filter_spec(client_factory, property_specs, object_specs): """Builds the Property Filter Spec""" - property_filter_spec = \ - ns0.PropertyFilterSpec_Def("PropertyFilterSpec").pyclass() - property_filter_spec.set_element_propSet(property_specs) - property_filter_spec.set_element_objectSet(object_specs) + property_filter_spec = client_factory.create('ns0:PropertyFilterSpec') + property_filter_spec.propSet = property_specs + property_filter_spec.objectSet = object_specs return property_filter_spec def get_object_properties(vim, collector, mobj, type, properties): """Gets the properties of the Managed object specified""" + client_factory = vim.client.factory if mobj is None: return None usecoll = collector if usecoll is None: - usecoll = vim.get_service_content().PropertyCollector - property_filter_spec = \ - ns0.PropertyFilterSpec_Def("PropertyFilterSpec").pyclass() - property_filter_spec._propSet = \ - [ns0.PropertySpec_Def("PropertySpec").pyclass()] - property_filter_spec.PropSet[0]._all = \ - (properties == None or len(properties) == 0) - property_filter_spec.PropSet[0]._type = type - property_filter_spec.PropSet[0]._pathSet = properties - - property_filter_spec._objectSet = \ - [ns0.ObjectSpec_Def("ObjectSpec").pyclass()] - property_filter_spec.ObjectSet[0]._obj = mobj - property_filter_spec.ObjectSet[0]._skip = False + usecoll = vim.get_service_content().propertyCollector + property_filter_spec = client_factory.create('ns0:PropertyFilterSpec') + property_spec = client_factory.create('ns0:PropertySpec') + property_spec.all = (properties == None or len(properties) == 0) + property_spec.pathSet = properties + property_spec.type = type + object_spec = client_factory.create('ns0:ObjectSpec') + object_spec.obj = mobj + object_spec.skip = False + property_filter_spec.propSet = [property_spec] + property_filter_spec.objectSet = [object_spec] return vim.RetrieveProperties(usecoll, specSet=[property_filter_spec]) @@ -198,74 +183,68 @@ def get_dynamic_property(vim, mobj, type, property_name): get_object_properties(vim, None, mobj, type, [property_name]) property_value = None if obj_content: - dynamic_property = obj_content[0].PropSet + dynamic_property = obj_content[0].propSet if dynamic_property: - property_value = dynamic_property[0].Val + property_value = dynamic_property[0].val return property_value def get_objects(vim, type, properties_to_collect=["name"], all=False): """Gets the list of objects of the type specified""" - object_spec = build_object_spec(vim.get_service_content().RootFolder, - [build_recursive_traversal_spec()]) - property_spec = build_property_spec(type=type, + client_factory = vim.client.factory + object_spec = build_object_spec(client_factory, + vim.get_service_content().rootFolder, + [build_recursive_traversal_spec(client_factory)]) + property_spec = build_property_spec(client_factory, type=type, properties_to_collect=properties_to_collect, all_properties=all) - property_filter_spec = build_property_filter_spec([property_spec], + property_filter_spec = build_property_filter_spec(client_factory, + [property_spec], [object_spec]) - return vim.RetrieveProperties(vim.get_service_content().PropertyCollector, + return vim.RetrieveProperties(vim.get_service_content().propertyCollector, specSet=[property_filter_spec]) -def get_traversal_spec(type, path, name="traversalSpec"): - """Builds the traversal spec object""" - t_spec = ns0.TraversalSpec_Def(name).pyclass() - t_spec._name = name - t_spec._type = type - t_spec._path = path - t_spec._skip = False - return t_spec - - -def get_prop_spec(type, properties): +def get_prop_spec(client_factory, type, properties): """Builds the Property Spec Object""" - prop_spec = ns0.PropertySpec_Def("PropertySpec").pyclass() - prop_spec._type = type - prop_spec._pathSet = properties + prop_spec = client_factory.create('ns0:PropertySpec') + prop_spec.type = type + prop_spec.pathSet = properties return prop_spec -def get_obj_spec(obj, select_set=None): +def get_obj_spec(client_factory, obj, select_set=None): """Builds the Object Spec object""" - obj_spec = ns0.ObjectSpec_Def("ObjectSpec").pyclass() - obj_spec._obj = obj - obj_spec._skip = False + obj_spec = client_factory.create('ns0:ObjectSpec') + obj_spec.obj = obj + obj_spec.skip = False if select_set is not None: - obj_spec._selectSet = select_set + obj_spec.selectSet = select_set return obj_spec -def get_prop_filter_spec(obj_spec, prop_spec): +def get_prop_filter_spec(client_factory, obj_spec, prop_spec): """Builds the Property Filter Spec Object""" prop_filter_spec = \ - ns0.PropertyFilterSpec_Def("PropertyFilterSpec").pyclass() - prop_filter_spec._propSet = prop_spec - prop_filter_spec._objectSet = obj_spec + client_factory.create('ns0:PropertyFilterSpec') + prop_filter_spec.propSet = prop_spec + prop_filter_spec.objectSet = obj_spec return prop_filter_spec def get_properites_for_a_collection_of_objects(vim, type, obj_list, properties): + """Gets the list of properties for the collection of + objects of the type specified """ - Gets the list of properties for the collection of objects of the - type specified - """ + client_factory = vim.client.factory if len(obj_list) == 0: return [] - prop_spec = get_prop_spec(type, properties) + prop_spec = get_prop_spec(client_factory, type, properties) lst_obj_specs = [] for obj in obj_list: - lst_obj_specs.append(get_obj_spec(obj)) - prop_filter_spec = get_prop_filter_spec(lst_obj_specs, [prop_spec]) - return vim.RetrieveProperties(vim.get_service_content().PropertyCollector, + lst_obj_specs.append(get_obj_spec(client_factory, obj)) + prop_filter_spec = get_prop_filter_spec(client_factory, + lst_obj_specs, [prop_spec]) + return vim.RetrieveProperties(vim.get_service_content().propertyCollector, specSet=[prop_filter_spec]) diff --git a/nova/virt/vmwareapi/vm_util.py b/nova/virt/vmwareapi/vm_util.py index 05e49de83..a46b4d10c 100644 --- a/nova/virt/vmwareapi/vm_util.py +++ b/nova/virt/vmwareapi/vm_util.py @@ -14,24 +14,19 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. - """ -Utility functions to handle virtual disks, virtual network adapters, VMs etc. - +The VMware API VM utility module to build SOAP object specs """ -from nova.virt.vmwareapi.VimService_services_types import ns0 - def build_datastore_path(datastore_name, path): - """Builds the datastore compliant path.""" + """Build the datastore compliant path""" return "[%s] %s" % (datastore_name, path) def split_datastore_path(datastore_path): - """ - Split the VMWare style datastore path to get the Datastore name and the - entity path + """Split the VMWare style datastore path to get the Datastore + name and the entity path """ spl = datastore_path.split('[', 1)[1].split(']', 1) path = "" @@ -42,117 +37,95 @@ def split_datastore_path(datastore_path): return datastore_url, path.strip() -def get_vm_create_spec(instance, data_store_name, network_name="vmnet0", +def get_vm_create_spec(client_factory, instance, data_store_name, + network_name="vmnet0", os_type="otherGuest"): - """Builds the VM Create spec.""" - config_spec = ns0.VirtualMachineConfigSpec_Def( - "VirtualMachineConfigSpec").pyclass() - - config_spec._name = instance.name - config_spec._guestId = os_type + """Builds the VM Create spec""" + config_spec = client_factory.create('ns0:VirtualMachineConfigSpec') + config_spec.name = instance.name + config_spec.guestId = os_type - vm_file_info = ns0.VirtualMachineFileInfo_Def( - "VirtualMachineFileInfo").pyclass() - vm_file_info._vmPathName = "[" + data_store_name + "]" - config_spec._files = vm_file_info + vm_file_info = client_factory.create('ns0:VirtualMachineFileInfo') + vm_file_info.vmPathName = "[" + data_store_name + "]" + config_spec.files = vm_file_info - tools_info = ns0.ToolsConfigInfo_Def("ToolsConfigInfo") - tools_info._afterPowerOn = True - tools_info._afterResume = True - tools_info._beforeGuestStandby = True - tools_info._bbeforeGuestShutdown = True - tools_info._beforeGuestReboot = True + tools_info = client_factory.create('ns0:ToolsConfigInfo') + tools_info.afterPowerOn = True + tools_info.afterResume = True + tools_info.beforeGuestStandby = True + tools_info.beforeGuestShutdown = True + tools_info.beforeGuestReboot = True - config_spec._tools = tools_info - config_spec._numCPUs = int(instance.vcpus) - config_spec._memoryMB = int(instance.memory_mb) + config_spec.tools = tools_info + config_spec.numCPUs = int(instance.vcpus) + config_spec.memoryMB = int(instance.memory_mb) - nic_spec = create_network_spec(network_name, instance.mac_address) + nic_spec = create_network_spec(client_factory, + network_name, instance.mac_address) device_config_spec = [nic_spec] - config_spec._deviceChange = device_config_spec + config_spec.deviceChange = device_config_spec return config_spec -def create_controller_spec(key): - """ - Builds a Config Spec for the LSI Logic Controller's addition which acts - as the controller for the Virtual Hard disk to be attached to the VM +def create_controller_spec(client_factory, key): + """Builds a Config Spec for the LSI Logic Controller's addition + which acts as the controller for the + Virtual Hard disk to be attached to the VM """ #Create a controller for the Virtual Hard Disk virtual_device_config = \ - ns0.VirtualDeviceConfigSpec_Def("VirtualDeviceConfigSpec").pyclass() - virtual_device_config._operation = "add" + client_factory.create('ns0:VirtualDeviceConfigSpec') + virtual_device_config.operation = "add" virtual_lsi = \ - ns0.VirtualLsiLogicController_Def( - "VirtualLsiLogicController").pyclass() - virtual_lsi._key = key - virtual_lsi._busNumber = 0 - virtual_lsi._sharedBus = "noSharing" - virtual_device_config._device = virtual_lsi - + client_factory.create('ns0:VirtualLsiLogicController') + virtual_lsi.key = key + virtual_lsi.busNumber = 0 + virtual_lsi.sharedBus = "noSharing" + virtual_device_config.device = virtual_lsi return virtual_device_config -def create_network_spec(network_name, mac_address): - """ - Builds a config spec for the addition of a new network adapter to - the VM. - """ +def create_network_spec(client_factory, network_name, mac_address): + """Builds a config spec for the addition of a new network + adapter to the VM""" network_spec = \ - ns0.VirtualDeviceConfigSpec_Def("VirtualDeviceConfigSpec").pyclass() - network_spec._operation = "add" + client_factory.create('ns0:VirtualDeviceConfigSpec') + network_spec.operation = "add" #Get the recommended card type for the VM based on the guest OS of the VM - net_device = ns0.VirtualPCNet32_Def("VirtualPCNet32").pyclass() + net_device = client_factory.create('ns0:VirtualPCNet32') backing = \ - ns0.VirtualEthernetCardNetworkBackingInfo_Def( - "VirtualEthernetCardNetworkBackingInfo").pyclass() - backing._deviceName = network_name + client_factory.create('ns0:VirtualEthernetCardNetworkBackingInfo') + backing.deviceName = network_name connectable_spec = \ - ns0.VirtualDeviceConnectInfo_Def("VirtualDeviceConnectInfo").pyclass() - connectable_spec._startConnected = True - connectable_spec._allowGuestControl = True - connectable_spec._connected = True + client_factory.create('ns0:VirtualDeviceConnectInfo') + connectable_spec.startConnected = True + connectable_spec.allowGuestControl = True + connectable_spec.connected = True - net_device._connectable = connectable_spec - net_device._backing = backing + net_device.connectable = connectable_spec + net_device.backing = backing #The Server assigns a Key to the device. Here we pass a -ve temporary key. #-ve because actual keys are +ve numbers and we don't #want a clash with the key that server might associate with the device - net_device._key = -47 - net_device._addressType = "manual" - net_device._macAddress = mac_address - net_device._wakeOnLanEnabled = True + net_device.key = -47 + net_device.addressType = "manual" + net_device.macAddress = mac_address + net_device.wakeOnLanEnabled = True - network_spec._device = net_device + network_spec.device = net_device return network_spec -def get_datastore_search_sepc(pattern=None): - """Builds the datastore search spec.""" - host_datastore_browser_search_spec = \ - ns0.HostDatastoreBrowserSearchSpec_Def( - "HostDatastoreBrowserSearchSpec").pyclass() - file_query_flags = ns0.FileQueryFlags_Def("FileQueryFlags").pyclass() - file_query_flags._modification = False - file_query_flags._fileSize = True - file_query_flags._fileType = True - file_query_flags._fileOwner = True - host_datastore_browser_search_spec._details = file_query_flags - if pattern is not None: - host_datastore_browser_search_spec._matchPattern = pattern - return host_datastore_browser_search_spec - - -def get_vmdk_attach_config_sepc(disksize, file_path, adapter_type="lsiLogic"): - """Builds the vmdk attach config spec.""" - config_spec = ns0.VirtualMachineConfigSpec_Def( - "VirtualMachineConfigSpec").pyclass() +def get_vmdk_attach_config_spec(client_factory, + disksize, file_path, adapter_type="lsiLogic"): + """Builds the vmdk attach config spec""" + config_spec = client_factory.create('ns0:VirtualMachineConfigSpec') #The controller Key pertains to the Key of the LSI Logic Controller, which #controls this Hard Disk @@ -163,145 +136,165 @@ def get_vmdk_attach_config_sepc(disksize, file_path, adapter_type="lsiLogic"): controller_key = 200 else: controller_key = -101 - controller_spec = create_controller_spec(controller_key) + controller_spec = create_controller_spec(client_factory, + controller_key) device_config_spec.append(controller_spec) - virtual_device_config_spec = create_virtual_disk_spec(disksize, - controller_key, file_path) + virtual_device_config_spec = create_virtual_disk_spec(client_factory, + disksize, controller_key, file_path) device_config_spec.append(virtual_device_config_spec) - config_spec._deviceChange = device_config_spec + config_spec.deviceChange = device_config_spec return config_spec -def get_vmdk_file_path_and_adapter_type(hardware_devices): - """Gets the vmdk file path and the storage adapter type.""" - if isinstance(hardware_devices.typecode, ns0.ArrayOfVirtualDevice_Def): +def get_vmdk_file_path_and_adapter_type(client_factory, hardware_devices): + """Gets the vmdk file path and the storage adapter type""" + if hardware_devices.__class__.__name__ == "ArrayOfVirtualDevice": hardware_devices = hardware_devices.VirtualDevice vmdk_file_path = None vmdk_controler_key = None adapter_type_dict = {} for device in hardware_devices: - if (isinstance(device.typecode, ns0.VirtualDisk_Def) and - isinstance(device.Backing.typecode, - ns0.VirtualDiskFlatVer2BackingInfo_Def)): - vmdk_file_path = device.Backing.FileName - vmdk_controler_key = device.ControllerKey - elif isinstance(device.typecode, ns0.VirtualLsiLogicController_Def): - adapter_type_dict[device.Key] = "lsiLogic" - elif isinstance(device.typecode, ns0.VirtualBusLogicController_Def): - adapter_type_dict[device.Key] = "busLogic" - elif isinstance(device.typecode, ns0.VirtualIDEController_Def): - adapter_type_dict[device.Key] = "ide" - elif isinstance(device.typecode, ns0.VirtualLsiLogicSASController_Def): - adapter_type_dict[device.Key] = "lsiLogic" + if device.__class__.__name__ == "VirtualDisk" and \ + device.backing.__class__.__name__ \ + == "VirtualDiskFlatVer2BackingInfo": + vmdk_file_path = device.backing.fileName + vmdk_controler_key = device.controllerKey + elif device.__class__.__name__ == "VirtualLsiLogicController": + adapter_type_dict[device.key] = "lsiLogic" + elif device.__class__.__name__ == "VirtualBusLogicController": + adapter_type_dict[device.key] = "busLogic" + elif device.__class__.__name__ == "VirtualIDEController": + adapter_type_dict[device.key] = "ide" + elif device.__class__.__name__ == "VirtualLsiLogicSASController": + adapter_type_dict[device.key] = "lsiLogic" adapter_type = adapter_type_dict.get(vmdk_controler_key, "") return vmdk_file_path, adapter_type -def get_copy_virtual_disk_spec(adapter_type="lsilogic"): - """Builds the Virtual Disk copy spec.""" - dest_spec = ns0.VirtualDiskSpec_Def("VirtualDiskSpec").pyclass() - dest_spec.AdapterType = adapter_type - dest_spec.DiskType = "thick" +def get_copy_virtual_disk_spec(client_factory, adapter_type="lsilogic"): + """Builds the Virtual Disk copy spec""" + dest_spec = client_factory.create('ns0:VirtualDiskSpec') + dest_spec.adapterType = adapter_type + dest_spec.diskType = "thick" return dest_spec -def get_vmdk_create_spec(size_in_kb, adapter_type="lsiLogic"): - """Builds the virtual disk create sepc.""" +def get_vmdk_create_spec(client_factory, size_in_kb, adapter_type="lsiLogic"): + """Builds the virtual disk create spec""" create_vmdk_spec = \ - ns0.FileBackedVirtualDiskSpec_Def("VirtualDiskSpec").pyclass() - create_vmdk_spec._adapterType = adapter_type - create_vmdk_spec._diskType = "thick" - create_vmdk_spec._capacityKb = size_in_kb + client_factory.create('ns0:FileBackedVirtualDiskSpec') + create_vmdk_spec.adapterType = adapter_type + create_vmdk_spec.diskType = "thick" + create_vmdk_spec.capacityKb = size_in_kb return create_vmdk_spec -def create_virtual_disk_spec(disksize, controller_key, file_path=None): - """Creates a Spec for the addition/attaching of a Virtual Disk to the VM""" +def create_virtual_disk_spec(client_factory, + disksize, controller_key, file_path=None): + """Creates a Spec for the addition/attaching of a + Virtual Disk to the VM""" virtual_device_config = \ - ns0.VirtualDeviceConfigSpec_Def("VirtualDeviceConfigSpec").pyclass() - virtual_device_config._operation = "add" + client_factory.create('ns0:VirtualDeviceConfigSpec') + virtual_device_config.operation = "add" if file_path is None: - virtual_device_config._fileOperation = "create" + virtual_device_config.fileOperation = "create" - virtual_disk = ns0.VirtualDisk_Def("VirtualDisk").pyclass() + virtual_disk = client_factory.create('ns0:VirtualDisk') - disk_file_backing = ns0.VirtualDiskFlatVer2BackingInfo_Def( - "VirtualDiskFlatVer2BackingInfo").pyclass() - disk_file_backing._diskMode = "persistent" - disk_file_backing._thinProvisioned = False + disk_file_backing = \ + client_factory.create('ns0:VirtualDiskFlatVer2BackingInfo') + disk_file_backing.diskMode = "persistent" + disk_file_backing.thinProvisioned = False if file_path is not None: - disk_file_backing._fileName = file_path + disk_file_backing.fileName = file_path else: - disk_file_backing._fileName = "" + disk_file_backing.fileName = "" - connectable_spec = ns0.VirtualDeviceConnectInfo_Def( - "VirtualDeviceConnectInfo").pyclass() - connectable_spec._startConnected = True - connectable_spec._allowGuestControl = False - connectable_spec._connected = True + connectable_spec = client_factory.create('ns0:VirtualDeviceConnectInfo') + connectable_spec.startConnected = True + connectable_spec.allowGuestControl = False + connectable_spec.connected = True - virtual_disk._backing = disk_file_backing - virtual_disk._connectable = connectable_spec + virtual_disk.backing = disk_file_backing + virtual_disk.connectable = connectable_spec #The Server assigns a Key to the device. Here we pass a -ve temporary key. #-ve because actual keys are +ve numbers and we don't #want a clash with the key that server might associate with the device - virtual_disk._key = -100 - virtual_disk._controllerKey = controller_key - virtual_disk._unitNumber = 0 - virtual_disk._capacityInKB = disksize + virtual_disk.key = -100 + virtual_disk.controllerKey = controller_key + virtual_disk.unitNumber = 0 + virtual_disk.capacityInKB = disksize - virtual_device_config._device = virtual_disk + virtual_device_config.device = virtual_disk return virtual_device_config -def get_dummy_vm_create_spec(name, data_store_name): - """Builds the dummy VM create spec.""" - config_spec = ns0.VirtualMachineConfigSpec_Def( - "VirtualMachineConfigSpec").pyclass() +def get_dummy_vm_create_spec(client_factory, name, data_store_name): + """Builds the dummy VM create spec""" + config_spec = client_factory.create('ns0:VirtualMachineConfigSpec') - config_spec._name = name - config_spec._guestId = "otherGuest" + config_spec.name = name + config_spec.guestId = "otherGuest" - vm_file_info = ns0.VirtualMachineFileInfo_Def( - "VirtualMachineFileInfo").pyclass() - vm_file_info._vmPathName = "[" + data_store_name + "]" - config_spec._files = vm_file_info + vm_file_info = client_factory.create('ns0:VirtualMachineFileInfo') + vm_file_info.vmPathName = "[" + data_store_name + "]" + config_spec.files = vm_file_info - tools_info = ns0.ToolsConfigInfo_Def("ToolsConfigInfo") - tools_info._afterPowerOn = True - tools_info._afterResume = True - tools_info._beforeGuestStandby = True - tools_info._bbeforeGuestShutdown = True - tools_info._beforeGuestReboot = True + tools_info = client_factory.create('ns0:ToolsConfigInfo') + tools_info.afterPowerOn = True + tools_info.afterResume = True + tools_info.beforeGuestStandby = True + tools_info.beforeGuestShutdown = True + tools_info.beforeGuestReboot = True - config_spec._tools = tools_info - config_spec._numCPUs = 1 - config_spec._memoryMB = 4 + config_spec.tools = tools_info + config_spec.numCPUs = 1 + config_spec.memoryMB = 4 controller_key = -101 - controller_spec = create_controller_spec(controller_key) - disk_spec = create_virtual_disk_spec(1024, controller_key) + controller_spec = create_controller_spec(client_factory, controller_key) + disk_spec = create_virtual_disk_spec(client_factory, 1024, controller_key) device_config_spec = [controller_spec, disk_spec] - config_spec._deviceChange = device_config_spec + config_spec.deviceChange = device_config_spec return config_spec -def get_machine_id_change_spec(mac, ip_addr, netmask, gateway): - """Builds the machine id change config spec.""" +def get_machine_id_change_spec(client_factory, mac, ip_addr, netmask, gateway): + """Builds the machine id change config spec""" machine_id_str = "%s;%s;%s;%s" % (mac, ip_addr, netmask, gateway) - virtual_machine_config_spec = ns0.VirtualMachineConfigSpec_Def( - "VirtualMachineConfigSpec").pyclass() - opt = ns0.OptionValue_Def('OptionValue').pyclass() - opt._key = "machine.id" - opt._value = machine_id_str - virtual_machine_config_spec._extraConfig = [opt] + virtual_machine_config_spec = \ + client_factory.create('ns0:VirtualMachineConfigSpec') + + opt = client_factory.create('ns0:OptionValue') + opt.key = "machine.id" + opt.value = machine_id_str + virtual_machine_config_spec.extraConfig = [opt] return virtual_machine_config_spec + + +def get_add_vswitch_port_group_spec(client_factory, vswitch_name, + port_group_name, vlan_id): + """Builds the virtual switch port group add spec""" + vswitch_port_group_spec = client_factory.create('ns0:HostPortGroupSpec') + vswitch_port_group_spec.name = port_group_name + vswitch_port_group_spec.vswitchName = vswitch_name + + #VLAN ID of 0 means that VLAN tagging is not to be done for the network. + vswitch_port_group_spec.vlanId = int(vlan_id) + + policy = client_factory.create('ns0:HostNetworkPolicy') + nicteaming = client_factory.create('ns0:HostNicTeamingPolicy') + nicteaming.notifySwitches = True + policy.nicTeaming = nicteaming + + vswitch_port_group_spec.policy = policy + return vswitch_port_group_spec diff --git a/nova/virt/vmwareapi/vmops.py b/nova/virt/vmwareapi/vmops.py index 3e32fceef..c2e6d9bf8 100644 --- a/nova/virt/vmwareapi/vmops.py +++ b/nova/virt/vmwareapi/vmops.py @@ -17,20 +17,27 @@ """ Class for VM tasks like spawn, snapshot, suspend, resume etc. - """ -import logging + +import base64 import os import time +import urllib +import urllib2 import uuid -from nova import db from nova import context +from nova import db +from nova import exception +from nova import flags +from nova import log as logging from nova.compute import power_state from nova.virt.vmwareapi import vim_util from nova.virt.vmwareapi import vm_util from nova.virt.vmwareapi import vmware_images +from nova.virt.vmwareapi.network_utils import NetworkHelper +FLAGS = flags.FLAGS LOG = logging.getLogger("nova.virt.vmwareapi.vmops") VMWARE_POWER_STATES = { @@ -40,14 +47,14 @@ VMWARE_POWER_STATES = { class VMWareVMOps(object): - """Management class for VM-related tasks""" + """ Management class for VM-related tasks """ def __init__(self, session): - """Initializer""" + """ Initializer """ self._session = session def _wait_with_callback(self, instance_id, task, callback): - """Waits for the task to finish and does a callback after""" + """ Waits for the task to finish and does a callback after """ ret = None try: ret = self._session._wait_for_task(instance_id, task) @@ -56,7 +63,7 @@ class VMWareVMOps(object): callback(ret) def list_instances(self): - """Lists the VM instances that are registered with the ESX host""" + """ Lists the VM instances that are registered with the ESX host """ LOG.debug(_("Getting list of instances")) vms = self._session._call_method(vim_util, "get_objects", "VirtualMachine", @@ -65,11 +72,11 @@ class VMWareVMOps(object): for vm in vms: vm_name = None conn_state = None - for prop in vm.PropSet: - if prop.Name == "name": - vm_name = prop.Val - elif prop.Name == "runtime.connectionState": - conn_state = prop.Val + for prop in vm.propSet: + if prop.name == "name": + vm_name = prop.val + elif prop.name == "runtime.connectionState": + conn_state = prop.val # Ignoring the oprhaned or inaccessible VMs if conn_state not in ["orphaned", "inaccessible"]: lst_vm_names.append(vm_name) @@ -93,18 +100,19 @@ class VMWareVMOps(object): """ vm_ref = self._get_vm_ref_from_the_name(instance.name) if vm_ref: - raise Exception('Attempted to create a VM with a name %s, ' - 'but that already exists on the host' % instance.name) - bridge = db.network_get_by_instance(context.get_admin_context(), - instance['id'])['bridge'] - #TODO(sateesh): Shouldn't we consider any public network in case - #the network name supplied isn't there + raise Exception(_("Attempted to create a VM with a name %s, " + "but that already exists on the host") % instance.name) + + client_factory = self._session._get_vim().client.factory + + network = db.network_get_by_instance(context.get_admin_context(), + instance['id']) + net_name = network['bridge'] network_ref = \ - self._get_network_with_the_name(bridge) + NetworkHelper.get_network_with_the_name(self._session, net_name) if network_ref is None: - raise Exception("Network with the name '%s' doesn't exist on " - "the ESX host" % bridge) - + raise Exception(_("Network with the name '%s' doesn't exist on" + " the ESX host") % net_name) #Get the Size of the flat vmdk file that is there on the storage #repository. image_size, image_properties = \ @@ -121,11 +129,11 @@ class VMWareVMOps(object): for elem in data_stores: ds_name = None ds_type = None - for prop in elem.PropSet: - if prop.Name == "summary.type": - ds_type = prop.Val - elif prop.Name == "summary.name": - ds_name = prop.Val + for prop in elem.propSet: + if prop.name == "summary.type": + ds_type = prop.val + elif prop.name == "summary.name": + ds_name = prop.val #Local storage identifier if ds_type == "VMFS": data_store_name = ds_name @@ -135,22 +143,21 @@ class VMWareVMOps(object): msg = _("Couldn't get a local Datastore reference") LOG.exception(msg) raise Exception(msg) - - config_spec = vm_util.get_vm_create_spec(instance, data_store_name, - bridge, os_type) + config_spec = vm_util.get_vm_create_spec(client_factory, instance, + data_store_name, net_name, os_type) #Get the Vm folder ref from the datacenter dc_objs = self._session._call_method(vim_util, "get_objects", "Datacenter", ["vmFolder"]) #There is only one default datacenter in a standalone ESX host - vm_folder_ref = dc_objs[0].PropSet[0].Val + vm_folder_ref = dc_objs[0].propSet[0].val #Get the resource pool. Taking the first resource pool coming our way. #Assuming that is the default resource pool. res_pool_mor = self._session._call_method(vim_util, "get_objects", - "ResourcePool")[0].Obj + "ResourcePool")[0].obj - LOG.debug(_("Creating VM with the name %s on the ESX host") % \ + LOG.debug(_("Creating VM with the name %s on the ESX host") % instance.name) #Create the VM on the ESX host vm_create_task = self._session._call_method(self._session._get_vim(), @@ -158,11 +165,11 @@ class VMWareVMOps(object): config=config_spec, pool=res_pool_mor) self._session._wait_for_task(instance.id, vm_create_task) - LOG.debug(_("Created VM with the name %s on the ESX host") % \ + LOG.debug(_("Created VM with the name %s on the ESX host") % instance.name) # Set the machine id for the VM for setting the IP - self._set_machine_id(instance) + self._set_machine_id(client_factory, instance) #Naming the VM files in correspondence with the VM instance name @@ -181,61 +188,56 @@ class VMWareVMOps(object): #depend on the size of the disk, thin/thick provisioning and the #storage adapter type. #Here we assume thick provisioning and lsiLogic for the adapter type - LOG.debug(_("Creating Virtual Disk of size %(vmdk_file_size_in_kb)sKB" - " and adapter type %(adapter_type)s on" - " the ESX host local store %(data_store_name)s") % - {'vmdk_file_size_in_kb': vmdk_file_size_in_kb, - 'adapter_type': adapter_type, - 'data_store_name': data_store_name}) - vmdk_create_spec = vm_util.get_vmdk_create_spec(vmdk_file_size_in_kb, - adapter_type) + LOG.debug(_("Creating Virtual Disk of size %(vmdk_file_size_in_kb)s " + "KB and adapter type %(adapter_type)s on " + "the ESX host local store %(data_store_name)s") % locals()) + vmdk_create_spec = vm_util.get_vmdk_create_spec(client_factory, + vmdk_file_size_in_kb, adapter_type) vmdk_create_task = self._session._call_method(self._session._get_vim(), "CreateVirtualDisk_Task", - self._session._get_vim().get_service_content().VirtualDiskManager, + self._session._get_vim().get_service_content().virtualDiskManager, name=uploaded_vmdk_path, datacenter=self._get_datacenter_name_and_ref()[0], spec=vmdk_create_spec) self._session._wait_for_task(instance.id, vmdk_create_task) - LOG.debug(_("Created Virtual Disk of size %(vmdk_file_size_in_kb)s KB" - " on the ESX host local store %(data_store_name)s ") % - {'vmdk_file_size_in_kb': vmdk_file_size_in_kb, - 'data_store_name': data_store_name}) - - LOG.debug(_("Deleting the file %(flat_uploaded_vmdk_path)s on " - "the ESX host local store %(data_store_names)s") % - {'flat_uploaded_vmdk_path': flat_uploaded_vmdk_path, - 'data_store_name': data_store_name}) + LOG.debug(_("Created Virtual Disk of size %(vmdk_file_size_in_kb)s " + "KB on the ESX host local store " + "%(data_store_name)s") % locals()) + + LOG.debug(_("Deleting the file %(flat_uploaded_vmdk_path)s " + "on the ESX host local" + "store %(data_store_name)s") % locals()) #Delete the -flat.vmdk file created. .vmdk file is retained. vmdk_delete_task = self._session._call_method(self._session._get_vim(), "DeleteDatastoreFile_Task", - self._session._get_vim().get_service_content().FileManager, + self._session._get_vim().get_service_content().fileManager, name=flat_uploaded_vmdk_path) self._session._wait_for_task(instance.id, vmdk_delete_task) - LOG.debug(_("Deleted the file %(flat_uploaded_vmdk_path)s on " - "the ESX host local store %(data_store_name)s ") % - {'flat_uploaded_vmdk_path': flat_uploaded_vmdk_path, - 'data_store_name': data_store_name}) - - LOG.debug(_("Downloading image file %(image_id)s to the " - "ESX data store %(datastore_name)s ") % - {'image_id': instance.image_id, - 'data_store_name': data_store_name}) + LOG.debug(_("Deleted the file %(flat_uploaded_vmdk_path)s on the " + "ESX host local store %(data_store_name)s") % locals()) + + LOG.debug(_("Downloading image file data %(image_id)s to the ESX " + "data store %(data_store_name)s") % + ({'image_id': instance.image_id, + 'data_store_name': data_store_name})) + cookies = self._session._get_vim().client.options.transport.cookiejar # Upload the -flat.vmdk file whose meta-data file we just created above vmware_images.fetch_image( - instance.image_id, - instance, - host=self._session._host_ip, - data_center_name=self._get_datacenter_name_and_ref()[1], - datastore_name=data_store_name, - cookies=self._session._get_vim().proxy.binding.cookies, - file_path=flat_uploaded_vmdk_name) - LOG.debug(_("Downloaded image file %(image_id)s to the ESX data " - "store %(data_store_name)s ") % - {'image_id': instance.image_id, - 'data_store_name': data_store_name}) + instance.image_id, + instance, + host=self._session._host_ip, + data_center_name=self._get_datacenter_name_and_ref()[1], + datastore_name=data_store_name, + cookies=cookies, + file_path=flat_uploaded_vmdk_name) + LOG.debug(_("Downloaded image file data %(image_id)s to the ESX " + "data store %(data_store_name)s") % + ({'image_id': instance.image_id, + 'data_store_name': data_store_name})) #Attach the vmdk uploaded to the VM. VM reconfigure is done to do so. - vmdk_attach_config_spec = vm_util.get_vmdk_attach_config_sepc( + vmdk_attach_config_spec = vm_util.get_vmdk_attach_config_spec( + client_factory, vmdk_file_size_in_kb, uploaded_vmdk_path, adapter_type) vm_ref = self._get_vm_ref_from_the_name(instance.name) @@ -248,12 +250,12 @@ class VMWareVMOps(object): LOG.debug(_("Reconfigured VM instance %s to attach the image " "disk") % instance.name) - LOG.debug(_("Powering on the VM instance %s ") % instance.name) + LOG.debug(_("Powering on the VM instance %s") % instance.name) #Power On the VM power_on_task = self._session._call_method(self._session._get_vim(), "PowerOnVM_Task", vm_ref) self._session._wait_for_task(instance.id, power_on_task) - LOG.debug(_("Powered on the VM instance %s ") % instance.name) + LOG.debug(_("Powered on the VM instance %s") % instance.name) def snapshot(self, instance, snapshot_name): """ @@ -277,14 +279,17 @@ class VMWareVMOps(object): hardware_devices = self._session._call_method(vim_util, "get_dynamic_property", vm_ref, "VirtualMachine", "config.hardware.device") + client_factory = self._session._get_vim().client.factory vmdk_file_path_before_snapshot, adapter_type = \ - vm_util.get_vmdk_file_path_and_adapter_type(hardware_devices) + vm_util.get_vmdk_file_path_and_adapter_type(client_factory, + hardware_devices) os_type = self._session._call_method(vim_util, "get_dynamic_property", vm_ref, "VirtualMachine", "summary.config.guestId") #Create a snapshot of the VM - LOG.debug(_("Creating Snapshot of the VM instance %s") % instance.name) + LOG.debug(_("Creating Snapshot of the VM instance %s ") % + instance.name) snapshot_task = self._session._call_method(self._session._get_vim(), "CreateSnapshot_Task", vm_ref, name="%s-snapshot" % instance.name, @@ -313,7 +318,8 @@ class VMWareVMOps(object): self._mkdir(vm_util.build_datastore_path(datastore_name, "vmware-tmp")) - copy_spec = vm_util.get_copy_virtual_disk_spec(adapter_type) + copy_spec = vm_util.get_copy_virtual_disk_spec(client_factory, + adapter_type) #Generate a random vmdk file name to which the coalesced vmdk content #will be copied to. A random name is chosen so that we don't have @@ -325,11 +331,11 @@ class VMWareVMOps(object): #Copy the contents of the disk ( or disks, if there were snapshots #done earlier) to a temporary vmdk file. - LOG.debug(_("Copying disk data before snapshot of " - "the VM instance %s") % instance.name) + LOG.debug(_("Copying disk data before snapshot of the VM instance %s") + % instance.name) copy_disk_task = self._session._call_method(self._session._get_vim(), "CopyVirtualDisk_Task", - self._session._get_vim().get_service_content().VirtualDiskManager, + self._session._get_vim().get_service_content().virtualDiskManager, sourceName=vmdk_file_path_before_snapshot, sourceDatacenter=dc_ref, destName=dest_vmdk_file_location, @@ -337,38 +343,39 @@ class VMWareVMOps(object): destSpec=copy_spec, force=False) self._session._wait_for_task(instance.id, copy_disk_task) - LOG.debug(_("Copied disk data before snapshot of " - "the VM instance %s") % instance.name) + LOG.debug(_("Copied disk data before snapshot of the VM instance %s") + % instance.name) + cookies = self._session._get_vim().client.options.transport.cookiejar #Upload the contents of -flat.vmdk file which has the disk data. LOG.debug(_("Uploading image %s") % snapshot_name) vmware_images.upload_image( - snapshot_name, - instance, - os_type=os_type, - adapter_type=adapter_type, - image_version=1, - host=self._session._host_ip, - data_center_name=self._get_datacenter_name_and_ref()[1], - datastore_name=datastore_name, - cookies=self._session._get_vim().proxy.binding.cookies, - file_path="vmware-tmp/%s-flat.vmdk" % random_name) + snapshot_name, + instance, + os_type=os_type, + adapter_type=adapter_type, + image_version=1, + host=self._session._host_ip, + data_center_name=self._get_datacenter_name_and_ref()[1], + datastore_name=datastore_name, + cookies=cookies, + file_path="vmware-tmp/%s-flat.vmdk" % random_name) LOG.debug(_("Uploaded image %s") % snapshot_name) #Delete the temporary vmdk created above. - LOG.debug(_("Deleting temporary vmdk file %s") % \ - dest_vmdk_file_location) + LOG.debug(_("Deleting temporary vmdk file %s") + % dest_vmdk_file_location) remove_disk_task = self._session._call_method(self._session._get_vim(), "DeleteVirtualDisk_Task", - self._session._get_vim().get_service_content().VirtualDiskManager, + self._session._get_vim().get_service_content().virtualDiskManager, name=dest_vmdk_file_location, datacenter=dc_ref) self._session._wait_for_task(instance.id, remove_disk_task) - LOG.debug(_("Deleted temporary vmdk file %s") % \ - dest_vmdk_file_location) + LOG.debug(_("Deleted temporary vmdk file %s") + % dest_vmdk_file_location) def reboot(self, instance): - """Reboot a VM instance""" + """ Reboot a VM instance """ vm_ref = self._get_vm_ref_from_the_name(instance.name) if vm_ref is None: raise Exception(_("instance - %s not present") % instance.name) @@ -379,11 +386,11 @@ class VMWareVMOps(object): for elem in props: pwr_state = None tools_status = None - for prop in elem.PropSet: - if prop.Name == "runtime.powerState": - pwr_state = prop.Val - elif prop.Name == "summary.guest.toolsStatus": - tools_status = prop.Val + for prop in elem.propSet: + if prop.name == "runtime.powerState": + pwr_state = prop.val + elif prop.name == "summary.guest.toolsStatus": + tools_status = prop.val #Raise an exception if the VM is not powered On. if pwr_state not in ["poweredOn"]: @@ -423,11 +430,11 @@ class VMWareVMOps(object): pwr_state = None for elem in props: vm_config_pathname = None - for prop in elem.PropSet: - if prop.Name == "runtime.powerState": - pwr_state = prop.Val - elif prop.Name == "config.files.vmPathName": - vm_config_pathname = prop.Val + for prop in elem.propSet: + if prop.name == "runtime.powerState": + pwr_state = prop.val + elif prop.name == "config.files.vmPathName": + vm_config_pathname = prop.val if vm_config_pathname: datastore_name, vmx_file_path = \ vm_util.split_datastore_path(vm_config_pathname) @@ -447,48 +454,49 @@ class VMWareVMOps(object): "UnregisterVM", vm_ref) LOG.debug(_("Unregistered the VM %s") % instance.name) except Exception, excep: - LOG.warn(_("In vmwareapi:vmops:destroy, got this exception " - "while un-registering the VM: ") + str(excep)) + LOG.warn(_("In vmwareapi:vmops:destroy, got this exception" + " while un-registering the VM: %s") % str(excep)) #Delete the folder holding the VM related content on the datastore. try: dir_ds_compliant_path = vm_util.build_datastore_path( datastore_name, os.path.dirname(vmx_file_path)) - LOG.debug(_("Deleting contents of the VM %(instance.name)s " - "from datastore %(datastore_name)s") % - {('instance.name': instance.name, - 'datastore_name': datastore_name)}) + LOG.debug(_("Deleting contents of the VM %(name)s from " + "datastore %(datastore_name)s") % + ({'name': instance.name, + 'datastore_name': datastore_name})) delete_task = self._session._call_method( self._session._get_vim(), "DeleteDatastoreFile_Task", - self._session._get_vim().get_service_content().FileManager, + self._session._get_vim().get_service_content().fileManager, name=dir_ds_compliant_path) self._session._wait_for_task(instance.id, delete_task) - LOG.debug(_("Deleted contents of the VM %(instance_name)s " - "from datastore %(datastore_name)s") % - {'instance_name': instance.name, - 'datastore_name': datastore_name}) + LOG.debug(_("Deleted contents of the VM %(name)s from " + "datastore %(datastore_name)s") + ({'name': instance.name, + 'datastore_name': datastore_name})) except Exception, excep: LOG.warn(_("In vmwareapi:vmops:destroy, " "got this exception while deleting" - " the VM contents from the disk: ") + str(excep)) + " the VM contents from the disk: %s") + % str(excep)) except Exception, e: LOG.exception(e) def pause(self, instance, callback): - """Pause a VM instance""" - return "Not Implemented" + """ Pause a VM instance """ + raise exception.APIError("pause not supported for vmwareapi") def unpause(self, instance, callback): - """Un-Pause a VM instance""" - return "Not Implemented" + """ Un-Pause a VM instance """ + raise exception.APIError("unpause not supported for vmwareapi") def suspend(self, instance, callback): - """Suspend the specified instance""" + """ Suspend the specified instance """ vm_ref = self._get_vm_ref_from_the_name(instance.name) if vm_ref is None: - raise Exception("instance - %s not present" % instance.name) + raise Exception(_("instance - %s not present") % instance.name) pwr_state = self._session._call_method(vim_util, "get_dynamic_property", vm_ref, @@ -505,10 +513,10 @@ class VMWareVMOps(object): raise Exception(_("instance - %s is poweredOff and hence can't " "be suspended.") % instance.name) LOG.debug(_("VM %s was already in suspended state. So returning " - "without doing anything") % instance.name) + "without doing anything") % instance.name) def resume(self, instance, callback): - """Resume the specified instance""" + """ Resume the specified instance """ vm_ref = self._get_vm_ref_from_the_name(instance.name) if vm_ref is None: raise Exception(_("instance - %s not present") % instance.name) @@ -517,7 +525,7 @@ class VMWareVMOps(object): "get_dynamic_property", vm_ref, "VirtualMachine", "runtime.powerState") if pwr_state.lower() == "suspended": - LOG.debug(_("Resuming the VM %s ") % instance.name) + LOG.debug(_("Resuming the VM %s") % instance.name) suspend_task = self._session._call_method( self._session._get_vim(), "PowerOnVM_Task", vm_ref) @@ -528,7 +536,7 @@ class VMWareVMOps(object): "can't be Resumed.") % instance.name) def get_info(self, instance_name): - """Return data about the VM instance""" + """ Return data about the VM instance """ vm_ref = self._get_vm_ref_from_the_name(instance_name) if vm_ref is None: raise Exception(_("instance - %s not present") % instance_name) @@ -543,14 +551,14 @@ class VMWareVMOps(object): pwr_state = None num_cpu = None for elem in vm_props: - for prop in elem.PropSet: - if prop.Name == "summary.config.numCpu": - num_cpu = int(prop.Val) - elif prop.Name == "summary.config..memorySizeMB": + for prop in elem.propSet: + if prop.name == "summary.config.numCpu": + num_cpu = int(prop.val) + elif prop.name == "summary.config.memorySizeMB": # In MB, but we want in KB - max_mem = int(prop.Val) * 1024 - elif prop.Name == "runtime.powerState": - pwr_state = VMWARE_POWER_STATES[prop.Val] + max_mem = int(prop.val) * 1024 + elif prop.name == "runtime.powerState": + pwr_state = VMWARE_POWER_STATES[prop.val] return {'state': pwr_state, 'max_mem': max_mem, @@ -559,22 +567,38 @@ class VMWareVMOps(object): 'cpu_time': 0} def get_diagnostics(self, instance): - """Return data about VM diagnostics""" - return "Not Implemented" + """ Return data about VM diagnostics """ + raise exception.APIError("get_diagnostics not implemented for " + "vmwareapi") def get_console_output(self, instance): - """Return snapshot of console""" - return 'FAKE CONSOLE OUTPUT of instance' + """ Return snapshot of console """ + vm_ref = self._get_vm_ref_from_the_name(instance.name) + if vm_ref is None: + raise Exception(_("instance - %s not present") % instance.name) + param_list = {"id": str(vm_ref)} + base_url = "%s://%s/screen?%s" % (self._session._scheme, + self._session._host_ip, + urllib.urlencode(param_list)) + request = urllib2.Request(base_url) + base64string = base64.encodestring( + '%s:%s' % ( + self._session._host_username, + self._session._host_password)).replace('\n', '') + request.add_header("Authorization", "Basic %s" % base64string) + result = urllib2.urlopen(request) + if result.code == 200: + return result.read() + else: + return "" def get_ajax_console(self, instance): - """Return link to instance's ajax console""" + """ Return link to instance's ajax console """ return 'http://fakeajaxconsole/fake_url' - def _set_machine_id(self, instance): - """ - Set the machine id of the VM for guest tools to pick up - and change the IP - """ + def _set_machine_id(self, client_factory, instance): + """ Set the machine id of the VM for guest tools to pick up and change + the IP """ vm_ref = self._get_vm_ref_from_the_name(instance.name) if vm_ref is None: raise Exception(_("instance - %s not present") % instance.name) @@ -585,95 +609,30 @@ class VMWareVMOps(object): gateway = network["gateway"] ip_addr = db.instance_get_fixed_address(context.get_admin_context(), instance['id']) - machine_id_chanfge_spec = vm_util.get_machine_id_change_spec(mac_addr, + machine_id_chanfge_spec = \ + vm_util.get_machine_id_change_spec(client_factory, mac_addr, ip_addr, net_mask, gateway) - LOG.debug(_("Reconfiguring VM instance %(instance_name)s to set " - "the machine id with ip - %(ip_addr)s") % - {'instance_name': instance.name, - 'ip_addr': ip_addr}) + LOG.debug(_("Reconfiguring VM instance %(name)s to set the machine id " + "with ip - %(ip_addr)s") % + ({'name': instance.name, + 'ip_addr': ip_addr})) reconfig_task = self._session._call_method(self._session._get_vim(), "ReconfigVM_Task", vm_ref, spec=machine_id_chanfge_spec) self._session._wait_for_task(instance.id, reconfig_task) - LOG.debug(_("Reconfigured VM instance %(instance_name)s to set " - "the machine id with ip - %(ip_addr)s") % - {'instance_name': instance.name, - 'ip_addr': ip_addr}) - - def _create_dummy_vm_for_test(self, instance): - """Create a dummy VM for testing purpose""" - vm_ref = self._get_vm_ref_from_the_name(instance.name) - if vm_ref: - raise Exception(_('Attempted to create a VM with a name %s, ' - 'but that already exists on the host') % instance.name) - - data_stores = self._session._call_method(vim_util, "get_objects", - "Datastore", ["summary.type", "summary.name"]) - data_store_name = None - for elem in data_stores: - ds_name = None - ds_type = None - for prop in elem.PropSet: - if prop.Name == "summary.type": - ds_type = prop.Val - elif prop.Name == "summary.name": - ds_name = prop.Val - #Local storage identifier - if ds_type == "VMFS": - data_store_name = ds_name - break - - if data_store_name is None: - msg = _("Couldn't get a local Datastore reference") - LOG.exception(msg) - raise Exception(msg) - - config_spec = vm_util.get_dummy_vm_create_spec(instance.name, - data_store_name) - - #Get the Vm folder ref from the datacenter - dc_objs = self._session._call_method(vim_util, "get_objects", - "Datacenter", ["vmFolder"]) - #There is only one default datacenter in a standalone ESX host - vm_folder_ref = dc_objs[0].PropSet[0].Val - - #Get the resource pool. Taking the first resource pool coming our way. - #Assuming that is the default resource pool. - res_pool_mor = self._session._call_method(vim_util, "get_objects", - "ResourcePool")[0].Obj - - #Create the VM on the ESX host - vm_create_task = self._session._call_method(self._session._get_vim(), - "CreateVM_Task", vm_folder_ref, - config=config_spec, pool=res_pool_mor) - self._session._wait_for_task(instance.id, vm_create_task) - - vm_ref = self._get_vm_ref_from_the_name(instance.name) - power_on_task = self._session._call_method(self._session._get_vim(), - "PowerOnVM_Task", vm_ref) - self._session._wait_for_task(instance.id, power_on_task) - - def _get_network_with_the_name(self, network_name="vmnet0"): - """Gets reference to network whose name is passed as the argument.""" - datacenters = self._session._call_method(vim_util, "get_objects", - "Datacenter", ["network"]) - vm_networks = datacenters[0].PropSet[0].Val.ManagedObjectReference - networks = self._session._call_method(vim_util, - "get_properites_for_a_collection_of_objects", - "Network", vm_networks, ["summary.name"]) - for network in networks: - if network.PropSet[0].Val == network_name: - return network.Obj - return None + LOG.debug(_("Reconfigured VM instance %(name)s to set the machine id " + "with ip - %(ip_addr)s") % + ({'name': instance.name, + 'ip_addr': ip_addr})) def _get_datacenter_name_and_ref(self): - """Get the datacenter name and the reference.""" + """ Get the datacenter name and the reference. """ dc_obj = self._session._call_method(vim_util, "get_objects", "Datacenter", ["name"]) - return dc_obj[0].Obj, dc_obj[0].PropSet[0].Val + return dc_obj[0].obj, dc_obj[0].propSet[0].val def _path_exists(self, ds_browser, ds_path): - """Check if the path exists on the datastore.""" + """Check if the path exists on the datastore""" search_task = self._session._call_method(self._session._get_vim(), "SearchDatastore_Task", ds_browser, @@ -684,31 +643,29 @@ class VMWareVMOps(object): task_info = self._session._call_method(vim_util, "get_dynamic_property", search_task, "Task", "info") - if task_info.State in ['queued', 'running']: + if task_info.state in ['queued', 'running']: time.sleep(2) continue break - if task_info.State == "error": + if task_info.state == "error": return False return True def _mkdir(self, ds_path): - """ - Creates a directory at the path specified. If it is just "NAME", then a - directory with this name is formed at the topmost level of the - DataStore. - """ + """ Creates a directory at the path specified. If it is just "NAME", + then a directory with this name is formed at the topmost level of the + DataStore. """ LOG.debug(_("Creating directory with path %s") % ds_path) self._session._call_method(self._session._get_vim(), "MakeDirectory", - self._session._get_vim().get_service_content().FileManager, + self._session._get_vim().get_service_content().fileManager, name=ds_path, createParentDirectories=False) LOG.debug(_("Created directory with path %s") % ds_path) def _get_vm_ref_from_the_name(self, vm_name): - """Get reference to the VM with the name specified.""" + """ Get reference to the VM with the name specified. """ vms = self._session._call_method(vim_util, "get_objects", "VirtualMachine", ["name"]) for vm in vms: - if vm.PropSet[0].Val == vm_name: - return vm.Obj + if vm.propSet[0].val == vm_name: + return vm.obj return None diff --git a/nova/virt/vmwareapi/vmware_images.py b/nova/virt/vmwareapi/vmware_images.py index 4aad657e6..f2a7c3b33 100644 --- a/nova/virt/vmwareapi/vmware_images.py +++ b/nova/virt/vmwareapi/vmware_images.py @@ -14,19 +14,16 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. - """ -Utility functions to handle vm images. Also include fake image handlers. - +Utility functions for Image transfer """ -import logging -import os import time from nova import flags -from nova.virt.vmwareapi import read_write_util +from nova import log as logging from nova.virt.vmwareapi import io_util +from nova.virt.vmwareapi import read_write_util FLAGS = flags.FLAGS @@ -35,11 +32,11 @@ READ_CHUNKSIZE = 2 * 1024 * 1024 WRITE_CHUNKSIZE = 2 * 1024 * 1024 LOG = logging.getLogger("nova.virt.vmwareapi.vmware_images") -TEST_IMAGE_PATH = "/tmp/vmware-test-images" def start_transfer(read_file_handle, write_file_handle, data_size): - """Start the data transfer from the read handle to the write handle.""" + """ Start the data transfer from the read handle to the write handle. """ + #The thread safe pipe thread_safe_pipe = io_util.ThreadSafePipe(QUEUE_BUFFER_SIZE) #The read thread @@ -73,7 +70,7 @@ def start_transfer(read_file_handle, write_file_handle, data_size): def fetch_image(image, instance, **kwargs): - """Fetch an image for attaching to the newly created VM.""" + """ Fetch an image for attaching to the newly created VM """ #Depending upon the image service, make appropriate image service call if FLAGS.image_service == "nova.image.glance.GlanceImageService": func = _get_glance_image @@ -81,8 +78,6 @@ def fetch_image(image, instance, **kwargs): func = _get_s3_image elif FLAGS.image_service == "nova.image.local.LocalImageService": func = _get_local_image - elif FLAGS.image_service == "nova.FakeImageService": - func = _get_fake_image else: raise NotImplementedError(_("The Image Service %s is not implemented") % FLAGS.image_service) @@ -90,7 +85,7 @@ def fetch_image(image, instance, **kwargs): def upload_image(image, instance, **kwargs): - """Upload the newly snapshotted VM disk file.""" + """ Upload the newly snapshotted VM disk file. """ #Depending upon the image service, make appropriate image service call if FLAGS.image_service == "nova.image.glance.GlanceImageService": func = _put_glance_image @@ -98,8 +93,6 @@ def upload_image(image, instance, **kwargs): func = _put_s3_image elif FLAGS.image_service == "nova.image.local.LocalImageService": func = _put_local_image - elif FLAGS.image_service == "nova.FakeImageService": - func = _put_fake_image else: raise NotImplementedError(_("The Image Service %s is not implemented") % FLAGS.image_service) @@ -107,7 +100,7 @@ def upload_image(image, instance, **kwargs): def _get_glance_image(image, instance, **kwargs): - """Download image from the glance image server.""" + """ Download image from the glance image server. """ LOG.debug(_("Downloading image %s from glance image server") % image) read_file_handle = read_write_util.GlanceHTTPReadFile(FLAGS.glance_host, FLAGS.glance_port, @@ -125,38 +118,17 @@ def _get_glance_image(image, instance, **kwargs): def _get_s3_image(image, instance, **kwargs): - """Download image from the S3 image server.""" + """ Download image from the S3 image server. """ raise NotImplementedError def _get_local_image(image, instance, **kwargs): - """Download image from the local nova compute node.""" + """ Download image from the local nova compute node. """ raise NotImplementedError -def _get_fake_image(image, instance, **kwargs): - """ Download a fake image from the nova local file repository for testing - purposes. - """ - LOG.debug(_("Downloading image %s from fake image service") % image) - image = str(image) - file_path = os.path.join(TEST_IMAGE_PATH, image, image) - file_path = os.path.abspath(file_path) - read_file_handle = read_write_util.FakeFileRead(file_path) - file_size = read_file_handle.get_size() - write_file_handle = read_write_util.VMWareHTTPWriteFile( - kwargs.get("host"), - kwargs.get("data_center_name"), - kwargs.get("datastore_name"), - kwargs.get("cookies"), - kwargs.get("file_path"), - file_size) - start_transfer(read_file_handle, write_file_handle, file_size) - LOG.debug(_("Downloaded image %s from fake image service") % image) - - def _put_glance_image(image, instance, **kwargs): - """Upload the snapshotted vm disk file to Glance image server.""" + """ Upload the snapshotted vm disk file to Glance image server """ LOG.debug(_("Uploading image %s to the Glance image server") % image) read_file_handle = read_write_util.VmWareHTTPReadFile( kwargs.get("host"), @@ -178,48 +150,20 @@ def _put_glance_image(image, instance, **kwargs): def _put_local_image(image, instance, **kwargs): - """Upload the snapshotted vm disk file to the local nova compute node.""" + """ Upload the snapshotted vm disk file to the local nova compute node. """ raise NotImplementedError def _put_s3_image(image, instance, **kwargs): - """Upload the snapshotted vm disk file to S3 image server.""" + """ Upload the snapshotted vm disk file to S3 image server. """ raise NotImplementedError -def _put_fake_image(image, instance, **kwargs): - """ Upload a dummy vmdk from the ESX host to the local file repository of - the nova node for testing purposes. - """ - LOG.debug(_("Uploading image %s to the Fake Image Service") % image) - read_file_handle = read_write_util.VmWareHTTPReadFile( - kwargs.get("host"), - kwargs.get("data_center_name"), - kwargs.get("datastore_name"), - kwargs.get("cookies"), - kwargs.get("file_path")) - file_size = read_file_handle.get_size() - image = str(image) - image_dir_path = os.path.join(TEST_IMAGE_PATH, image) - if not os.path.exists(TEST_IMAGE_PATH): - os.mkdir(TEST_IMAGE_PATH) - os.mkdir(image_dir_path) - else: - if not os.path.exists(image_dir_path): - os.mkdir(image_dir_path) - file_path = os.path.join(image_dir_path, image) - file_path = os.path.abspath(file_path) - write_file_handle = read_write_util.FakeFileWrite(file_path) - start_transfer(read_file_handle, write_file_handle, file_size) - LOG.debug(_("Uploaded image %s to the Fake Image Service") % image) - - def get_vmdk_size_and_properties(image, instance): - """ - Get size of the vmdk file that is to be downloaded for attach in spawn. + """ Get size of the vmdk file that is to be downloaded for attach in spawn. Need this to create the dummy virtual disk for the meta-data file. The - geometry of the disk created depends on the size. - """ + geometry of the disk created depends on the size.""" + LOG.debug(_("Getting image size for the image %s") % image) if FLAGS.image_service == "nova.image.glance.GlanceImageService": read_file_handle = read_write_util.GlanceHTTPReadFile( @@ -230,15 +174,9 @@ def get_vmdk_size_and_properties(image, instance): raise NotImplementedError elif FLAGS.image_service == "nova.image.local.LocalImageService": raise NotImplementedError - elif FLAGS.image_service == "nova.FakeImageService": - image = str(image) - file_path = os.path.join(TEST_IMAGE_PATH, image, image) - file_path = os.path.abspath(file_path) - read_file_handle = read_write_util.FakeFileRead(file_path) size = read_file_handle.get_size() properties = read_file_handle.get_image_properties() read_file_handle.close() LOG.debug(_("Got image size of %(size)s for the image %(image)s") % - {'size': size, - 'image': image}) + locals()) return size, properties diff --git a/nova/virt/vmwareapi_conn.py b/nova/virt/vmwareapi_conn.py index 29748fe81..bd3ab4320 100644 --- a/nova/virt/vmwareapi_conn.py +++ b/nova/virt/vmwareapi_conn.py @@ -16,33 +16,30 @@ # under the License. """ -Connection class for VMware Infrastructure API in VMware ESX/ESXi platform - -Encapsulates the session management activties and acts as interface for VI API. -The connection class sets up a session with the ESX/ESXi compute provider host -and handles all the calls made to the host. +A connection to the VMware ESX platform. **Related Flags** -:vmwareapi_host_ip: IP of VMware ESX/ESXi compute provider host -:vmwareapi_host_username: ESX/ESXi server user to be used for API session -:vmwareapi_host_password: Password for the user "vmwareapi_host_username" -:vmwareapi_task_poll_interval: The interval used for polling of remote tasks -:vmwareapi_api_retry_count: Max number of retry attempts upon API failures - +:vmwareapi_host_ip: IPAddress of VMware ESX server. +:vmwareapi_host_username: Username for connection to VMware ESX Server. +:vmwareapi_host_password: Password for connection to VMware ESX Server. +:vmwareapi_task_poll_interval: The interval (seconds) used for polling of + remote tasks + (default: 1.0). +:vmwareapi_api_retry_count: The API retry count in case of failure such as + network failures (socket errors etc.) + (default: 10). """ -import logging import time -import urlparse from eventlet import event from nova import context from nova import db from nova import flags +from nova import log as logging from nova import utils - from nova.virt.vmwareapi import vim from nova.virt.vmwareapi import vim_util from nova.virt.vmwareapi.vmops import VMWareVMOps @@ -71,45 +68,34 @@ flags.DEFINE_float('vmwareapi_api_retry_count', 'The number of times we retry on failures, ' 'e.g., socket error, etc.' 'Used only if connection_type is vmwareapi') +flags.DEFINE_string('vmwareapi_vlan_interface', + 'vmnic0', + 'Physical ethernet adapter name for vlan networking') TIME_BETWEEN_API_CALL_RETRIES = 2.0 -class TaskState: - """Enumeration class for different states of task - 0 - Task completed successfully - 1 - Task is in queued state - 2 - Task is in running state - """ - - TASK_SUCCESS = 0 - TASK_QUEUED = 1 - TASK_RUNNING = 2 - - class Failure(Exception): """Base Exception class for handling task failures""" def __init__(self, details): - """Initializer""" self.details = details def __str__(self): - """The informal string representation of the object""" return str(self.details) def get_connection(_): - """Sets up the ESX host connection""" + """Sets up the ESX host connection.""" host_ip = FLAGS.vmwareapi_host_ip host_username = FLAGS.vmwareapi_host_username host_password = FLAGS.vmwareapi_host_password api_retry_count = FLAGS.vmwareapi_api_retry_count - if not host_ip or host_username is None or host_password is None: - raise Exception('Must specify vmwareapi_host_ip,' - 'vmwareapi_host_username ' - 'and vmwareapi_host_password to use' - 'connection_type=vmwareapi') + if not host_ip or host_username is None or host_password is None: + raise Exception(_("Must specify vmwareapi_host_ip," + "vmwareapi_host_username " + "and vmwareapi_host_password to use" + "connection_type=vmwareapi")) return VMWareESXConnection(host_ip, host_username, host_password, api_retry_count) @@ -119,7 +105,6 @@ class VMWareESXConnection(object): def __init__(self, host_ip, host_username, host_password, api_retry_count, scheme="https"): - """The Initializer""" session = VMWareAPISession(host_ip, host_username, host_password, api_retry_count, scheme=scheme) self._vmops = VMWareVMOps(session) @@ -191,22 +176,18 @@ class VMWareESXConnection(object): def get_console_pool_info(self, console_type): """Get info about the host on which the VM resides""" - esx_url = urlparse.urlparse(FLAGS.vmwareapi_host_ip) - return {'address': esx_url.netloc, - 'username': FLAGS.vmwareapi_host_password, - 'password': FLAGS.vmwareapi_host_password} - - def _create_dummy_vm_for_test(self, instance): - """Creates a dummy VM with default parameters for testing purpose""" - return self._vmops._create_dummy_vm_for_test(instance) + return {'address': FLAGS.vmwareapi_host_ip, + 'username': FLAGS.vmwareapi_host_username, + 'password': FLAGS.vmwareapi_host_password} class VMWareAPISession(object): - """Sets up a session with ESX host and handles all calls made to host""" + """Sets up a session with the ESX host and handles all + the calls made to the host + """ def __init__(self, host_ip, host_username, host_password, api_retry_count, scheme="https"): - """Set the connection credentials""" self._host_ip = host_ip self._host_username = host_username self._host_password = host_password @@ -216,15 +197,19 @@ class VMWareAPISession(object): self.vim = None self._create_session() + def _get_vim_object(self): + """Create the VIM Object instance""" + return vim.Vim(protocol=self._scheme, host=self._host_ip) + def _create_session(self): """Creates a session with the ESX host""" while True: try: # Login and setup the session with the ESX host for making # API calls - self.vim = vim.Vim(protocol=self._scheme, host=self._host_ip) + self.vim = self._get_vim_object() session = self.vim.Login( - self.vim.get_service_content().SessionManager, + self.vim.get_service_content().sessionManager, userName=self._host_username, password=self._host_password) # Terminate the earlier session, if possible ( For the sake of @@ -233,34 +218,40 @@ class VMWareAPISession(object): if self._session_id: try: self.vim.TerminateSession( - self.vim.get_service_content().SessionManager, + self.vim.get_service_content().sessionManager, sessionId=[self._session_id]) except Exception, excep: LOG.exception(excep) - self._session_id = session.Key + self._session_id = session.key return except Exception, excep: - LOG.info(_("In vmwareapi:_create_session, " + LOG.critical(_("In vmwareapi:_create_session, " "got this exception: %s") % excep) raise Exception(excep) def __del__(self): - """The Destructor. Logs-out the session.""" + """Logs-out the session.""" # Logout to avoid un-necessary increase in session count at the # ESX host try: - self.vim.Logout(self.vim.get_service_content().SessionManager) + self.vim.Logout(self.vim.get_service_content().sessionManager) except Exception: pass + def _is_vim_object(self, module): + """Check if the module is a VIM Object instance""" + return isinstance(module, vim.Vim) + def _call_method(self, module, method, *args, **kwargs): - """Calls a method within the module specified with args provided""" + """Calls a method within the module specified with + args provided + """ args = list(args) retry_count = 0 exc = None while True: try: - if not isinstance(module, vim.Vim): + if not self._is_vim_object(module): #If it is not the first try, then get the latest vim object if retry_count > 0: args = args[1:] @@ -295,8 +286,8 @@ class VMWareAPISession(object): break time.sleep(TIME_BETWEEN_API_CALL_RETRIES) - LOG.info(_("In vmwareapi:_call_method, " - "got this exception: ") % exc) + LOG.critical(_("In vmwareapi:_call_method, " + "got this exception: %s") % exc) raise Exception(exc) def _get_vim(self): @@ -306,8 +297,7 @@ class VMWareAPISession(object): return self.vim def _wait_for_task(self, instance_id, task_ref): - """ - Return a Deferred that will give the result of the given task. + """Return a Deferred that will give the result of the given task. The task is polled until it completes. """ done = event.Event() @@ -319,36 +309,30 @@ class VMWareAPISession(object): return ret_val def _poll_task(self, instance_id, task_ref, done): - """ - Poll the given task, and fires the given Deferred if we + """Poll the given task, and fires the given Deferred if we get a result. """ try: task_info = self._call_method(vim_util, "get_dynamic_property", task_ref, "Task", "info") - task_name = task_info.Name + task_name = task_info.name action = dict( instance_id=int(instance_id), action=task_name[0:255], error=None) - if task_info.State in [TaskState.TASK_QUEUED, - TaskState.TASK_RUNNING]: + if task_info.state in ['queued', 'running']: return - elif task_info.State == TaskState.TASK_SUCCESS: - LOG.info(_("Task [%(taskname)s] %(taskref)s status: success") % - {'taskname': task_name, - 'taskref': str(task_ref)}) - done.send(TaskState.TASK_SUCCESS) + elif task_info.state == 'success': + LOG.info(_("Task [%(task_name)s] %(task_ref)s " + "status: success") % locals()) + done.send("success") else: - error_info = str(task_info.Error.LocalizedMessage) + error_info = str(task_info.error.localizedMessage) action["error"] = error_info - LOG.info(_("Task [%(task_name)s] %(task_ref)s status: " - "error [%(error_info)s]") % - {'task_name': task_name, - 'task_ref': str(task_ref), - 'error_info': error_info}) + LOG.warn(_("Task [%(task_name)s] %(task_ref)s " + "status: error %(error_info)s") % locals()) done.send_exception(Exception(error_info)) db.instance_action_create(context.get_admin_context(), action) except Exception, excep: - LOG.info(_("In vmwareapi:_poll_task, Got this error %s") % excep) + LOG.warn(_("In vmwareapi:_poll_task, Got this error %s") % excep) done.send_exception(excep) -- cgit