summaryrefslogtreecommitdiffstats
path: root/agents/vmware_soap/fence_vmware_soap.py
diff options
context:
space:
mode:
Diffstat (limited to 'agents/vmware_soap/fence_vmware_soap.py')
-rw-r--r--agents/vmware_soap/fence_vmware_soap.py265
1 files changed, 265 insertions, 0 deletions
diff --git a/agents/vmware_soap/fence_vmware_soap.py b/agents/vmware_soap/fence_vmware_soap.py
new file mode 100644
index 0000000..4a4ec17
--- /dev/null
+++ b/agents/vmware_soap/fence_vmware_soap.py
@@ -0,0 +1,265 @@
+#!@PYTHON@ -tt
+
+import sys
+import shutil, tempfile, suds
+import logging, requests
+import atexit, signal
+sys.path.append("@FENCEAGENTSLIBDIR@")
+
+from suds.client import Client
+from suds.sudsobject import Property
+from suds.transport.http import HttpAuthenticated
+from suds.transport import Reply, TransportError
+from fencing import *
+from fencing import fail, fail_usage, EC_STATUS, EC_LOGIN_DENIED, EC_INVALID_PRIVILEGES, EC_WAITING_ON, EC_WAITING_OFF
+from fencing import run_delay
+
+options_global = None
+conn_global = None
+
+class RequestsTransport(HttpAuthenticated):
+ def __init__(self, **kwargs):
+ self.cert = kwargs.pop('cert', None)
+ self.verify = kwargs.pop('verify', True)
+ self.session = requests.Session()
+ # super won't work because not using new style class
+ HttpAuthenticated.__init__(self, **kwargs)
+
+ def send(self, request):
+ self.addcredentials(request)
+ resp = self.session.post(request.url, data=request.message, headers=request.headers, cert=self.cert, verify=self.verify)
+ result = Reply(resp.status_code, resp.headers, resp.content)
+ return result
+
+def soap_login(options):
+ run_delay(options)
+
+ if "--ssl-secure" in options or "--ssl-insecure" in options:
+ if "--ssl-insecure" in options:
+ import ssl
+ import urllib3
+ if hasattr(ssl, '_create_unverified_context'):
+ ssl._create_default_https_context = ssl._create_unverified_context
+ urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
+ verify = False
+ else:
+ verify = True
+ url = "https://"
+ else:
+ verify = False
+ url = "http://"
+
+ url += options["--ip"] + ":" + str(options["--ipport"]) + "/sdk"
+
+ tmp_dir = tempfile.mkdtemp()
+ tempfile.tempdir = tmp_dir
+ atexit.register(remove_tmp_dir, tmp_dir)
+
+ try:
+ headers = {"Content-Type" : "text/xml;charset=UTF-8", "SOAPAction" : "vim25"}
+ login_timeout = int(options["--login-timeout"]) or 15
+ conn = Client(url + "/vimService.wsdl", location=url, transport=RequestsTransport(verify=verify), headers=headers, timeout=login_timeout)
+
+ mo_ServiceInstance = Property('ServiceInstance')
+ mo_ServiceInstance._type = 'ServiceInstance'
+ ServiceContent = conn.service.RetrieveServiceContent(mo_ServiceInstance)
+ mo_SessionManager = Property(ServiceContent.sessionManager.value)
+ mo_SessionManager._type = 'SessionManager'
+
+ conn.service.Login(mo_SessionManager, options["--username"], options["--password"])
+ except requests.exceptions.SSLError as ex:
+ fail_usage("Server side certificate verification failed: %s" % ex)
+ except Exception as e:
+ logging.error("Server side certificate verification failed: {}".format(str(e)))
+ fail(EC_LOGIN_DENIED)
+
+ options["ServiceContent"] = ServiceContent
+ options["mo_SessionManager"] = mo_SessionManager
+ return conn
+
+def process_results(results, machines, uuid, mappingToUUID):
+ for m in results.objects:
+ info = {}
+ for i in m.propSet:
+ info[i.name] = i.val
+ # Prevent error KeyError: 'config.uuid' when reaching systems which P2V failed,
+ # since these systems don't have a valid UUID
+ if "config.uuid" in info:
+ machines[info["name"]] = (info["config.uuid"], info["summary.runtime.powerState"])
+ uuid[info["config.uuid"]] = info["summary.runtime.powerState"]
+ mappingToUUID[m.obj.value] = info["config.uuid"]
+
+ return (machines, uuid, mappingToUUID)
+
+def get_power_status(conn, options):
+ mo_ViewManager = Property(options["ServiceContent"].viewManager.value)
+ mo_ViewManager._type = "ViewManager"
+
+ mo_RootFolder = Property(options["ServiceContent"].rootFolder.value)
+ mo_RootFolder._type = "Folder"
+
+ mo_PropertyCollector = Property(options["ServiceContent"].propertyCollector.value)
+ mo_PropertyCollector._type = 'PropertyCollector'
+
+ ContainerView = conn.service.CreateContainerView(mo_ViewManager, recursive=1,
+ container=mo_RootFolder, type=['VirtualMachine'])
+ mo_ContainerView = Property(ContainerView.value)
+ mo_ContainerView._type = "ContainerView"
+
+ FolderTraversalSpec = conn.factory.create('ns0:TraversalSpec')
+ FolderTraversalSpec.name = "traverseEntities"
+ FolderTraversalSpec.path = "view"
+ FolderTraversalSpec.skip = False
+ FolderTraversalSpec.type = "ContainerView"
+
+ objSpec = conn.factory.create('ns0:ObjectSpec')
+ objSpec.obj = mo_ContainerView
+ objSpec.selectSet = [FolderTraversalSpec]
+ objSpec.skip = True
+
+ propSpec = conn.factory.create('ns0:PropertySpec')
+ propSpec.all = False
+ propSpec.pathSet = ["name", "summary.runtime.powerState", "config.uuid"]
+ propSpec.type = "VirtualMachine"
+
+ propFilterSpec = conn.factory.create('ns0:PropertyFilterSpec')
+ propFilterSpec.propSet = [propSpec]
+ propFilterSpec.objectSet = [objSpec]
+
+ try:
+ raw_machines = conn.service.RetrievePropertiesEx(mo_PropertyCollector, propFilterSpec)
+ except Exception as e:
+ logging.error("Failed: {}".format(str(e)))
+ fail(EC_STATUS)
+
+ (machines, uuid, mappingToUUID) = process_results(raw_machines, {}, {}, {})
+
+ # Probably need to loop over the ContinueRetreive if there are more results after 1 iteration.
+ while hasattr(raw_machines, 'token'):
+ try:
+ raw_machines = conn.service.ContinueRetrievePropertiesEx(mo_PropertyCollector, raw_machines.token)
+ except Exception as e:
+ logging.error("Failed: {}".format(str(e)))
+ fail(EC_STATUS)
+ (more_machines, more_uuid, more_mappingToUUID) = process_results(raw_machines, {}, {}, {})
+ machines.update(more_machines)
+ uuid.update(more_uuid)
+ mappingToUUID.update(more_mappingToUUID)
+ # Do not run unnecessary SOAP requests
+ if "--uuid" in options and options["--uuid"] in uuid:
+ break
+
+ if ["list", "monitor"].count(options["--action"]) == 1:
+ return machines
+ else:
+ if "--uuid" not in options:
+ if options["--plug"].startswith('/'):
+ ## Transform InventoryPath to UUID
+ mo_SearchIndex = Property(options["ServiceContent"].searchIndex.value)
+ mo_SearchIndex._type = "SearchIndex"
+
+ vm = conn.service.FindByInventoryPath(mo_SearchIndex, options["--plug"])
+
+ try:
+ options["--uuid"] = mappingToUUID[vm.value]
+ except KeyError:
+ fail(EC_STATUS)
+ except AttributeError:
+ fail(EC_STATUS)
+ else:
+ ## Name of virtual machine instead of path
+ ## warning: if you have same names of machines this won't work correctly
+ try:
+ (options["--uuid"], _) = machines[options["--plug"]]
+ except KeyError:
+ fail(EC_STATUS)
+ except AttributeError:
+ fail(EC_STATUS)
+
+ try:
+ if uuid[options["--uuid"]] == "poweredOn":
+ return "on"
+ else:
+ return "off"
+ except KeyError:
+ fail(EC_STATUS)
+
+def set_power_status(conn, options):
+ mo_SearchIndex = Property(options["ServiceContent"].searchIndex.value)
+ mo_SearchIndex._type = "SearchIndex"
+ vm = conn.service.FindByUuid(mo_SearchIndex, vmSearch=1, uuid=options["--uuid"])
+
+ mo_machine = Property(vm.value)
+ mo_machine._type = "VirtualMachine"
+
+ try:
+ if options["--action"] == "on":
+ conn.service.PowerOnVM_Task(mo_machine)
+ else:
+ conn.service.PowerOffVM_Task(mo_machine)
+ except suds.WebFault as ex:
+ if (str(ex).find("Permission to perform this operation was denied")) >= 0:
+ fail(EC_INVALID_PRIVILEGES)
+ else:
+ if options["--action"] == "on":
+ fail(EC_WAITING_ON)
+ else:
+ fail(EC_WAITING_OFF)
+
+def remove_tmp_dir(tmp_dir):
+ shutil.rmtree(tmp_dir)
+
+def logout():
+ try:
+ conn_global.service.Logout(options_global["mo_SessionManager"])
+ except Exception:
+ pass
+
+def signal_handler(signum, frame):
+ raise Exception("Signal \"%d\" received which has triggered an exit of the process." % signum)
+
+def main():
+ global options_global
+ global conn_global
+ device_opt = ["ipaddr", "login", "passwd", "web", "ssl", "notls", "port"]
+
+ atexit.register(atexit_handler)
+ atexit.register(logout)
+
+ signal.signal(signal.SIGTERM, signal_handler)
+
+ options_global = check_input(device_opt, process_input(device_opt))
+
+ ##
+ ## Fence agent specific defaults
+ #####
+ docs = {}
+ docs["shortdesc"] = "Fence agent for VMWare over SOAP API"
+ docs["longdesc"] = "fence_vmware_soap is an I/O Fencing agent \
+which can be used with the virtual machines managed by VMWare products \
+that have SOAP API v4.1+. \
+\n.P\n\
+Name of virtual machine (-n / port) has to be used in inventory path \
+format (e.g. /datacenter/vm/Discovered virtual machine/myMachine). \
+In the cases when name of yours VM is unique you can use it instead. \
+Alternatively you can always use UUID to access virtual machine."
+ docs["vendorurl"] = "http://www.vmware.com"
+ show_docs(options_global, docs)
+
+ logging.basicConfig(level=logging.INFO)
+ logging.getLogger('suds.client').setLevel(logging.CRITICAL)
+ logging.getLogger("requests").setLevel(logging.CRITICAL)
+ logging.getLogger("urllib3").setLevel(logging.CRITICAL)
+
+ ##
+ ## Operate the fencing device
+ ####
+ conn_global = soap_login(options_global)
+
+ result = fence_action(conn_global, options_global, set_power_status, get_power_status, get_power_status)
+
+ ## Logout from system is done automatically via atexit()
+ sys.exit(result)
+
+if __name__ == "__main__":
+ main()