summaryrefslogtreecommitdiffstats
path: root/agents/vmware_soap/fence_vmware_soap.py
blob: 4a4ec1780c91719e574f4fb7e86c8cf1fe55b1e3 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
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()