summaryrefslogtreecommitdiffstats
path: root/agents/xenapi/fence_xenapi.py
blob: 10c8ee03e0b4085dd131224e470b6f34ec15f66b (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
#!@PYTHON@ -tt
#
#############################################################################
# Copyright 2011 Matthew Clark
# This file is part of fence-xenserver
#
# fence-xenserver is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# fence-xenserver is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <http://www.gnu.org/licenses/>.

# Please let me know if you are using this script so that I can work out
# whether I should continue support for it. mattjclark0407 at hotmail dot com
#############################################################################

#############################################################################
# It's only just begun...
# Current status: completely usable. This script is now working well and,
# has a lot of functionality as a result of the fencing.py library and the
# XenAPI libary.

#############################################################################
# Please let me know if you are using this script so that I can work out
# whether I should continue support for it. mattjclark0407 at hotmail dot com

import sys
import atexit
sys.path.append("@FENCEAGENTSLIBDIR@")
from fencing import *
from fencing import run_delay
import XenAPI

EC_BAD_SESSION = 1
# Find the status of the port given in the -U flag of options.
def get_power_fn(session, options):
	if "--verbose" in options:
		verbose = True
	else:
		verbose = False

	try:
		# Get a reference to the vm specified in the UUID or vm_name/port parameter
		vm = return_vm_reference(session, options)
		# Query the VM for its' associated parameters
		record = session.xenapi.VM.get_record(vm)
		# Check that we are not trying to manipulate a template or a control
		# domain as they show up as VM's with specific properties.
		if not record["is_a_template"] and not record["is_control_domain"]:
			status = record["power_state"]
			if verbose:
				print("UUID:", record["uuid"], "NAME:", record["name_label"], "POWER STATUS:", record["power_state"])
			# Note that the VM can be in the following states (from the XenAPI document)
			# Halted: VM is offline and not using any resources.
			# Paused: All resources have been allocated but the VM itself is paused and its vCPUs are not running
			# Running: Running
			# Paused: VM state has been saved to disk and it is nolonger running. Note that disks remain in-Use while
			# We want to make sure that we only return the status "off" if the machine is actually halted as the status
			# is checked before a fencing action. Only when the machine is Halted is it not consuming resources which
			# may include whatever you are trying to protect with this fencing action.
			return status == "Halted" and "off" or "on"
	except Exception as exn:
		print(str(exn))

	return "Error"

# Set the state of the port given in the -U flag of options.
def set_power_fn(session, options):
	try:
		# Get a reference to the vm specified in the UUID or vm_name/port parameter
		vm = return_vm_reference(session, options)
		# Query the VM for its' associated parameters
		record = session.xenapi.VM.get_record(vm)
		# Check that we are not trying to manipulate a template or a control
		# domain as they show up as VM's with specific properties.
		if not record["is_a_template"] and not record["is_control_domain"]:
			if options["--action"] == "on":
				# Start the VM
				session.xenapi.VM.start(vm, False, True)
			elif options["--action"] == "off":
				# Force shutdown the VM
				session.xenapi.VM.hard_shutdown(vm)
			elif options["--action"] == "reboot":
				# Force reboot the VM
				session.xenapi.VM.hard_reboot(vm)
	except Exception as exn:
		print(str(exn))

# Function to populate an array of virtual machines and their status
def get_outlet_list(session, options):
	result = {}
	if "--verbose" in options:
		verbose = True
	else:
		verbose = False

	try:
		# Return an array of all the VM's on the host
		vms = session.xenapi.VM.get_all()
		for vm in vms:
			# Query the VM for its' associated parameters
			record = session.xenapi.VM.get_record(vm)
			# Check that we are not trying to manipulate a template or a control
			# domain as they show up as VM's with specific properties.
			if not record["is_a_template"] and not record["is_control_domain"]:
				name = record["name_label"]
				uuid = record["uuid"]
				status = record["power_state"]
				result[uuid] = (name, status)
				if verbose:
					print("UUID:", record["uuid"], "NAME:", name, "POWER STATUS:", record["power_state"])
	except Exception as exn:
		print(str(exn))

	return result

# Function to initiate the XenServer session via the XenAPI library.
def connect_and_login(options):
	url = options["--session-url"]
	username = options["--username"]
	password = options["--password"]

	try:
		# Create the XML RPC session to the specified URL.
		session = XenAPI.Session(url)
		# Login using the supplied credentials.
		session.xenapi.login_with_password(username, password)
	except Exception as exn:
		print(str(exn))
		# http://sources.redhat.com/cluster/wiki/FenceAgentAPI says that for no connectivity
		# the exit value should be 1. It doesn't say anything about failed logins, so
		# until I hear otherwise it is best to keep this exit the same to make sure that
		# anything calling this script (that uses the same information in the web page
		# above) knows that this is an error condition, not a msg signifying a down port.
		sys.exit(EC_BAD_SESSION)
	return session

# return a reference to the VM by either using the UUID or the vm_name/port. If the UUID is set then
# this is tried first as this is the only properly unique identifier.
# Exceptions are not handled in this function, code that calls this must be ready to handle them.
def return_vm_reference(session, options):
	if "--verbose" in options:
		verbose = True
	else:
		verbose = False

	# Case where the UUID has been specified
	if "--uuid" in options:
		uuid = options["--uuid"].lower()
		# When using the -n parameter for name, we get an error message (in verbose
		# mode) that tells us that we didn't find a VM. To immitate that here we
		# need to catch and re-raise the exception produced by get_by_uuid.
		try:
			return session.xenapi.VM.get_by_uuid(uuid)
		except Exception:
			if verbose:
				print("No VM's found with a UUID of \"%s\"" % uuid)
			raise

	# Case where the vm_name/port has been specified
	if "--plug" in options:
		vm_name = options["--plug"]
		vm_arr = session.xenapi.VM.get_by_name_label(vm_name)
		# Need to make sure that we only have one result as the vm_name may
		# not be unique. Average case, so do it first.
		if len(vm_arr) == 1:
			return vm_arr[0]
		else:
			if len(vm_arr) == 0:
				if verbose:
					print("No VM's found with a name of \"%s\"" % vm_name)
				# NAME_INVALID used as the XenAPI throws a UUID_INVALID if it can't find
				# a VM with the specified UUID. This should make the output look fairly
				# consistent.
				raise Exception("NAME_INVALID")
			else:
				if verbose:
					print("Multiple VM's have the name \"%s\", use UUID instead" % vm_name)
				raise Exception("MULTIPLE_VMS_FOUND")

	# We should never get to this case as the input processing checks that either the UUID or
	# the name parameter is set. Regardless of whether or not a VM is found the above if
	# statements will return to the calling function (either by exception or by a reference
	# to the VM).
	raise Exception("VM_LOGIC_ERROR")

def main():

	device_opt = ["login", "passwd", "port", "no_login", "no_password", "session_url", "web"]

	atexit.register(atexit_handler)

	options = check_input(device_opt, process_input(device_opt))

	docs = {}
	docs["shortdesc"] = "Fence agent for Citrix XenServer over XenAPI"
	docs["longdesc"] = "\
fence_cxs is an I/O Fencing agent used on Citrix XenServer hosts. \
It uses the XenAPI, supplied by Citrix, to establish an XML-RPC session \
to a XenServer host. Once the session is established, further XML-RPC \
commands are issued in order to switch on, switch off, restart and query \
the status of virtual machines running on the host."
	docs["vendorurl"] = "http://www.xenproject.org"
	show_docs(options, docs)

	run_delay(options)

	xen_session = connect_and_login(options)
	result = fence_action(xen_session, options, set_power_fn, get_power_fn, get_outlet_list)

	sys.exit(result)

if __name__ == "__main__":
	main()