From 6d08c7eeacf00874c92026b504f47d370b051848 Mon Sep 17 00:00:00 2001 From: gentoo90 Date: Sat, 24 May 2014 11:23:04 +0300 Subject: [PATCH 01/11] Add Kerberos login --- pysphere/vi_server.py | 89 ++++++++++++++++++++++++++++++++++++------- 1 file changed, 76 insertions(+), 13 deletions(-) diff --git a/pysphere/vi_server.py b/pysphere/vi_server.py index a9185c7..ea0137e 100644 --- a/pysphere/vi_server.py +++ b/pysphere/vi_server.py @@ -28,6 +28,10 @@ #-- import sys +import os +from base64 import b64encode, b64decode +from urlparse import urlparse +from socket import gethostbyaddr from pysphere.resources import VimService_services as VI @@ -50,18 +54,24 @@ def __init__(self): #By default impersonate the VI Client to be accepted by Virtual Server self.__initial_headers = {"User-Agent":"VMware VI Client/5.0.0"} - def connect(self, host, user, password, trace_file=None, sock_timeout=None): + def connect(self, host, user=None, password=None, passthrough=False, trace_file=None, sock_timeout=None): """Opens a session to a VC/ESX server with the given credentials: @host: is the server's hostname or address. If the web service uses another protocol or port than the default, you must use the full service URL (e.g. http://myhost:8888/sdk) - @user: username to connect with - @password: password to authenticate the session + @user: (optional) username to connect with + @password: (optional) password to authenticate the session + @passthrough: (optional) use Windows session credentials + or MIT Kerberos credentials to connect. + User should provide user/password pair OR set passthrough to True @trace_file: (optional) a file path to log SOAP requests and responses @sock_timeout: (optional) only for python >= 2.6, sets the connection timeout for sockets, in python 2.5 you'll have to use socket.setdefaulttimeout(secs) to change the global setting. """ + if (((user is None or password is None) and not passthrough) + or ((user is not None or password is not None) and passthrough)): + raise TypeError("connect() takes user/password pair OR passthrough=True") self.__user = user self.__password = password @@ -103,16 +113,69 @@ def connect(self, host, user, password, trace_file=None, sock_timeout=None): self.__api_version = self._do_service_content.About.ApiVersion self.__api_type = self._do_service_content.About.ApiType - #login - request = VI.LoginRequestMsg() - mor_session_manager = request.new__this( - self._do_service_content.SessionManager) - mor_session_manager.set_attribute_type(MORTypes.SessionManager) - request.set_element__this(mor_session_manager) - request.set_element_userName(user) - request.set_element_password(password) - self.__session = self._proxy.Login(request)._returnval - self.__logged = True + if not passthrough: + #login with user/password + request = VI.LoginRequestMsg() + mor_session_manager = request.new__this( + self._do_service_content.SessionManager) + mor_session_manager.set_attribute_type(MORTypes.SessionManager) + request.set_element__this(mor_session_manager) + request.set_element_userName(user) + request.set_element_password(password) + self.__session = self._proxy.Login(request)._returnval + self.__logged = True + + else: + fqdn, aliases, addrs = gethostbyaddr(urlparse(server_url).netloc) + if os.name == 'nt': + #login with Windows session credentials + try: + from sspi import ClientAuth + except ImportError: + raise ImportError("To enable passthrough authentication please"\ + " install pywin32 (available for Windows only)") + spn = "host/%s" % fqdn + client = ClientAuth("Kerberos", targetspn=spn) + + def get_token(serverToken=None): + if serverToken is not None: + serverToken = b64decode(serverToken) + err, bufs = client.authorize(serverToken) + return b64encode(bufs[0].Buffer) + else: + #login with MIT Kerberos credentials + try: + import kerberos + except ImportError: + raise ImportError("To enable passthrough authentication please"\ + " install python bindings for kerberos") + spn = "host@%s" % fqdn + flags = kerberos.GSS_C_INTEG_FLAG|kerberos.GSS_C_SEQUENCE_FLAG|\ + kerberos.GSS_C_REPLAY_FLAG|kerberos.GSS_C_CONF_FLAG + errc, client = kerberos.authGSSClientInit(spn, gssflags=flags) + + def get_token(serverToken=''): + cres = kerberos.authGSSClientStep(client, serverToken) + return kerberos.authGSSClientResponse(client) + + token = get_token() + + while not self.__logged: + try: + request = VI.LoginBySSPIRequestMsg() + mor_session_manager = request.new__this( + self._do_service_content.SessionManager) + mor_session_manager.set_attribute_type(MORTypes.SessionManager) + request.set_element__this(mor_session_manager) + request.set_element_base64Token(token) + self.__session = self._proxy.LoginBySSPI(request)._returnval + self.__logged = True + except (VI.ZSI.FaultException), e: + if e.fault.string == "fault.SSPIChallenge.summary": + serverToken = e.fault.detail[0].Base64Token + token = get_token(serverToken) + else: + raise e except (VI.ZSI.FaultException), e: raise VIApiException(e) From bead920f2a5562d0785ca5043f540d1faac6ebb9 Mon Sep 17 00:00:00 2001 From: gentoo90 Date: Sat, 24 May 2014 11:28:05 +0300 Subject: [PATCH 02/11] Add snapshots renaming --- pysphere/vi_virtual_machine.py | 79 ++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/pysphere/vi_virtual_machine.py b/pysphere/vi_virtual_machine.py index 8db4532..ef65168 100644 --- a/pysphere/vi_virtual_machine.py +++ b/pysphere/vi_virtual_machine.py @@ -894,6 +894,56 @@ def create_snapshot(self, name, sync_run=True, description=None, except (VI.ZSI.FaultException), e: raise VIApiException(e) + def rename_current_snapshot(self, new_name=None, new_description=None): + """Renames the current snapshot. + If @new_name is None, snapshot name remains unchanged. + If @new_description is None snapshot description remains unchanged.""" + self.refresh_snapshot_list() + if not self.__current_snapshot: + raise VIException("There is no current snapshot", + FaultTypes.OBJECT_NOT_FOUND) + + target_snap = None + for snap in self._snapshot_list: + if snap._mor == self.__current_snapshot: + target_snap = snap + break + + return self.__rename_snapshot(target_snap, new_name, new_description) + + def rename_named_snapshot(self, name, new_name=None, new_description=None): + """Renames the first snapshot found in this VM named after @name. + If @new_name is None, snapshot name remains unchanged. + If @new_description is None snapshot description remains unchanged.""" + self.refresh_snapshot_list() + target_snap = None + for snap in self._snapshot_list: + if snap._name == name: + target_snap = snap + break + if target_snap is None: + raise VIException("Could not find snapshot '%s'" % name, + FaultTypes.OBJECT_NOT_FOUND) + + return self.__rename_snapshot(target_snap, new_name, new_description) + + def rename_snapshot_by_path(self, path, index=0, new_name=None, new_description=None): + """Renames the VM snapshot of the given path and index (to disambiguate + among snapshots with the same path, default 0). + If @new_name is None, snapshot name remains unchanged. + If @new_description is None snapshot description remains unchanged.""" + + target_snap = None + for snap in self._snapshot_list: + if snap.get_path() == path and snap._index == index: + target_snap = snap + break + if not target_snap: + raise VIException("Couldn't find snapshot with path '%s' (index %d)" + % (path, index), FaultTypes.OBJECT_NOT_FOUND) + + self.__rename_snapshot(target_snap, new_name, new_description) + def delete_current_snapshot(self, remove_children=False, sync_run=True): """Removes the current snapshot. If @remove_children is True, removes all the snapshots in the subtree as well. If @sync_run is True (default) @@ -1676,6 +1726,35 @@ def __create_pendant_task_collector(self): except (VI.ZSI.FaultException), e: raise VIApiException(e) + def __rename_snapshot(self, snap, new_name=None, new_description=None): + """Renames the given snapshot. + If @new_name is None, snapshot name remains unchanged. + If @new_description is None snapshot description remains unchanged.""" + + if new_name == '': + raise VIException("Snapshot name must not be empty string.", + FaultTypes.PARAMETER_ERROR) + + if new_name is None and new_description is None: + raise VIException("Didn't provide name nor description. Nothing to do.", + FaultTypes.PARAMETER_ERROR) + elif new_name is None: + new_name = snap._name + elif new_description is None: + new_description = snap._description + + try: + request = VI.RenameSnapshotRequestMsg() + mor_snap = request.new__this(snap._mor) + mor_snap.set_attribute_type(snap._mor.get_attribute_type()) + request.set_element__this(mor_snap) + request.set_element_name(new_name) + request.set_element_description(new_description) + self._server._proxy.RenameSnapshot(request) + self.refresh_snapshot_list() + except (VI.ZSI.FaultException), e: + raise VIApiException(e) + def __delete_snapshot(self, mor, remove_children, sync_run): """Deletes the snapshot of the given MOR. If remove_children is True, deletes all the snapshots in the subtree as well. If @sync_run is True From 1f302ecd67f5823ba04c60325dc4ae1540d93c97 Mon Sep 17 00:00:00 2001 From: gentoo90 Date: Sat, 24 May 2014 22:52:30 +0300 Subject: [PATCH 03/11] Add unregister method to VIVirtualMachine class --- pysphere/vi_virtual_machine.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pysphere/vi_virtual_machine.py b/pysphere/vi_virtual_machine.py index ef65168..02573d7 100644 --- a/pysphere/vi_virtual_machine.py +++ b/pysphere/vi_virtual_machine.py @@ -1659,6 +1659,16 @@ def set_extra_config(self, settings, sync_run=True): except (VI.ZSI.FaultException), e: raise VIApiException(e) + def unregister(self): + try: + request = VI.UnregisterVMRequestMsg() + _this = request.new__this(self._mor) + _this.set_attribute_type(self._mor.get_attribute_type()) + request.set_element__this(_this) + self._server._proxy.UnregisterVM(request) + except (VI.ZSI.FaultException), e: + raise VIApiException(e) + #---------------------# #-- PRIVATE METHODS --# #---------------------# From 9d7bbf3b518280b3968d0c48c37a58678d2e4260 Mon Sep 17 00:00:00 2001 From: gentoo90 Date: Sun, 25 May 2014 23:50:27 +0300 Subject: [PATCH 04/11] Add VIFileManager --- pysphere/vi_file_manager.py | 183 ++++++++++++++++++++++++++++++++++++ pysphere/vi_server.py | 6 ++ 2 files changed, 189 insertions(+) create mode 100644 pysphere/vi_file_manager.py diff --git a/pysphere/vi_file_manager.py b/pysphere/vi_file_manager.py new file mode 100644 index 0000000..24258ad --- /dev/null +++ b/pysphere/vi_file_manager.py @@ -0,0 +1,183 @@ +#-- +# Copyright (c) 2014, Sebastian Tello +# All rights reserved. + +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of copyright holders nor the names of its contributors +# may be used to endorse or promote products derived from this software +# without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +#-- + +import urllib, urllib2 +import sys + +from pysphere.resources import VimService_services as VI +from pysphere import VIProperty, VIMor, MORTypes +from pysphere.vi_task import VITask +from pysphere.resources.vi_exception import VIException, VIApiException, \ + FaultTypes +from pysphere.vi_snapshot import VISnapshot +from pysphere.vi_managed_entity import VIManagedEntity + + +class VIFileManager: + + def __init__(self, server, mor): + self._server = server + self._mor = mor + self._properties = VIProperty(server, mor) + self.relogin() + + def relogin(self): + self._handler = self._build_auth_handler() + + def list_files(self, datastore, path, case_insensitive=True, + folders_first=True, match_patterns=[]): + """Return a list of files inside folder @path on @datastore + """ + + ds = [k for k,v in self._server.get_datastores().items() if v == datastore][0] + browser_mor = VIProperty(self._server, ds).browser._obj + + request = VI.SearchDatastore_TaskRequestMsg() + _this = request.new__this(browser_mor) + _this.set_attribute_type(browser_mor.get_attribute_type()) + request.set_element__this(_this) + request.set_element_datastorePath("[%s] %s" % (datastore, path)) + + search_spec = request.new_searchSpec() + + query = [VI.ns0.FloppyImageFileQuery_Def('floppy').pyclass(), + VI.ns0.FileQuery_Def('file').pyclass(), + VI.ns0.FolderFileQuery_Def('folder').pyclass(), + VI.ns0.IsoImageFileQuery_Def('iso').pyclass(), + VI.ns0.VmConfigFileQuery_Def('vm').pyclass(), + VI.ns0.TemplateConfigFileQuery_Def('template').pyclass(), + VI.ns0.VmDiskFileQuery_Def('vm_disk').pyclass(), + VI.ns0.VmLogFileQuery_Def('vm_log').pyclass(), + VI.ns0.VmNvramFileQuery_Def('vm_ram').pyclass(), + VI.ns0.VmSnapshotFileQuery_Def('vm_snapshot').pyclass()] + search_spec.set_element_query(query) + details = search_spec.new_details() + details.set_element_fileOwner(True) + details.set_element_fileSize(True) + details.set_element_fileType(True) + details.set_element_modification(True) + search_spec.set_element_details(details) + search_spec.set_element_searchCaseInsensitive(case_insensitive) + search_spec.set_element_sortFoldersFirst(folders_first) + search_spec.set_element_matchPattern(match_patterns) + request.set_element_searchSpec(search_spec) + response = self._server._proxy.SearchDatastore_Task(request)._returnval + task = VITask(response, self._server) + if task.wait_for_state([task.STATE_ERROR, task.STATE_SUCCESS]) == task.STATE_ERROR: + raise Exception(task.get_error_message()) + + info = task.get_result() + # return info + + if not hasattr(info, "file"): + return [] + # for fi in info.file: + # fi._get_all() + return [{'type':fi._type, + 'path':fi.path, + 'size':fi.fileSize, + 'modified':fi.modification, + 'owner':fi.owner + } for fi in info.file] + + def make_directory(self, datastore, path, create_parent=False): + """Creates new directory on @datastore with given @path + """ + try: + request = VI.MakeDirectoryRequestMsg() + _this = request.new__this(self._fileManager) + _this.set_attribute_type(self._fileManager.get_attribute_type()) + request.set_element__this(_this) + request.set_element_name("[%s] %s" % (datastore, path)) + request.set_element_createParentDirectories(create_parent) + self._server._proxy.MakeDirectory(request) + except (VI.ZSI.FaultException), e: + raise VIApiException(e) + + def upload(self, datastore, local_file_path, remote_file_path): + """Uploads @local_file_path to @remote_file_path on @datastore + replacing existing file. Returns True if @remote_file_path was replaced + and False otherwise. + """ + fd = open(local_file_path, "r") + data = fd.read() + fd.close() + resource = "/folder/%s" % remote_file_path.lstrip("/") + url = self._get_url(datastore, resource) + resp = self._do_request(url, data) + return resp.code == 200 + + def download(self, datastore, remote_file_path, local_file_path): + """Downloads @remote_file_path from @datastore to @local_file_path + replacing existing file. + """ + resource = "/folder/%s" % remote_file_path.lstrip("/") + url = self._get_url(datastore, resource) + + if sys.version_info >= (2, 6): + resp = self._do_request(url) + CHUNK = 16 * 1024 + fd = open(local_file_path, "wb") + while True: + chunk = resp.read(CHUNK) + if not chunk: break + fd.write(chunk) + fd.close() + else: + urllib.urlretrieve(url, local_file_path) + + def _do_request(self, url, data=None): + opener = urllib2.build_opener(self._handler) + request = urllib2.Request(url, data=data) + if data: + request.get_method = lambda: 'PUT' + return opener.open(request) + + def _get_url(self, datastore, resource, datacenter=None): + if not resource.startswith("/"): + resource = "/" + resource + + params = {"dsName":datastore} + if datacenter: + params["dcPath": datacenter] + params = urllib.urlencode(params) + + return "%s%s?%s" % (self._get_service_url(), resource, params) + + def _get_service_url(self): + service_url = self._server._proxy.binding.url + return service_url[:service_url.rindex("/sdk")] + + def _build_auth_handler(self): + service_url = self._get_service_url() + user = self._server._VIServer__user + password = self._server._VIServer__password + auth_manager = urllib2.HTTPPasswordMgrWithDefaultRealm() + auth_manager.add_password(None, service_url, user, password) + return urllib2.HTTPBasicAuthHandler(auth_manager) diff --git a/pysphere/vi_server.py b/pysphere/vi_server.py index ea0137e..afcbb2a 100644 --- a/pysphere/vi_server.py +++ b/pysphere/vi_server.py @@ -37,8 +37,10 @@ from pysphere import VIException, VIApiException, FaultTypes from pysphere.vi_virtual_machine import VIVirtualMachine +from pysphere.vi_file_manager import VIFileManager from pysphere.vi_performance_manager import PerformanceManager from pysphere.vi_task_history_collector import VITaskHistoryCollector +from pysphere.vi_property import VIProperty from pysphere.vi_mor import VIMor, MORTypes class VIServer: @@ -214,6 +216,10 @@ def disconnect(self): except (VI.ZSI.FaultException), e: raise VIApiException(e) + def get_file_manager(self): + """Returns a File Manager entity""" + return VIFileManager(self, self._do_service_content.FileManager) + def get_performance_manager(self): """Returns a Performance Manager entity""" return PerformanceManager(self, self._do_service_content.PerfManager) From ef939e3bc1469a87aa807cecc3788d693c7478ed Mon Sep 17 00:00:00 2001 From: gentoo90 Date: Mon, 26 May 2014 14:36:59 +0300 Subject: [PATCH 05/11] Add register_vm method to VIServer class --- pysphere/vi_server.py | 62 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/pysphere/vi_server.py b/pysphere/vi_server.py index afcbb2a..d36f0ab 100644 --- a/pysphere/vi_server.py +++ b/pysphere/vi_server.py @@ -42,6 +42,7 @@ from pysphere.vi_task_history_collector import VITaskHistoryCollector from pysphere.vi_property import VIProperty from pysphere.vi_mor import VIMor, MORTypes +from pysphere.vi_task import VITask class VIServer: @@ -539,6 +540,67 @@ def acquire_clone_ticket(self): except (VI.ZSI.FaultException), e: raise VIApiException(e) + def register_vm(self, path, name=None, sync_run=True, folder=None, + template=False, resourcepool=None, host=None): + """Adds an existing virtual machine to the folder. + @path: a datastore path to the virtual machine. + Example "[datastore] path/to/machine.vmx". + @name: the name to be assigned to the virtual machine. + If this parameter is not set, the displayName configuration + parameter of the virtual machine is used. + @sync_run: if True (default) waits for the task to finish, and returns + a VIVirtualMachine instance with the new VM (raises an exception if + the task didn't succeed). If @sync_run is set to False the task is + started and a VITask instance is returned + @folder_name: folder in which to register the virtual machine. + @template: Flag to specify whether or not the virtual machine + should be marked as a template. + @resourcepool: MOR of the resource pool to which the virtual machine should + be attached. If imported as a template, this parameter is not set. + @host: The target host on which the virtual machine will run. This + parameter must specify a host that is a member of the ComputeResource + indirectly specified by the pool. For a stand-alone host or a cluster + with DRS, the parameter can be omitted, and the system selects a default. + """ + if not folder: + folders = self._get_managed_objects(MORTypes.Folder) + folder = [_mor for _mor, _name in folders.iteritems() + if _name == 'vm'][0] + try: + request = VI.RegisterVM_TaskRequestMsg() + _this = request.new__this(folder) + _this.set_attribute_type(folder.get_attribute_type()) + request.set_element__this(_this) + request.set_element_path(path) + if name: + request.set_element_name(name) + request.set_element_asTemplate(template) + if resourcepool: + pool = request.new_pool(resourcepool) + pool.set_attribute_type(resourcepool.get_attribute_type()) + request.set_element_pool(pool) + if host: + if not VIMor.is_mor(host): + host = VIMor(host, MORTypes.HostSystem) + hs = request.new_host(host) + hs.set_attribute_type(host.get_attribute_type()) + request.set_element_host(hs) + + task = self._proxy.RegisterVM_Task(request)._returnval + vi_task = VITask(task, self) + if sync_run: + status = vi_task.wait_for_state([vi_task.STATE_SUCCESS, + vi_task.STATE_ERROR]) + if status == vi_task.STATE_ERROR: + raise VIException(vi_task.get_error_message(), + FaultTypes.TASK_ERROR) + return + + return vi_task + + except (VI.ZSI.FaultException), e: + raise VIApiException(e) + def _get_object_properties(self, mor, property_names=[], get_all=False): """Returns the properties defined in property_names (or all if get_all is set to True) of the managed object reference given in @mor. From 0703d0ccde3542a872a7b01e1e2d4ba77ff734da Mon Sep 17 00:00:00 2001 From: gentoo90 Date: Sat, 31 May 2014 16:32:05 +0300 Subject: [PATCH 06/11] VIFileManager changes * 'datastore' is now a part of 'path' parameter ('[datastore] /path/to/file.vmx') * add 'delete_file' method * 'upload'/'download' methods now use cookies of main HTTP session for authentication * some fixes and code cleaning --- pysphere/vi_file_manager.py | 121 ++++++++++++++++++++++-------------- 1 file changed, 76 insertions(+), 45 deletions(-) diff --git a/pysphere/vi_file_manager.py b/pysphere/vi_file_manager.py index 24258ad..03fb535 100644 --- a/pysphere/vi_file_manager.py +++ b/pysphere/vi_file_manager.py @@ -29,6 +29,7 @@ import urllib, urllib2 import sys +import re from pysphere.resources import VimService_services as VI from pysphere import VIProperty, VIMor, MORTypes @@ -45,24 +46,22 @@ def __init__(self, server, mor): self._server = server self._mor = mor self._properties = VIProperty(server, mor) - self.relogin() + self._re_path = re.compile(r'\[(.*?)\] (.*)') - def relogin(self): - self._handler = self._build_auth_handler() - - def list_files(self, datastore, path, case_insensitive=True, + def list_files(self, path, case_insensitive=True, folders_first=True, match_patterns=[]): - """Return a list of files inside folder @path on @datastore + """Return a list of files in folder @path """ - ds = [k for k,v in self._server.get_datastores().items() if v == datastore][0] + ds_name, file_name = re.match(self._re_path, path).groups() + ds = [k for k,v in self._server.get_datastores().items() if v == ds_name][0] browser_mor = VIProperty(self._server, ds).browser._obj request = VI.SearchDatastore_TaskRequestMsg() _this = request.new__this(browser_mor) _this.set_attribute_type(browser_mor.get_attribute_type()) request.set_element__this(_this) - request.set_element_datastorePath("[%s] %s" % (datastore, path)) + request.set_element_datastorePath(path) search_spec = request.new_searchSpec() @@ -88,11 +87,11 @@ def list_files(self, datastore, path, case_insensitive=True, search_spec.set_element_matchPattern(match_patterns) request.set_element_searchSpec(search_spec) response = self._server._proxy.SearchDatastore_Task(request)._returnval - task = VITask(response, self._server) - if task.wait_for_state([task.STATE_ERROR, task.STATE_SUCCESS]) == task.STATE_ERROR: - raise Exception(task.get_error_message()) - - info = task.get_result() + vi_task = VITask(response, self._server) + if vi_task.wait_for_state([vi_task.STATE_ERROR, vi_task.STATE_SUCCESS]) == vi_task.STATE_ERROR: + raise VIException(vi_task.get_error_message(), + FaultTypes.TASK_ERROR) + info = vi_task.get_result() # return info if not hasattr(info, "file"): @@ -106,54 +105,93 @@ def list_files(self, datastore, path, case_insensitive=True, 'owner':fi.owner } for fi in info.file] - def make_directory(self, datastore, path, create_parent=False): - """Creates new directory on @datastore with given @path + def make_directory(self, path, create_parent=False): + """Creates new directory with given @path on datastore """ try: request = VI.MakeDirectoryRequestMsg() - _this = request.new__this(self._fileManager) - _this.set_attribute_type(self._fileManager.get_attribute_type()) + _this = request.new__this(self._mor) + _this.set_attribute_type(self._mor.get_attribute_type()) request.set_element__this(_this) - request.set_element_name("[%s] %s" % (datastore, path)) + request.set_element_name(path) request.set_element_createParentDirectories(create_parent) self._server._proxy.MakeDirectory(request) except (VI.ZSI.FaultException), e: raise VIApiException(e) - def upload(self, datastore, local_file_path, remote_file_path): - """Uploads @local_file_path to @remote_file_path on @datastore - replacing existing file. Returns True if @remote_file_path was replaced - and False otherwise. + def delete_file(self, path, datacenter=None, sync_run=True): + """Deletes the specified file or folder from the datastore. + If a file of a virtual machine is deleted, it may corrupt + that virtual machine. Folder deletes are always recursive. """ + try: + request = VI.DeleteDatastoreFile_TaskRequestMsg() + _this = request.new__this(self._mor) + _this.set_attribute_type(self._mor.get_attribute_type()) + request.set_element__this(_this) + request.set_element_name(path) + if datacenter: + request.set_element_datacenter(datacenter) + + task = self._server._proxy.DeleteDatastoreFile_Task(request)._returnval + vi_task = VITask(task, self._server) + if sync_run: + status = vi_task.wait_for_state([vi_task.STATE_SUCCESS, + vi_task.STATE_ERROR]) + if status == vi_task.STATE_ERROR: + raise VIException(vi_task.get_error_message(), + FaultTypes.TASK_ERROR) + return + + return vi_task + + except (VI.ZSI.FaultException), e: + raise VIApiException(e) + + def upload(self, local_file_path, remote_file_path): + """Uploads @local_file_path to @remote_file_path on datastore + replacing existing file. Returns True if @remote_file_path was replaced, + otherwise False. + """ + ds_name, file_name = re.match(self._re_path, remote_file_path).groups() fd = open(local_file_path, "r") data = fd.read() fd.close() - resource = "/folder/%s" % remote_file_path.lstrip("/") - url = self._get_url(datastore, resource) + resource = "/folder/%s" % file_name.lstrip("/") + url = self._get_url(ds_name, resource) resp = self._do_request(url, data) return resp.code == 200 - def download(self, datastore, remote_file_path, local_file_path): - """Downloads @remote_file_path from @datastore to @local_file_path + def download(self, remote_file_path, local_file_path): + """Downloads @remote_file_path from datastore to @local_file_path replacing existing file. """ - resource = "/folder/%s" % remote_file_path.lstrip("/") - url = self._get_url(datastore, resource) - - if sys.version_info >= (2, 6): - resp = self._do_request(url) - CHUNK = 16 * 1024 - fd = open(local_file_path, "wb") + ds_name, file_name = re.match(self._re_path, remote_file_path).groups() + resource = "/folder/%s" % file_name.lstrip("/") + url = self._get_url(ds_name, resource) + resp = self._do_request(url) + CHUNK = 16 * 1024 + with open(local_file_path, "wb") as fd: while True: chunk = resp.read(CHUNK) if not chunk: break fd.write(chunk) - fd.close() - else: - urllib.urlretrieve(url, local_file_path) def _do_request(self, url, data=None): - opener = urllib2.build_opener(self._handler) + opener = urllib2.build_opener() + for cname, morsel in self._server._proxy.binding.cookies.iteritems(): + attrs = [] + value = morsel.get('version', '') + if value != '' and value != '0': + attrs.append('$Version=%s' % value) + attrs.append('%s=%s' % (cname, morsel.coded_value)) + value = morsel.get('path') + if value: + attrs.append('$Path=%s' % value) + value = morsel.get('domain') + if value: + attrs.append('$Domain=%s' % value) + opener.addheaders.append(('Cookie', "; ".join(attrs))) request = urllib2.Request(url, data=data) if data: request.get_method = lambda: 'PUT' @@ -162,6 +200,7 @@ def _do_request(self, url, data=None): def _get_url(self, datastore, resource, datacenter=None): if not resource.startswith("/"): resource = "/" + resource + resource = urllib.quote(resource) params = {"dsName":datastore} if datacenter: @@ -173,11 +212,3 @@ def _get_url(self, datastore, resource, datacenter=None): def _get_service_url(self): service_url = self._server._proxy.binding.url return service_url[:service_url.rindex("/sdk")] - - def _build_auth_handler(self): - service_url = self._get_service_url() - user = self._server._VIServer__user - password = self._server._VIServer__password - auth_manager = urllib2.HTTPPasswordMgrWithDefaultRealm() - auth_manager.add_password(None, service_url, user, password) - return urllib2.HTTPBasicAuthHandler(auth_manager) From 4dc5d7605bda5fbe46e6b6e891216b35a0a74146 Mon Sep 17 00:00:00 2001 From: gentoo90 Date: Sat, 21 Jun 2014 16:17:24 +0300 Subject: [PATCH 07/11] Add copy_file and move_file methods to VIFileManager --- pysphere/vi_file_manager.py | 76 +++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/pysphere/vi_file_manager.py b/pysphere/vi_file_manager.py index 03fb535..d6abd83 100644 --- a/pysphere/vi_file_manager.py +++ b/pysphere/vi_file_manager.py @@ -119,6 +119,82 @@ def make_directory(self, path, create_parent=False): except (VI.ZSI.FaultException), e: raise VIApiException(e) + def move_file(self, source_path, dest_path, source_datacenter=None, + dest_datacenter=None, force=False, sync_run=True): + """Moves the source file or folder to the destination. If the destination + file does not exist, it is created. If the destination file exists, the + @force parameter determines whether to overwrite it with the source or not. + Folders can be copied recursively. In this case, the destination, if it + exists, must be a folder, else one will be created. Existing files on + the destination that conflict with source files can be overwritten + using the @force parameter. + """ + try: + request = VI.MoveDatastoreFile_TaskRequestMsg() + _this = request.new__this(self._mor) + _this.set_attribute_type(self._mor.get_attribute_type()) + request.set_element__this(_this) + request.set_element_sourceName(source_path) + request.set_element_destinationName(dest_path) + if source_datacenter: + request.set_element_sourceDatacenter(source_datacenter) + if dest_datacenter: + request.set_element_destinationDatacenter(dest_datacenter) + request.set_element_force(force) + + task = self._server._proxy.MoveDatastoreFile_Task(request)._returnval + vi_task = VITask(task, self._server) + if sync_run: + status = vi_task.wait_for_state([vi_task.STATE_SUCCESS, + vi_task.STATE_ERROR]) + if status == vi_task.STATE_ERROR: + raise VIException(vi_task.get_error_message(), + FaultTypes.TASK_ERROR) + return + + return vi_task + + except (VI.ZSI.FaultException), e: + raise VIApiException(e) + + def copy_file(self, source_path, dest_path, source_datacenter=None, + dest_datacenter=None, force=False, sync_run=True): + """Copies the source file or folder to the destination. If the destination + file does not exist, it is created. If the destination file exists, the + @force parameter determines whether to overwrite it with the source or not. + Folders can be copied recursively. In this case, the destination, if it + exists, must be a folder, else one will be created. Existing files on + the destination that conflict with source files can be overwritten + using the @force parameter. + """ + try: + request = VI.CopyDatastoreFile_TaskRequestMsg() + _this = request.new__this(self._mor) + _this.set_attribute_type(self._mor.get_attribute_type()) + request.set_element__this(_this) + request.set_element_sourceName(source_path) + request.set_element_destinationName(dest_path) + if source_datacenter: + request.set_element_sourceDatacenter(source_datacenter) + if dest_datacenter: + request.set_element_destinationDatacenter(dest_datacenter) + request.set_element_force(force) + + task = self._server._proxy.CopyDatastoreFile_Task(request)._returnval + vi_task = VITask(task, self._server) + if sync_run: + status = vi_task.wait_for_state([vi_task.STATE_SUCCESS, + vi_task.STATE_ERROR]) + if status == vi_task.STATE_ERROR: + raise VIException(vi_task.get_error_message(), + FaultTypes.TASK_ERROR) + return + + return vi_task + + except (VI.ZSI.FaultException), e: + raise VIApiException(e) + def delete_file(self, path, datacenter=None, sync_run=True): """Deletes the specified file or folder from the datastore. If a file of a virtual machine is deleted, it may corrupt From b27c2c86d7fb9e4cf1f8d0f598a66edff44c16a1 Mon Sep 17 00:00:00 2001 From: gentoo90 Date: Thu, 14 May 2015 21:00:33 +0300 Subject: [PATCH 08/11] More informative exceptions, refactoring --- .gitignore | 2 + pysphere/__init__.py | 6 +- pysphere/resources/vi_exception.py | 15 +- pysphere/vi_file_manager.py | 14 +- pysphere/vi_managed_entity.py | 24 +- pysphere/vi_server.py | 258 +++++++++++----------- pysphere/vi_virtual_machine.py | 340 ++++++++++++++--------------- 7 files changed, 322 insertions(+), 337 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ea3bf03 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.pyc +*.egg-info/ diff --git a/pysphere/__init__.py b/pysphere/__init__.py index 2e9797a..92032c3 100644 --- a/pysphere/__init__.py +++ b/pysphere/__init__.py @@ -163,11 +163,11 @@ # accompanying credits file. # #-- -__all__ = ['VIServer', 'VIException', 'VIApiException', 'VITask', 'FaultTypes', - 'VIMor', 'MORTypes', 'VMPowerState', 'ToolsStatus', 'VIProperty'] +__all__ = ['VIServer', 'VIException', 'VIApiException', 'VITaskException', 'VITask', + 'FaultTypes', 'VIMor', 'MORTypes', 'VMPowerState', 'ToolsStatus', 'VIProperty'] from pysphere.resources.vi_exception import VIException, VIApiException, \ - FaultTypes + VITaskException, FaultTypes from pysphere.vi_task import VITask from pysphere.vi_property import VIProperty from pysphere.vi_mor import VIMor, MORTypes diff --git a/pysphere/resources/vi_exception.py b/pysphere/resources/vi_exception.py index de2efd1..16d7811 100644 --- a/pysphere/resources/vi_exception.py +++ b/pysphere/resources/vi_exception.py @@ -48,6 +48,19 @@ def __init__(self, e): super(self.__class__, self).__init__(message, fault) +class VITaskException(VIException): + def __init__(self, e): + try: + message = e.localizedMessage + except: + message = str(e) + try: + fault = e.fault.typecode.type[1] + except: + fault = 'Undefined' + + super(self.__class__, self).__init__(message, fault) + class UnsupportedPerfIntervalError(VIException): pass @@ -58,4 +71,4 @@ class FaultTypes: TIME_OUT = 'Operation Timed Out' TASK_ERROR = 'Task Error' NOT_SUPPORTED = 'Operation Not Supported' - INVALID_OPERATION = 'Invalid Operation' \ No newline at end of file + INVALID_OPERATION = 'Invalid Operation' diff --git a/pysphere/vi_file_manager.py b/pysphere/vi_file_manager.py index d6abd83..d8f3fbb 100644 --- a/pysphere/vi_file_manager.py +++ b/pysphere/vi_file_manager.py @@ -35,7 +35,7 @@ from pysphere import VIProperty, VIMor, MORTypes from pysphere.vi_task import VITask from pysphere.resources.vi_exception import VIException, VIApiException, \ - FaultTypes + VITaskException, FaultTypes from pysphere.vi_snapshot import VISnapshot from pysphere.vi_managed_entity import VIManagedEntity @@ -89,8 +89,7 @@ def list_files(self, path, case_insensitive=True, response = self._server._proxy.SearchDatastore_Task(request)._returnval vi_task = VITask(response, self._server) if vi_task.wait_for_state([vi_task.STATE_ERROR, vi_task.STATE_SUCCESS]) == vi_task.STATE_ERROR: - raise VIException(vi_task.get_error_message(), - FaultTypes.TASK_ERROR) + raise VITaskException(vi_task.info.error) info = vi_task.get_result() # return info @@ -148,8 +147,7 @@ def move_file(self, source_path, dest_path, source_datacenter=None, status = vi_task.wait_for_state([vi_task.STATE_SUCCESS, vi_task.STATE_ERROR]) if status == vi_task.STATE_ERROR: - raise VIException(vi_task.get_error_message(), - FaultTypes.TASK_ERROR) + raise VITaskException(vi_task.info.error) return return vi_task @@ -186,8 +184,7 @@ def copy_file(self, source_path, dest_path, source_datacenter=None, status = vi_task.wait_for_state([vi_task.STATE_SUCCESS, vi_task.STATE_ERROR]) if status == vi_task.STATE_ERROR: - raise VIException(vi_task.get_error_message(), - FaultTypes.TASK_ERROR) + raise VITaskException(vi_task.info.error) return return vi_task @@ -215,8 +212,7 @@ def delete_file(self, path, datacenter=None, sync_run=True): status = vi_task.wait_for_state([vi_task.STATE_SUCCESS, vi_task.STATE_ERROR]) if status == vi_task.STATE_ERROR: - raise VIException(vi_task.get_error_message(), - FaultTypes.TASK_ERROR) + raise VITaskException(vi_task.info.error) return return vi_task diff --git a/pysphere/vi_managed_entity.py b/pysphere/vi_managed_entity.py index 535290b..10b9a4c 100644 --- a/pysphere/vi_managed_entity.py +++ b/pysphere/vi_managed_entity.py @@ -30,14 +30,14 @@ from pysphere.resources import VimService_services as VI from pysphere.vi_task import VITask from pysphere.resources.vi_exception import VIException, VIApiException, \ - FaultTypes + VITaskException, FaultTypes class VIManagedEntity(object): def __init__(self, server, mor): self._server = server self._mor = mor - + def rename(self, new_name, sync_run=True): """ Renames this managed entity. @@ -48,7 +48,7 @@ def rename(self, new_name, sync_run=True): escaped as %5C or %5c, and a percent is escaped as %25. * sync_run: (default True), If False does not wait for the task to finish and returns an instance of a VITask for the user to monitor - its progress + its progress """ try: request = VI.Rename_TaskRequestMsg() @@ -63,21 +63,20 @@ def rename(self, new_name, sync_run=True): status = vi_task.wait_for_state([vi_task.STATE_SUCCESS, vi_task.STATE_ERROR]) if status == vi_task.STATE_ERROR: - raise VIException(vi_task.get_error_message(), - FaultTypes.TASK_ERROR) + raise VITaskException(vi_task.info.error) return return vi_task except (VI.ZSI.FaultException), e: raise VIApiException(e) - + def reload(self): """ Reload the entity state. Clients only need to call this method if they changed some external state that affects the service without using the Web service interface - to perform the change. For example, hand-editing a virtual machine + to perform the change. For example, hand-editing a virtual machine configuration file affects the configuration of the associated virtual machine but the service managing the virtual machine might not monitor the file for changes. In this case, after such an edit, a client would @@ -92,10 +91,10 @@ def reload(self): self._server._proxy.Reload(request) except (VI.ZSI.FaultException), e: raise VIApiException(e) - + def destroy(self, sync_run=True): """ - Destroys this object, deleting its contents and removing it from its + Destroys this object, deleting its contents and removing it from its parent folder (if any) * sync_run: (default True), If False does not wait for the task to finish and returns an instance of a VITask for the user to monitor @@ -106,7 +105,7 @@ def destroy(self, sync_run=True): _this = request.new__this(self._mor) _this.set_attribute_type(self._mor.get_attribute_type()) request.set_element__this(_this) - + task = self._server._proxy.Destroy_Task(request)._returnval vi_task = VITask(task, self._server) @@ -114,10 +113,9 @@ def destroy(self, sync_run=True): status = vi_task.wait_for_state([vi_task.STATE_SUCCESS, vi_task.STATE_ERROR]) if status == vi_task.STATE_ERROR: - raise VIException(vi_task.get_error_message(), - FaultTypes.TASK_ERROR) + raise VITaskException(vi_task.info.error) return return vi_task except (VI.ZSI.FaultException), e: - raise VIApiException(e) \ No newline at end of file + raise VIApiException(e) diff --git a/pysphere/vi_server.py b/pysphere/vi_server.py index d36f0ab..83d95f1 100644 --- a/pysphere/vi_server.py +++ b/pysphere/vi_server.py @@ -35,7 +35,7 @@ from pysphere.resources import VimService_services as VI -from pysphere import VIException, VIApiException, FaultTypes +from pysphere import VIException, VIApiException, VITaskException, FaultTypes from pysphere.vi_virtual_machine import VIVirtualMachine from pysphere.vi_file_manager import VIFileManager from pysphere.vi_performance_manager import PerformanceManager @@ -592,8 +592,7 @@ def register_vm(self, path, name=None, sync_run=True, folder=None, status = vi_task.wait_for_state([vi_task.STATE_SUCCESS, vi_task.STATE_ERROR]) if status == vi_task.STATE_ERROR: - raise VIException(vi_task.get_error_message(), - FaultTypes.TASK_ERROR) + raise VITaskException(vi_task.info.error) return return vi_task @@ -706,7 +705,6 @@ def _get_object_properties_bulk(self, mor_list, properties): except (VI.ZSI.FaultException), e: raise VIApiException(e) - def _retrieve_properties_traversal(self, property_names=[], from_node=None, obj_type='ManagedEntity'): """Uses VI API's property collector to retrieve the properties defined @@ -736,138 +734,12 @@ def _retrieve_properties_traversal(self, property_names=[], do_PropertyFilterSpec_specSet = request.new_specSet() props_set = [] - do_PropertySpec_propSet =do_PropertyFilterSpec_specSet.new_propSet() + do_PropertySpec_propSet = do_PropertyFilterSpec_specSet.new_propSet() do_PropertySpec_propSet.set_element_type(obj_type) do_PropertySpec_propSet.set_element_pathSet(property_names) props_set.append(do_PropertySpec_propSet) - objects_set = [] - do_ObjectSpec_objSet = do_PropertyFilterSpec_specSet.new_objectSet() - mor_obj = do_ObjectSpec_objSet.new_obj(from_node) - mor_obj.set_attribute_type(from_node.get_attribute_type()) - do_ObjectSpec_objSet.set_element_obj(mor_obj) - do_ObjectSpec_objSet.set_element_skip(False) - - #Recurse through all ResourcePools - rp_to_rp = VI.ns0.TraversalSpec_Def('rpToRp').pyclass() - rp_to_rp.set_element_name('rpToRp') - rp_to_rp.set_element_type(MORTypes.ResourcePool) - rp_to_rp.set_element_path('resourcePool') - rp_to_rp.set_element_skip(False) - rp_to_vm= VI.ns0.TraversalSpec_Def('rpToVm').pyclass() - rp_to_vm.set_element_name('rpToVm') - rp_to_vm.set_element_type(MORTypes.ResourcePool) - rp_to_vm.set_element_path('vm') - rp_to_vm.set_element_skip(False) - - spec_array_resource_pool = [do_ObjectSpec_objSet.new_selectSet(), - do_ObjectSpec_objSet.new_selectSet()] - spec_array_resource_pool[0].set_element_name('rpToRp') - spec_array_resource_pool[1].set_element_name('rpToVm') - - rp_to_rp.set_element_selectSet(spec_array_resource_pool) - - #Traversal through resource pool branch - cr_to_rp = VI.ns0.TraversalSpec_Def('crToRp').pyclass() - cr_to_rp.set_element_name('crToRp') - cr_to_rp.set_element_type(MORTypes.ComputeResource) - cr_to_rp.set_element_path('resourcePool') - cr_to_rp.set_element_skip(False) - spec_array_computer_resource =[do_ObjectSpec_objSet.new_selectSet(), - do_ObjectSpec_objSet.new_selectSet()] - spec_array_computer_resource[0].set_element_name('rpToRp'); - spec_array_computer_resource[1].set_element_name('rpToVm'); - cr_to_rp.set_element_selectSet(spec_array_computer_resource) - - #Traversal through host branch - cr_to_h = VI.ns0.TraversalSpec_Def('crToH').pyclass() - cr_to_h.set_element_name('crToH') - cr_to_h.set_element_type(MORTypes.ComputeResource) - cr_to_h.set_element_path('host') - cr_to_h.set_element_skip(False) - - #Traversal through hostFolder branch - dc_to_hf = VI.ns0.TraversalSpec_Def('dcToHf').pyclass() - dc_to_hf.set_element_name('dcToHf') - dc_to_hf.set_element_type(MORTypes.Datacenter) - dc_to_hf.set_element_path('hostFolder') - dc_to_hf.set_element_skip(False) - spec_array_datacenter_host = [do_ObjectSpec_objSet.new_selectSet()] - spec_array_datacenter_host[0].set_element_name('visitFolders') - dc_to_hf.set_element_selectSet(spec_array_datacenter_host) - - #Traversal through vmFolder branch - dc_to_vmf = VI.ns0.TraversalSpec_Def('dcToVmf').pyclass() - dc_to_vmf.set_element_name('dcToVmf') - dc_to_vmf.set_element_type(MORTypes.Datacenter) - dc_to_vmf.set_element_path('vmFolder') - dc_to_vmf.set_element_skip(False) - spec_array_datacenter_vm = [do_ObjectSpec_objSet.new_selectSet()] - spec_array_datacenter_vm[0].set_element_name('visitFolders') - dc_to_vmf.set_element_selectSet(spec_array_datacenter_vm) - - #Traversal through datastore branch - dc_to_ds = VI.ns0.TraversalSpec_Def('dcToDs').pyclass() - dc_to_ds.set_element_name('dcToDs') - dc_to_ds.set_element_type(MORTypes.Datacenter) - dc_to_ds.set_element_path('datastore') - dc_to_ds.set_element_skip(False) - spec_array_datacenter_ds = [do_ObjectSpec_objSet.new_selectSet()] - spec_array_datacenter_ds[0].set_element_name('visitFolders') - dc_to_ds.set_element_selectSet(spec_array_datacenter_ds) - - #Recurse through all hosts - h_to_vm = VI.ns0.TraversalSpec_Def('hToVm').pyclass() - h_to_vm.set_element_name('hToVm') - h_to_vm.set_element_type(MORTypes.HostSystem) - h_to_vm.set_element_path('vm') - h_to_vm.set_element_skip(False) - spec_array_host_vm = [do_ObjectSpec_objSet.new_selectSet()] - spec_array_host_vm[0].set_element_name('visitFolders') - h_to_vm.set_element_selectSet(spec_array_host_vm) - - #Recurse through all datastores - ds_to_vm = VI.ns0.TraversalSpec_Def('dsToVm').pyclass() - ds_to_vm.set_element_name('dsToVm') - ds_to_vm.set_element_type(MORTypes.Datastore) - ds_to_vm.set_element_path('vm') - ds_to_vm.set_element_skip(False) - spec_array_datastore_vm = [do_ObjectSpec_objSet.new_selectSet()] - spec_array_datastore_vm[0].set_element_name('visitFolders') - ds_to_vm.set_element_selectSet(spec_array_datastore_vm) - - #Recurse through the folders - visit_folders = VI.ns0.TraversalSpec_Def('visitFolders').pyclass() - visit_folders.set_element_name('visitFolders') - visit_folders.set_element_type(MORTypes.Folder) - visit_folders.set_element_path('childEntity') - visit_folders.set_element_skip(False) - spec_array_visit_folders = [do_ObjectSpec_objSet.new_selectSet(), - do_ObjectSpec_objSet.new_selectSet(), - do_ObjectSpec_objSet.new_selectSet(), - do_ObjectSpec_objSet.new_selectSet(), - do_ObjectSpec_objSet.new_selectSet(), - do_ObjectSpec_objSet.new_selectSet(), - do_ObjectSpec_objSet.new_selectSet(), - do_ObjectSpec_objSet.new_selectSet(), - do_ObjectSpec_objSet.new_selectSet()] - spec_array_visit_folders[0].set_element_name('visitFolders') - spec_array_visit_folders[1].set_element_name('dcToHf') - spec_array_visit_folders[2].set_element_name('dcToVmf') - spec_array_visit_folders[3].set_element_name('crToH') - spec_array_visit_folders[4].set_element_name('crToRp') - spec_array_visit_folders[5].set_element_name('dcToDs') - spec_array_visit_folders[6].set_element_name('hToVm') - spec_array_visit_folders[7].set_element_name('dsToVm') - spec_array_visit_folders[8].set_element_name('rpToVm') - visit_folders.set_element_selectSet(spec_array_visit_folders) - - #Add all of them here - spec_array = [visit_folders, dc_to_vmf, dc_to_ds, dc_to_hf, cr_to_h, - cr_to_rp, rp_to_rp, h_to_vm, ds_to_vm, rp_to_vm] - - do_ObjectSpec_objSet.set_element_selectSet(spec_array) - objects_set.append(do_ObjectSpec_objSet) + objects_set = self._get_traversal_objects_set(do_PropertyFilterSpec_specSet, from_node) do_PropertyFilterSpec_specSet.set_element_propSet(props_set) do_PropertyFilterSpec_specSet.set_element_objectSet(objects_set) @@ -876,8 +748,7 @@ def _retrieve_properties_traversal(self, property_names=[], return request_call(request) except (VI.ZSI.FaultException), e: - raise VIApiException(e) - + raise VIApiException(e) def _retrieve_property_request(self): """Returns a base request object an call request method pointer for @@ -923,6 +794,125 @@ def call_retrieve_properties_ex(request): return request, call_pointer + def _get_traversal_objects_set(self, specSet, from_node): + objects_set = [] + do_ObjectSpec_objSet = specSet.new_objectSet() + mor_obj = do_ObjectSpec_objSet.new_obj(from_node) + mor_obj.set_attribute_type(from_node.get_attribute_type()) + do_ObjectSpec_objSet.set_element_obj(mor_obj) + do_ObjectSpec_objSet.set_element_skip(False) + + #Recurse through all ResourcePools + rp_to_rp = VI.ns0.TraversalSpec_Def('rpToRp').pyclass() + rp_to_rp.set_element_name('rpToRp') + rp_to_rp.set_element_type(MORTypes.ResourcePool) + rp_to_rp.set_element_path('resourcePool') + rp_to_rp.set_element_skip(False) + rp_to_vm = VI.ns0.TraversalSpec_Def('rpToVm').pyclass() + rp_to_vm.set_element_name('rpToVm') + rp_to_vm.set_element_type(MORTypes.ResourcePool) + rp_to_vm.set_element_path('vm') + rp_to_vm.set_element_skip(False) + + spec_array_resource_pool = [do_ObjectSpec_objSet.new_selectSet(), + do_ObjectSpec_objSet.new_selectSet()] + spec_array_resource_pool[0].set_element_name('rpToRp') + spec_array_resource_pool[1].set_element_name('rpToVm') + + rp_to_rp.set_element_selectSet(spec_array_resource_pool) + + #Traversal through resource pool branch + cr_to_rp = VI.ns0.TraversalSpec_Def('crToRp').pyclass() + cr_to_rp.set_element_name('crToRp') + cr_to_rp.set_element_type(MORTypes.ComputeResource) + cr_to_rp.set_element_path('resourcePool') + cr_to_rp.set_element_skip(False) + spec_array_computer_resource = [do_ObjectSpec_objSet.new_selectSet(), + do_ObjectSpec_objSet.new_selectSet()] + spec_array_computer_resource[0].set_element_name('rpToRp'); + spec_array_computer_resource[1].set_element_name('rpToVm'); + cr_to_rp.set_element_selectSet(spec_array_computer_resource) + + #Traversal through host branch + cr_to_h = VI.ns0.TraversalSpec_Def('crToH').pyclass() + cr_to_h.set_element_name('crToH') + cr_to_h.set_element_type(MORTypes.ComputeResource) + cr_to_h.set_element_path('host') + cr_to_h.set_element_skip(False) + + #Traversal through hostFolder branch + dc_to_hf = VI.ns0.TraversalSpec_Def('dcToHf').pyclass() + dc_to_hf.set_element_name('dcToHf') + dc_to_hf.set_element_type(MORTypes.Datacenter) + dc_to_hf.set_element_path('hostFolder') + dc_to_hf.set_element_skip(False) + spec_array_datacenter_host = [do_ObjectSpec_objSet.new_selectSet()] + spec_array_datacenter_host[0].set_element_name('visitFolders') + dc_to_hf.set_element_selectSet(spec_array_datacenter_host) + + #Traversal through vmFolder branch + dc_to_vmf = VI.ns0.TraversalSpec_Def('dcToVmf').pyclass() + dc_to_vmf.set_element_name('dcToVmf') + dc_to_vmf.set_element_type(MORTypes.Datacenter) + dc_to_vmf.set_element_path('vmFolder') + dc_to_vmf.set_element_skip(False) + spec_array_datacenter_vm = [do_ObjectSpec_objSet.new_selectSet()] + spec_array_datacenter_vm[0].set_element_name('visitFolders') + dc_to_vmf.set_element_selectSet(spec_array_datacenter_vm) + + #Traversal through datastore branch + dc_to_ds = VI.ns0.TraversalSpec_Def('dcToDs').pyclass() + dc_to_ds.set_element_name('dcToDs') + dc_to_ds.set_element_type(MORTypes.Datacenter) + dc_to_ds.set_element_path('datastore') + dc_to_ds.set_element_skip(False) + spec_array_datacenter_ds = [do_ObjectSpec_objSet.new_selectSet()] + spec_array_datacenter_ds[0].set_element_name('visitFolders') + dc_to_ds.set_element_selectSet(spec_array_datacenter_ds) + + #Recurse through all hosts + h_to_vm = VI.ns0.TraversalSpec_Def('hToVm').pyclass() + h_to_vm.set_element_name('hToVm') + h_to_vm.set_element_type(MORTypes.HostSystem) + h_to_vm.set_element_path('vm') + h_to_vm.set_element_skip(False) + spec_array_host_vm = [do_ObjectSpec_objSet.new_selectSet()] + spec_array_host_vm[0].set_element_name('visitFolders') + h_to_vm.set_element_selectSet(spec_array_host_vm) + + #Recurse through all datastores + ds_to_vm = VI.ns0.TraversalSpec_Def('dsToVm').pyclass() + ds_to_vm.set_element_name('dsToVm') + ds_to_vm.set_element_type(MORTypes.Datastore) + ds_to_vm.set_element_path('vm') + ds_to_vm.set_element_skip(False) + spec_array_datastore_vm = [do_ObjectSpec_objSet.new_selectSet()] + spec_array_datastore_vm[0].set_element_name('visitFolders') + ds_to_vm.set_element_selectSet(spec_array_datastore_vm) + + #Recurse through the folders + visit_folders = VI.ns0.TraversalSpec_Def('visitFolders').pyclass() + visit_folders.set_element_name('visitFolders') + visit_folders.set_element_type(MORTypes.Folder) + visit_folders.set_element_path('childEntity') + visit_folders.set_element_skip(False) + spec_array_visit_folders = [] + for i in ('visitFolders', 'dcToHf', 'dcToVmf', 'crToH', 'crToRp', + 'dcToDs', 'hToVm', 'dsToVm', 'rpToVm'): + select_set = do_ObjectSpec_objSet.new_selectSet() + select_set.set_element_name(i) + spec_array_visit_folders.append(select_set) + + visit_folders.set_element_selectSet(spec_array_visit_folders) + + #Add all of them here + spec_array = [visit_folders, dc_to_vmf, dc_to_ds, dc_to_hf, cr_to_h, + cr_to_rp, rp_to_rp, h_to_vm, ds_to_vm, rp_to_vm] + + do_ObjectSpec_objSet.set_element_selectSet(spec_array) + objects_set.append(do_ObjectSpec_objSet) + return objects_set + def _set_header(self, name, value): """Sets a HTTP header to be sent with the SOAP requests. E.g. for impersonation of a particular client. diff --git a/pysphere/vi_virtual_machine.py b/pysphere/vi_virtual_machine.py index 02573d7..981c571 100644 --- a/pysphere/vi_virtual_machine.py +++ b/pysphere/vi_virtual_machine.py @@ -35,7 +35,7 @@ from pysphere import VIProperty, VIMor, MORTypes from pysphere.vi_task import VITask from pysphere.resources.vi_exception import VIException, VIApiException, \ - FaultTypes + VITaskException, FaultTypes from pysphere.vi_snapshot import VISnapshot from pysphere.vi_managed_entity import VIManagedEntity @@ -76,7 +76,7 @@ def __init__(self, server, mor): except AttributeError: #guest operations not supported (since API 5.0) pass - + #-------------------# #-- POWER METHODS --# #-------------------# @@ -102,8 +102,7 @@ def power_on(self, sync_run=True, host=None): status = vi_task.wait_for_state([vi_task.STATE_SUCCESS, vi_task.STATE_ERROR]) if status == vi_task.STATE_ERROR: - raise VIException(vi_task.get_error_message(), - FaultTypes.TASK_ERROR) + raise VITaskException(vi_task.info.error) return return vi_task @@ -128,8 +127,7 @@ def power_off(self, sync_run=True): status = vi_task.wait_for_state([vi_task.STATE_SUCCESS, vi_task.STATE_ERROR]) if status == vi_task.STATE_ERROR: - raise VIException(vi_task.get_error_message(), - FaultTypes.TASK_ERROR) + raise VITaskException(vi_task.info.error) return return vi_task @@ -154,8 +152,7 @@ def reset(self, sync_run=True): status = vi_task.wait_for_state([vi_task.STATE_SUCCESS, vi_task.STATE_ERROR]) if status == vi_task.STATE_ERROR: - raise VIException(vi_task.get_error_message(), - FaultTypes.TASK_ERROR) + raise VITaskException(vi_task.info.error) return return vi_task @@ -177,8 +174,7 @@ def suspend(self, sync_run=True): status = vi_task.wait_for_state([vi_task.STATE_SUCCESS, vi_task.STATE_ERROR]) if status == vi_task.STATE_ERROR: - raise VIException(vi_task.get_error_message(), - FaultTypes.TASK_ERROR) + raise VITaskException(vi_task.info.error) return return vi_task @@ -191,7 +187,7 @@ def get_status(self, basic_status=False): basic statuses: 'POWERED ON', 'POWERED OFF', 'SUSPENDED', 'BLOCKED ON MSG' extended_statuses (only available for vCenter): - 'POWERING ON', 'POWERING OFF', 'SUSPENDING', 'RESETTING', + 'POWERING ON', 'POWERING OFF', 'SUSPENDING', 'RESETTING', 'REVERTING TO SNAPSHOT', 'UNKNOWN' if basic_status is False (defautl) and the server is a vCenter, then one of the extended statuses might be returned. @@ -266,17 +262,17 @@ def get_question(self): """Returns a VMQuestion object with information about a question in this vm pending to be answered. None if the vm has no pending questions. """ - + class VMQuestion(object): def __init__(self, vm, qprop): self._answered = False self._vm = vm self._qid = qprop.id self._text = qprop.text - self._choices = [(ci.key, ci.label) + self._choices = [(ci.key, ci.label) for ci in qprop.choice.choiceInfo] self._default = getattr(qprop.choice, 'defaultIndex', None) - + def text(self): return self._text def choices(self): @@ -285,12 +281,12 @@ def default_choice(self): return self._choices[self._default] def answer(self, choice=None): if self._answered: - raise VIException("Question already answered", + raise VIException("Question already answered", FaultTypes.INVALID_OPERATION) if choice is None and self._default is None: raise VIException("No default choice available", FaultTypes.PARAMETER_ERROR) - elif choice is not None and choice not in [i[0] for i + elif choice is not None and choice not in [i[0] for i in self._choices]: raise VIException("Invalid choice id", FaultTypes.PARAMETER_ERROR) @@ -306,14 +302,14 @@ def answer(self, choice=None): self._vm._server._proxy.AnswerVM(request) self._answered = True except (VI.ZSI.FaultException), e: - raise VIApiException(e) + raise VIApiException(e) self.__update_properties() if not hasattr(self.properties.runtime, "question"): return return VMQuestion(self, self.properties.runtime.question) - - + + def is_powering_off(self): """Returns True if the VM is being powered off""" return self.get_status() == VMPowerState.POWERING_OFF @@ -353,7 +349,7 @@ def is_reverting(self): #-------------------------# #-- GUEST POWER METHODS --# #-------------------------# - + def reboot_guest(self): """Issues a command to the guest operating system asking it to perform a reboot. Returns immediately and does not wait for the guest operating @@ -394,27 +390,27 @@ def standby_guest(self): mor_vm = request.new__this(self._mor) mor_vm.set_attribute_type(self._mor.get_attribute_type()) request.set_element__this(mor_vm) - + self._server._proxy.StandbyGuest(request) - + except (VI.ZSI.FaultException), e: raise VIApiException(e) #--------------# #-- CLONE VM --# #--------------# - def clone(self, name, sync_run=True, folder=None, resourcepool=None, - datastore=None, host=None, power_on=True, template=False, + def clone(self, name, sync_run=True, folder=None, resourcepool=None, + datastore=None, host=None, power_on=True, template=False, snapshot=None, linked=False): """Clones this Virtual Machine @name: name of the new virtual machine @sync_run: if True (default) waits for the task to finish, and returns - a VIVirtualMachine instance with the new VM (raises an exception if - the task didn't succeed). If sync_run is set to False the task is + a VIVirtualMachine instance with the new VM (raises an exception if + the task didn't succeed). If sync_run is set to False the task is started and a VITask instance is returned @folder: name of the folder that will contain the new VM, if not set the vm will be added to the folder the original VM belongs to - @resourcepool: MOR of the resourcepool to be used for the new vm. + @resourcepool: MOR of the resourcepool to be used for the new vm. If not set, it uses the same resourcepool than the original vm. @datastore: MOR of the datastore where the virtual machine should be located. If not specified, the current datastore is used. @@ -425,28 +421,28 @@ def clone(self, name, sync_run=True, folder=None, resourcepool=None, stand-alone host, the host is used. * if resourcepool is specified, and the target pool represents a DRS-enabled cluster, a host selected by DRS is used. - * if resource pool is specified and the target pool represents a + * if resource pool is specified and the target pool represents a cluster without DRS enabled, an InvalidArgument exception be thrown. @power_on: If the new VM will be powered on after being created. If template is set to True, this parameter is ignored. - @template: Specifies whether or not the new virtual machine should be - marked as a template. + @template: Specifies whether or not the new virtual machine should be + marked as a template. @snapshot: Snaphot MOR, or VISnaphost object, or snapshot name (if a - name is given, then the first matching occurrence will be used). - Is the snapshot reference from which to base the clone. If this - parameter is set, the clone is based off of the snapshot point. This - means that the newly created virtual machine will have the same - configuration as the virtual machine at the time the snapshot was - taken. If this parameter is not set then the clone is based off of + name is given, then the first matching occurrence will be used). + Is the snapshot reference from which to base the clone. If this + parameter is set, the clone is based off of the snapshot point. This + means that the newly created virtual machine will have the same + configuration as the virtual machine at the time the snapshot was + taken. If this parameter is not set then the clone is based off of the virtual machine's current configuration. @linked: If True (requires @snapshot to be set) creates a new child disk - backing on the destination datastore. None of the virtual disk's + backing on the destination datastore. None of the virtual disk's existing files should be moved from their current locations. - Note that in the case of a clone operation, this means that the + Note that in the case of a clone operation, this means that the original virtual machine's disks are now all being shared. This is - only safe if the clone was taken from a snapshot point, because - snapshot points are always read-only. Thus for a clone this option + only safe if the clone was taken from a snapshot point, because + snapshot points are always read-only. Thus for a clone this option is only valid when cloning from a snapshot """ try: @@ -472,7 +468,7 @@ def clone(self, name, sync_run=True, folder=None, resourcepool=None, elif not folder_mor: raise VIException("Error locating current VM folder", FaultTypes.OBJECT_NOT_FOUND) - + request = VI.CloneVM_TaskRequestMsg() _this = request.new__this(self._mor) _this.set_attribute_type(self._mor.get_attribute_type()) @@ -516,15 +512,15 @@ def clone(self, name, sync_run=True, folder=None, resourcepool=None, break if not sn_mor: raise VIException("Could not find snapshot '%s'" % snapshot, - FaultTypes.OBJECT_NOT_FOUND) + FaultTypes.OBJECT_NOT_FOUND) snapshot = spec.new_snapshot(sn_mor) snapshot.set_attribute_type(sn_mor.get_attribute_type()) spec.set_element_snapshot(snapshot) - + if linked and snapshot: location.set_element_diskMoveType("createNewChildDiskBacking") - - spec.set_element_location(location) + + spec.set_element_location(location) spec.set_element_template(template) request.set_element_spec(spec) task = self._server._proxy.CloneVM_Task(request)._returnval @@ -533,10 +529,9 @@ def clone(self, name, sync_run=True, folder=None, resourcepool=None, status = vi_task.wait_for_state([vi_task.STATE_SUCCESS, vi_task.STATE_ERROR]) if status == vi_task.STATE_ERROR: - raise VIException(vi_task.get_error_message(), - FaultTypes.TASK_ERROR) - return VIVirtualMachine(self._server, vi_task.get_result()._obj) - + raise VITaskException(vi_task.info.error) + return VIVirtualMachine(self._server, vi_task.get_result()._obj) + return vi_task except (VI.ZSI.FaultException), e: @@ -549,23 +544,23 @@ def migrate(self, sync_run=True, priority='default', resource_pool=None, host=None, state=None): """ Cold or Hot migrates this VM to a new host or resource pool. - @sync_run: If True (default) waits for the task to finish, and returns + @sync_run: If True (default) waits for the task to finish, and returns (raises an exception if the task didn't succeed). If False the task is started an a VITask instance is returned. @priority: either 'default', 'high', or 'low': priority of the task that - moves the vm. Note this priority can affect both the source and + moves the vm. Note this priority can affect both the source and target hosts. @resource_pool: The target resource pool for the virtual machine. If the pool parameter is left unset, the virtual machine's current pool is used as the target pool. - @host: The target host to which the virtual machine is intended to - migrate. The host parameter may be left unset if the compute + @host: The target host to which the virtual machine is intended to + migrate. The host parameter may be left unset if the compute resource associated with the target pool represents a stand-alone host or a DRS-enabled cluster. In the former case the stand-alone host is used as the target host. In the latter case, the DRS system selects an appropriate target host from the cluster. @state: If specified, the virtual machine migrates only if its state - matches the specified state. + matches the specified state. """ try: if priority not in ['default', 'low', 'high']: @@ -578,7 +573,7 @@ def migrate(self, sync_run=True, priority='default', resource_pool=None, VMPowerState.POWERED_OFF, VMPowerState.SUSPENDED), FaultTypes.PARAMETER_ERROR) - + request = VI.MigrateVM_TaskRequestMsg() _this = request.new__this(self._mor) _this.set_attribute_type(self._mor.get_attribute_type()) @@ -601,27 +596,26 @@ def migrate(self, sync_run=True, priority='default', resource_pool=None, VMPowerState.POWERED_OFF: 'poweredOff', VMPowerState.SUSPENDED: 'suspended'} request.set_element_state(states[state]) - + task = self._server._proxy.MigrateVM_Task(request)._returnval vi_task = VITask(task, self._server) if sync_run: status = vi_task.wait_for_state([vi_task.STATE_SUCCESS, vi_task.STATE_ERROR]) if status == vi_task.STATE_ERROR: - raise VIException(vi_task.get_error_message(), - FaultTypes.TASK_ERROR) + raise VITaskException(vi_task.info.error) return return vi_task except (VI.ZSI.FaultException), e: raise VIApiException(e) - - def relocate(self, sync_run=True, priority='default', datastore=None, + + def relocate(self, sync_run=True, priority='default', datastore=None, resource_pool=None, host=None, transform=None, disks=None): """ - Cold or Hot relocates this virtual machine's virtual disks to a new + Cold or Hot relocates this virtual machine's virtual disks to a new datastore. - @sync_run: If True (default) waits for the task to finish, and returns + @sync_run: If True (default) waits for the task to finish, and returns (raises an exception if the task didn't succeed). If False the task is started an a VITask instance is returned. @priority: either 'default', 'high', or 'low': priority of the task that @@ -629,22 +623,22 @@ def relocate(self, sync_run=True, priority='default', datastore=None, hosts. @datastore: The target datastore to which the virtual machine's virtual disks are intended to migrate. - @resource_pool: The resource pool to which this virtual machine should - be attached. If the argument is not supplied, the current resource + @resource_pool: The resource pool to which this virtual machine should + be attached. If the argument is not supplied, the current resource pool of virtual machine is used. @host: The target host for the virtual machine. If not specified, * if resource pool is not specified, current host is used. - * if resource pool is specified, and the target pool represents a + * if resource pool is specified, and the target pool represents a stand-alone host, the host is used. * if resource pool is specified, and the target pool represents a DRS-enabled cluster, a host selected by DRS is used. - * if resource pool is specified and the target pool represents a - cluster without DRS enabled, an InvalidArgument exception be thrown. - @transform: If specified, the virtual machine's virtual disks are - transformed to the datastore using the specificed method; must be + * if resource pool is specified and the target pool represents a + cluster without DRS enabled, an InvalidArgument exception be thrown. + @transform: If specified, the virtual machine's virtual disks are + transformed to the datastore using the specificed method; must be either 'flat' or 'sparse'. @disks: Allows specifying the datastore location for each virtual disk. - A dictionary with the device id as key, and the datastore MOR as value + A dictionary with the device id as key, and the datastore MOR as value """ try: if priority not in ['default', 'low', 'high']: @@ -653,7 +647,7 @@ def relocate(self, sync_run=True, priority='default', datastore=None, FaultTypes.PARAMETER_ERROR) if transform and transform not in [None, 'flat', 'sparse']: raise VIException( - "transform, if set, must be either '%s' or '%s'." + "transform, if set, must be either '%s' or '%s'." % ('flat', 'sparse'), FaultTypes.PARAMETER_ERROR) request = VI.RelocateVM_TaskRequestMsg() _this = request.new__this(self._mor) @@ -691,7 +685,7 @@ def relocate(self, sync_run=True, priority='default', datastore=None, ds.set_attribute_type(disk_ds.get_attribute_type()) disk.Datastore = ds disk_spec.append(disk) - spec.Disk = disk_spec + spec.Disk = disk_spec request.set_element_priority(priority + "Priority") request.set_element_spec(spec) task = self._server._proxy.RelocateVM_Task(request)._returnval @@ -700,8 +694,7 @@ def relocate(self, sync_run=True, priority='default', datastore=None, status = vi_task.wait_for_state([vi_task.STATE_SUCCESS, vi_task.STATE_ERROR]) if status == vi_task.STATE_ERROR: - raise VIException(vi_task.get_error_message(), - FaultTypes.TASK_ERROR) + raise VITaskException(vi_task.info.error) return return vi_task except (VI.ZSI.FaultException), e: @@ -750,8 +743,7 @@ def revert_to_snapshot(self, sync_run=True, host=None): status = vi_task.wait_for_state([vi_task.STATE_SUCCESS, vi_task.STATE_ERROR]) if status == vi_task.STATE_ERROR: - raise VIException(vi_task.get_error_message(), - FaultTypes.TASK_ERROR) + raise VITaskException(vi_task.info.error) return return vi_task @@ -793,8 +785,7 @@ def revert_to_named_snapshot(self, name, sync_run=True, host=None): status = vi_task.wait_for_state([vi_task.STATE_SUCCESS, vi_task.STATE_ERROR]) if status == vi_task.STATE_ERROR: - raise VIException(vi_task.get_error_message(), - FaultTypes.TASK_ERROR) + raise VITaskException(vi_task.info.error) return return vi_task @@ -837,8 +828,7 @@ def revert_to_path(self, path, index=0, sync_run=True, host=None): status = vi_task.wait_for_state([vi_task.STATE_SUCCESS, vi_task.STATE_ERROR]) if status == vi_task.STATE_ERROR: - raise VIException(vi_task.get_error_message(), - FaultTypes.TASK_ERROR) + raise VITaskException(vi_task.info.error) return return vi_task @@ -865,7 +855,7 @@ def create_snapshot(self, name, sync_run=True, description=None, in the virtual machine. This assures that a disk snapshot represents a consistent state of the guest file systems. If the virtual machine is powered off or VMware Tools are not available, the quiesce flag - is ignored. + is ignored. """ try: request = VI.CreateSnapshot_TaskRequestMsg() @@ -885,8 +875,7 @@ def create_snapshot(self, name, sync_run=True, description=None, vi_task.STATE_ERROR]) self.refresh_snapshot_list() if status == vi_task.STATE_ERROR: - raise VIException(vi_task.get_error_message(), - FaultTypes.TASK_ERROR) + raise VITaskException(vi_task.info.error) return return vi_task @@ -1029,8 +1018,7 @@ def upgrade_tools(self, sync_run=True, params=None): status = vi_task.wait_for_state([vi_task.STATE_SUCCESS, vi_task.STATE_ERROR]) if status == vi_task.STATE_ERROR: - raise VIException(vi_task.get_error_message(), - FaultTypes.TASK_ERROR) + raise VITaskException(vi_task.info.error) return return vi_task @@ -1085,14 +1073,14 @@ def wait_for_tools(self, timeout=15): #-- GUEST AUTHENTICATION --# #--------------------------# def login_in_guest(self, user, password): - """Authenticates in the guest with the acquired credentials for use in + """Authenticates in the guest with the acquired credentials for use in subsequent guest operation calls.""" auth = VI.ns0.NamePasswordAuthentication_Def("NameAndPwd").pyclass() auth.set_element_interactiveSession(False) auth.set_element_username(user) auth.set_element_password(password) self.__validate_authentication(auth) - self._auth_obj = auth + self._auth_obj = auth #------------------------# #-- GUEST FILE METHODS --# @@ -1102,8 +1090,8 @@ def make_directory(self, path, create_parents=True): """ Creates a directory in the guest OS * path [string]: The complete path to the directory to be created. - * create_parents [bool]: Whether any parent directories are to be - created. Default: True + * create_parents [bool]: Whether any parent directories are to be + created. Default: True """ if not self._file_mgr: raise VIException("Files operations not supported on this server", @@ -1122,18 +1110,18 @@ def make_directory(self, path, create_parents=True): request.set_element_auth(self._auth_obj) request.set_element_directoryPath(path) request.set_element_createParentDirectories(create_parents) - + self._server._proxy.MakeDirectoryInGuest(request) except (VI.ZSI.FaultException), e: raise VIApiException(e) - + def move_directory(self, src_path, dst_path): """ Moves or renames a directory in the guest. * src_path [string]: The complete path to the directory to be moved. - * dst_path [string]: The complete path to the where the directory is + * dst_path [string]: The complete path to the where the directory is moved or its new name. It cannot be a path to an - existing directory or an existing file. + existing directory or an existing file. """ if not self._file_mgr: raise VIException("Files operations not supported on this server", @@ -1152,7 +1140,7 @@ def move_directory(self, src_path, dst_path): request.set_element_auth(self._auth_obj) request.set_element_srcDirectoryPath(src_path) request.set_element_dstDirectoryPath(dst_path) - + self._server._proxy.MoveDirectoryInGuest(request) except (VI.ZSI.FaultException), e: raise VIApiException(e) @@ -1161,9 +1149,9 @@ def delete_directory(self, path, recursive): """ Deletes a directory in the guest OS. * path [string]: The complete path to the directory to be deleted. - * recursive [bool]: If true, all subdirectories are also deleted. + * recursive [bool]: If true, all subdirectories are also deleted. If false, the directory must be empty for the - operation to succeed. + operation to succeed. """ if not self._file_mgr: raise VIException("Files operations not supported on this server", @@ -1182,24 +1170,24 @@ def delete_directory(self, path, recursive): request.set_element_auth(self._auth_obj) request.set_element_directoryPath(path) request.set_element_recursive(recursive) - + self._server._proxy.DeleteDirectoryInGuest(request) except (VI.ZSI.FaultException), e: raise VIApiException(e) - + def list_files(self, path, match_pattern=None): """ Returns information about files or directories in the guest. * path [string]: The complete path to the directory or file to query. - * match_pattern[string]: A filter for the return values. Match - patterns are specified using perl-compatible regular expressions. - If match_pattern isn't set, then the pattern '.*' is used. - + * match_pattern[string]: A filter for the return values. Match + patterns are specified using perl-compatible regular expressions. + If match_pattern isn't set, then the pattern '.*' is used. + Returns a list of dictionaries with these keys: - * path [string]: The complete path to the file - * size [long]: The file size in bytes + * path [string]: The complete path to the file + * size [long]: The file size in bytes * type [string]: 'directory', 'file', or 'symlink' - + """ if not self._file_mgr: raise VIException("Files operations not supported on this server", @@ -1233,21 +1221,21 @@ def ListFilesInGuest(path, match_pattern, index, max_results): return ret, finfo.Remaining except (VI.ZSI.FaultException), e: raise VIApiException(e) - + file_set, remaining = ListFilesInGuest(path, match_pattern, None, None) if remaining: - file_set.extend(ListFilesInGuest(path, match_pattern, + file_set.extend(ListFilesInGuest(path, match_pattern, len(file_set), remaining)[0]) - + return file_set def get_file(self, guest_path, local_path, overwrite=False): """ Initiates an operation to transfer a file from the guest. - * guest_path [string]: The complete path to the file inside the guest - that has to be transferred to the client. It + * guest_path [string]: The complete path to the file inside the guest + that has to be transferred to the client. It cannot be a path to a directory or a sym link. - * local_path [string]: The path to the local file to be created + * local_path [string]: The path to the local file to be created """ if not self._file_mgr: raise VIException("Files operations not supported on this server", @@ -1258,9 +1246,9 @@ def get_file(self, guest_path, local_path, overwrite=False): if os.path.exists(local_path) and not overwrite: raise VIException("Local file already exists", FaultTypes.PARAMETER_ERROR) - + from urlparse import urlparse - + try: request = VI.InitiateFileTransferFromGuestRequestMsg() _this = request.new__this(self._file_mgr) @@ -1271,8 +1259,8 @@ def get_file(self, guest_path, local_path, overwrite=False): request.set_element_vm(vm) request.set_element_auth(self._auth_obj) request.set_element_guestFilePath(guest_path) - - + + url = self._server._proxy.InitiateFileTransferFromGuest(request )._returnval.Url url = url.replace("*", urlparse(self._server._proxy.binding.url @@ -1281,7 +1269,7 @@ def get_file(self, guest_path, local_path, overwrite=False): import urllib2 req = urllib2.Request(url) r = urllib2.urlopen(req) - + CHUNK = 16 * 1024 fd = open(local_path, "wb") while True: @@ -1294,10 +1282,10 @@ def get_file(self, guest_path, local_path, overwrite=False): #I was getting a SSL Protocol error executing this on #python 2.6, but not with 2.5 urllib.urlretrieve(url, local_path) - + except (VI.ZSI.FaultException), e: raise VIApiException(e) - + def send_file(self, local_path, guest_path, overwrite=False): """ Initiates an operation to transfer a file to the guest. @@ -1352,17 +1340,17 @@ def send_file(self, local_path, guest_path, overwrite=False): if not resp.code == 200: raise VIException("File could not be send", FaultTypes.TASK_ERROR) - + def move_file(self, src_path, dst_path, overwrite=False): """ Renames a file in the guest. - * src_path [string]: The complete path to the original file or + * src_path [string]: The complete path to the original file or symbolic link to be moved. - * dst_path [string]: The complete path to the where the file is - renamed. It cannot be a path to an existing + * dst_path [string]: The complete path to the where the file is + renamed. It cannot be a path to an existing directory. * overwrite [bool]: Default False, if True the destination file is - clobbered. + clobbered. """ if not self._file_mgr: raise VIException("Files operations not supported on this server", @@ -1385,7 +1373,7 @@ def move_file(self, src_path, dst_path, overwrite=False): self._server._proxy.MoveFileInGuest(request) except (VI.ZSI.FaultException), e: raise VIApiException(e) - + def delete_file(self, path): """ Deletes a file in the guest OS @@ -1418,9 +1406,9 @@ def delete_file(self, path): def list_processes(self): """ List the processes running in the guest operating system, plus those - started by start_process that have recently completed. + started by start_process that have recently completed. The list contains dicctionary objects with these keys: - cmd_line [string]: The full command line + cmd_line [string]: The full command line end_time [datetime]: If the process was started using start_process then the process completion time will be available if queried within 5 minutes after it completes. None otherwise @@ -1428,9 +1416,9 @@ def list_processes(self): the process exit code will be available if queried within 5 minutes after it completes. None otherwise name [string]: The process name - owner [string]: The process owner + owner [string]: The process owner pid [long]: The process ID - start_time [datetime] The start time of the process + start_time [datetime] The start time of the process """ if not self._proc_mgr: raise VIException("Process operations not supported on this server", @@ -1467,7 +1455,7 @@ def get_environment_variables(self): """ Reads the environment variables from the guest OS (system user). Returns a dictionary where keys are the var names and the dict value is the var - value. + value. """ if not self._proc_mgr: raise VIException("Process operations not supported on this server", @@ -1497,17 +1485,17 @@ def start_process(self, program_path, args=None, env=None, cwd=None): args [list of strings]: The arguments to the program. env [dictionary]: environment variables to be set for the program being run. Eg. {'foo':'bar', 'varB':'B Value'} - cwd [string]: The absolute path of the working directory for the - program to be run. VMware recommends explicitly - setting the working directory for the program to be - run. If this value is unset or is an empty string, - the behavior depends on the guest operating system. - For Linux guest operating systems, if this value is - unset or is an empty string, the working directory + cwd [string]: The absolute path of the working directory for the + program to be run. VMware recommends explicitly + setting the working directory for the program to be + run. If this value is unset or is an empty string, + the behavior depends on the guest operating system. + For Linux guest operating systems, if this value is + unset or is an empty string, the working directory will be the home directory of the user associated with - the guest authentication. For other guest operating - systems, if this value is unset, the behavior is - unspecified. + the guest authentication. For other guest operating + systems, if this value is unset, the behavior is + unspecified. """ if not self._proc_mgr: raise VIException("Process operations not supported on this server", @@ -1526,16 +1514,16 @@ def start_process(self, program_path, args=None, env=None, cwd=None): request.set_element_auth(self._auth_obj) spec = request.new_spec() spec.set_element_programPath(program_path) - if env: spec.set_element_envVariables(["%s=%s" % (k,v) + if env: spec.set_element_envVariables(["%s=%s" % (k,v) for k,v in env.iteritems()]) if cwd: spec.set_element_workingDirectory(cwd) spec.set_element_arguments("") if args: import subprocess spec.set_element_arguments(subprocess.list2cmdline(args)) - + request.set_element_spec(spec) - + return self._server._proxy.StartProgramInGuest(request)._returnval except (VI.ZSI.FaultException), e: raise VIApiException(e) @@ -1568,7 +1556,7 @@ def terminate_process(self, pid): #-------------------# #-- OTHER METHODS --# #-------------------# - + def get_property(self, name='', from_cache=True): """"Returns the VM property with the given @name or None if the property doesn't exist or have not been set. The property is looked in the cached @@ -1616,14 +1604,14 @@ def get_resource_pool_name(self): if prop.Name == 'name': return prop.Val - + def set_extra_config(self, settings, sync_run=True): """Sets the advanced configuration settings (as the ones on the .vmx file). - * settings: a key-value pair dictionary with the settings to + * settings: a key-value pair dictionary with the settings to set/change * sync_run: if True (default) waits for the task to finish and returns - (raises an exception if the task didn't succeed). If False the task + (raises an exception if the task didn't succeed). If False the task is started and a VITask instance is returned E.g.: #prevent virtual disk shrinking @@ -1635,7 +1623,7 @@ def set_extra_config(self, settings, sync_run=True): _this = request.new__this(self._mor) _this.set_attribute_type(self._mor.get_attribute_type()) request.set_element__this(_this) - + spec = request.new_spec() extra_config = [] for k,v in settings.iteritems(): @@ -1644,7 +1632,7 @@ def set_extra_config(self, settings, sync_run=True): ec.set_element_value(str(v)) extra_config.append(ec) spec.set_element_extraConfig(extra_config) - + request.set_element_spec(spec) task = self._server._proxy.ReconfigVM_Task(request)._returnval vi_task = VITask(task, self._server) @@ -1652,8 +1640,7 @@ def set_extra_config(self, settings, sync_run=True): status = vi_task.wait_for_state([vi_task.STATE_SUCCESS, vi_task.STATE_ERROR]) if status == vi_task.STATE_ERROR: - raise VIException(vi_task.get_error_message(), - FaultTypes.TASK_ERROR) + raise VITaskException(vi_task.info.error) return return vi_task except (VI.ZSI.FaultException), e: @@ -1785,8 +1772,7 @@ def __delete_snapshot(self, mor, remove_children, sync_run): vi_task.STATE_ERROR]) self.refresh_snapshot_list() if status == vi_task.STATE_ERROR: - raise VIException(vi_task.get_error_message(), - FaultTypes.TASK_ERROR) + raise VITaskException(vi_task.info.error) return return vi_task @@ -1809,13 +1795,13 @@ def __validate_authentication(self, auth_obj): self._server._proxy.ValidateCredentialsInGuest(request) except (VI.ZSI.FaultException), e: raise VIApiException(e) - + def __update_properties(self): """Refreshes the properties retrieved from the virtual machine (i.e. name, path, snapshot tree, etc). To reduce traffic, all the properties are retrieved from one shot, if you expect changes, then you should call this method before other""" - + def update_devices(devices): for dev in devices: d = { @@ -1842,9 +1828,9 @@ def update_devices(devices): if hasattr(dev,'busNumber'): d['busNumber'] = dev.busNumber d['devices'] = getattr(dev,'device',[]) - + self._devices[dev.key] = d - + def update_disks(disks): new_disks = [] for disk in disks: @@ -1860,7 +1846,7 @@ def update_disks(disks): if f['type'] == 'diskDescriptor': store = f['name'] dev = self._devices[disk.key] - + new_disks.append({ 'device': dev, 'files': files, @@ -1870,7 +1856,7 @@ def update_disks(disks): 'label': dev['label'], }) self._disks = new_disks - + def update_files(files): for file_info in files: self._files[file_info.key] = { @@ -1879,16 +1865,16 @@ def update_files(files): 'size': file_info.size, 'type': file_info.type } - - + + try: self.properties = VIProperty(self._server, self._mor) except (VI.ZSI.FaultException), e: - raise VIApiException(e) - + raise VIApiException(e) + p = {} p['name'] = self.properties.name - + #------------------------# #-- UPDATE CONFIG INFO --# if hasattr(self.properties, "config"): @@ -1898,14 +1884,14 @@ def update_files(files): p['path'] = self.properties.config.files.vmPathName p['memory_mb'] = self.properties.config.hardware.memoryMB p['num_cpu'] = self.properties.config.hardware.numCPU - + if hasattr(self.properties.config.hardware, "device"): update_devices(self.properties.config.hardware.device) p['devices'] = self._devices - + #-----------------------# #-- UPDATE GUEST INFO --# - + if hasattr(self.properties, "guest"): if hasattr(self.properties.guest, "hostName"): p['hostname'] = self.properties.guest.hostName @@ -1920,12 +1906,12 @@ def update_files(files): 'ip_addresses':getattr(nic, "ipAddress", []), 'network':getattr(nic, "network", None) }) - + p['net'] = nics - + #------------------------# #-- UPDATE LAYOUT INFO --# - + if hasattr(self.properties, "layoutEx"): if hasattr(self.properties.layoutEx, "file"): update_files(self.properties.layoutEx.file) @@ -1933,30 +1919,30 @@ def update_files(files): if hasattr(self.properties.layoutEx, "disk"): update_disks(self.properties.layoutEx.disk) p['disks'] = self._disks - + self._properties = p - + #----------------------# #-- UPDATE SNAPSHOTS --# - + root_snapshots = [] if hasattr(self.properties, "snapshot"): if hasattr(self.properties.snapshot, "currentSnapshot"): self.__current_snapshot = \ self.properties.snapshot.currentSnapshot._obj - - + + for root_snap in self.properties.snapshot.rootSnapshotList: root = VISnapshot(root_snap) root_snapshots.append(root) self._root_snapshots = root_snapshots self.__create_snapshot_list() - + #-----------------------# #-- SET RESOURCE POOL --# if hasattr(self.properties, "resourcePool"): self._resource_pool = self.properties.resourcePool._obj - + class VMPowerState: POWERED_ON = 'POWERED ON' @@ -1984,4 +1970,4 @@ class ToolsStatus: RUNNING_OLD = 'RUNNING OLD' #Couldn't obtain the status of the VMwareTools. - UNKNOWN = 'UNKNOWN' \ No newline at end of file + UNKNOWN = 'UNKNOWN' From a668fb198ab282958f95fd723b494411815b5382 Mon Sep 17 00:00:00 2001 From: gentoo90 Date: Thu, 14 May 2015 21:04:28 +0300 Subject: [PATCH 09/11] Add _create_filter and _wait_for_updates methods to VIServer --- pysphere/ZSI/generate/pyclass.py | 5 +++ pysphere/ZSI/schema.py | 2 +- pysphere/vi_server.py | 67 ++++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+), 1 deletion(-) mode change 100644 => 100755 pysphere/ZSI/schema.py diff --git a/pysphere/ZSI/generate/pyclass.py b/pysphere/ZSI/generate/pyclass.py index 068ec3a..4c5b737 100644 --- a/pysphere/ZSI/generate/pyclass.py +++ b/pysphere/ZSI/generate/pyclass.py @@ -41,6 +41,10 @@ def _x(): return # nil.typecode = typecode or cls.typecode # return nil +def setElements(self, **kw): + for k, v in kw.iteritems(): + getattr(self, 'set_element_'+k)(v) + class pyclass_type(type): """Stability: Unstable @@ -126,6 +130,7 @@ def __new__(cls, classname, bases, classdict): # or immutable type simpleContent (float, str, etc) # if hasattr(typecode, 'attribute_typecode_dict'): + classdict['setElements'] = setElements attribute_typecode_dict = typecode.attribute_typecode_dict or {} for key,what in attribute_typecode_dict.iteritems(): get,_set = cls.__create_attr_functions_from_what(key, what) diff --git a/pysphere/ZSI/schema.py b/pysphere/ZSI/schema.py old mode 100644 new mode 100755 index 162e088..72bcdf9 --- a/pysphere/ZSI/schema.py +++ b/pysphere/ZSI/schema.py @@ -414,4 +414,4 @@ def WrapImmutable(cls, pyobj, what): WrapImmutable = classmethod(WrapImmutable) -from TC import Any, RegisterType \ No newline at end of file +from TC import Any, RegisterType diff --git a/pysphere/vi_server.py b/pysphere/vi_server.py index 83d95f1..0365b0c 100644 --- a/pysphere/vi_server.py +++ b/pysphere/vi_server.py @@ -750,6 +750,42 @@ def _retrieve_properties_traversal(self, property_names=[], except (VI.ZSI.FaultException), e: raise VIApiException(e) + def _create_filter(self, property_names=[], + from_node=None, obj_type='ManagedEntity', partial_updates=True): + """Creates filter with given parameters and returns its MOR""" + try: + if not from_node: + from_node = self._do_service_content.RootFolder + + elif isinstance(from_node, tuple) and len(from_node) == 2: + from_node = VIMor(from_node[0], from_node[1]) + elif not VIMor.is_mor(from_node): + raise VIException("from_node must be a MOR object or a " + "( mor_id, mor_type) tuple", + FaultTypes.PARAMETER_ERROR) + + request = VI.CreateFilterRequestMsg() + _this = request.new__this(self._do_service_content.PropertyCollector) + _this.set_attribute_type(MORTypes.PropertyCollector) + request.set_element__this(_this) + request.set_element_partialUpdates(partial_updates) + + spec = request.new_spec() + propSet = spec.new_propSet() + propSet.set_element_type(obj_type) + propSet.set_element_pathSet(property_names) + spec.set_element_propSet([propSet]) + + objects_set = self._get_traversal_objects_set(spec, from_node) + spec.set_element_objectSet(objects_set) + request.set_element_spec(spec) + + mor = self._proxy.CreateFilter(request)._returnval + return mor + + except (VI.ZSI.FaultException), e: + raise VIApiException(e) + def _retrieve_property_request(self): """Returns a base request object an call request method pointer for either RetrieveProperties or RetrievePropertiesEx depending on @@ -794,6 +830,37 @@ def call_retrieve_properties_ex(request): return request, call_pointer + def _wait_for_updates(self, version='', max_object_updates=None, max_wait_seconds=None): + + try: + if self.__api_version >= "4.1": + request = VI.WaitForUpdatesExRequestMsg() + options = request.new_options() + if max_object_updates is not None: + options.set_element_maxObjectUpdates(max_object_updates) + if max_wait_seconds is not None: + options.set_element_maxWaitSeconds(max_wait_seconds) + request.set_element_options(options) + method = self._proxy.WaitForUpdatesEx + else: + if max_wait_seconds is None: + print 'WaitForUpdates' + request = VI.WaitForUpdatesRequestMsg() + method = self._proxy.WaitForUpdates + else: + print 'CheckForUpdates' + request = VI.CheckForUpdatesRequestMsg() + method = self._proxy.CheckForUpdates + _this = request.new__this(self._do_service_content.PropertyCollector) + _this.set_attribute_type(MORTypes.PropertyCollector) + request.set_element__this(_this) + request.set_element_version(version) + retval = method(request)._returnval + return retval + + except (VI.ZSI.FaultException), e: + raise VIApiException(e) + def _get_traversal_objects_set(self, specSet, from_node): objects_set = [] do_ObjectSpec_objSet = specSet.new_objectSet() From 76640ef5b25c4729a4f7fbf2173dbe80876ca708 Mon Sep 17 00:00:00 2001 From: gentoo90 Date: Sun, 24 May 2015 20:52:43 +0300 Subject: [PATCH 10/11] Add filtering by pids to vm.list_processes() --- pysphere/vi_virtual_machine.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pysphere/vi_virtual_machine.py b/pysphere/vi_virtual_machine.py index 981c571..be0b4b1 100644 --- a/pysphere/vi_virtual_machine.py +++ b/pysphere/vi_virtual_machine.py @@ -1403,7 +1403,7 @@ def delete_file(self, path): #-- GUEST PROCESS METHODS --# #---------------------------# - def list_processes(self): + def list_processes(self, pids=[]): """ List the processes running in the guest operating system, plus those started by start_process that have recently completed. @@ -1419,6 +1419,8 @@ def list_processes(self): owner [string]: The process owner pid [long]: The process ID start_time [datetime] The start time of the process + * pids [list of numbers]: Optional list of pids to show. If skipped or + empty, all processes will be shown. """ if not self._proc_mgr: raise VIException("Process operations not supported on this server", @@ -1435,6 +1437,7 @@ def list_processes(self): vm.set_attribute_type(self._mor.get_attribute_type()) request.set_element_vm(vm) request.set_element_auth(self._auth_obj) + request.set_element_pids(pids) pinfo = self._server._proxy.ListProcessesInGuest(request)._returnval ret = [] for proc in pinfo: From aebc2b82e58857b9a0242fc277ced5125da2ad94 Mon Sep 17 00:00:00 2001 From: a-hadley Date: Tue, 1 Mar 2016 15:58:07 -0800 Subject: [PATCH 11/11] Changed cache to false by default Using a from_cache=True setting for clusters, datacenters, and resource pools causes issues when using multiple vcenter hosts, or when using multiple standalone esxi hosts. I expect the performance impact to be minimal, but I don't have a large cluster to test this against. --- pysphere/vi_server.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pysphere/vi_server.py b/pysphere/vi_server.py index 0365b0c..1eead5c 100644 --- a/pysphere/vi_server.py +++ b/pysphere/vi_server.py @@ -1014,7 +1014,7 @@ def _get_managed_objects(self, mo_type, from_mor=None): #---- DEPRECATED METHODS ----# - def _get_clusters(self, from_cache=True): + def _get_clusters(self, from_cache=False): """DEPRECATED: use get_clusters instead.""" import warnings from exceptions import DeprecationWarning @@ -1025,7 +1025,7 @@ def _get_clusters(self, from_cache=True): ret = self.get_clusters() return dict([(v,k) for k,v in ret.iteritems()]) - def _get_datacenters(self, from_cache=True): + def _get_datacenters(self, from_cache=False): """DEPRECATED: use get_datacenters instead.""" import warnings from exceptions import DeprecationWarning @@ -1036,7 +1036,7 @@ def _get_datacenters(self, from_cache=True): ret = self.get_datacenters() return dict([(v,k) for k,v in ret.iteritems()]) - def _get_resource_pools(self, from_cache=True): + def _get_resource_pools(self, from_cache=False): """DEPRECATED: use get_resource_pools instead.""" import warnings from exceptions import DeprecationWarning