1    	#!@PYTHON@ -tt
2    	
3    	#
4    	# Requires the googleapiclient and oauth2client
5    	# RHEL 7.x: google-api-python-client==1.6.7 python-gflags==2.0 pyasn1==0.4.8 rsa==3.4.2 pysocks==1.7.1 httplib2==0.19.0
6    	# RHEL 8.x: pysocks==1.7.1 httplib2==0.19.0
7    	# SLES 12.x: python-google-api-python-client python-oauth2client python-oauth2client-gce pysocks==1.7.1 httplib2==0.19.0
8    	# SLES 15.x: python3-google-api-python-client python3-oauth2client pysocks==1.7.1 httplib2==0.19.0
9    	#
10   	
11   	import atexit
12   	import logging
13   	import json
14   	import re
15   	import os
16   	import socket
17   	import sys
18   	import time
19   	
20   	from ssl import SSLError
21   	
22   	if sys.version_info >= (3, 0):
23   	  # Python 3 imports.
24   	  import urllib.parse as urlparse
25   	  import urllib.request as urlrequest
26   	else:
27   	  # Python 2 imports.
28   	  import urllib as urlparse
29   	  import urllib2 as urlrequest
30   	sys.path.append("@FENCEAGENTSLIBDIR@")
31   	
32   	from fencing import fail_usage, run_delay, all_opt, atexit_handler, check_input, process_input, show_docs, fence_action, run_command
33   	try:
34   	  import httplib2
35   	  import googleapiclient.discovery
36   	  import socks
37   	  try:
38   	    from google.oauth2.credentials import Credentials as GoogleCredentials
39   	  except ImportError:
40   	    from oauth2client.client import GoogleCredentials
41   	except Exception as e:
42   	    from fencing import fail_import_if_not_metadata_or_help_action
43   	    fail_import_if_not_metadata_or_help_action("Failed to import Google Cloud dependencies", e)
44   	
45   	VERSION = '1.0.5'
46   	ACTION_IDS = {
47   			'on': 1, 'off': 2, 'reboot': 3, 'status': 4, 'list': 5, 'list-status': 6,
48   			'monitor': 7, 'metadata': 8, 'manpage': 9, 'validate-all': 10
49   	}
50   	USER_AGENT = 'sap-core-eng/fencegce/%s/%s/ACTION/%s'
51   	METADATA_SERVER = 'http://metadata.google.internal/computeMetadata/v1/'
52   	METADATA_HEADERS = {'Metadata-Flavor': 'Google'}
53   	INSTANCE_LINK = 'https://www.googleapis.com/compute/v1/projects/{}/zones/{}/instances/{}'
54   	
55   	def run_on_fail(options):
56   		if "--runonfail" in options:
57   			run_command(options, options["--runonfail"])
58   	
59   	def fail_fence_agent(options, message):
60   		run_on_fail(options)
61   		fail_usage(message)
62   	
63   	def raise_fence_agent(options, message):
64   		run_on_fail(options)
65   		raise Exception(message)
66   	
67   	#
68   	# Will use baremetalsolution setting or the environment variable
69   	# FENCE_GCE_URI_REPLACEMENTS to replace the uri for calls to *.googleapis.com.
70   	#
71   	def replace_api_uri(options, http_request):
72   		uri_replacements = []
73   		# put any env var replacements first, then baremetalsolution if in options
74   		if "FENCE_GCE_URI_REPLACEMENTS" in os.environ:
75   			logging.debug("FENCE_GCE_URI_REPLACEMENTS environment variable exists")
76   			env_uri_replacements = os.environ["FENCE_GCE_URI_REPLACEMENTS"]
77   			try:
78   				uri_replacements_json = json.loads(env_uri_replacements)
79   				if isinstance(uri_replacements_json, list):
80   					uri_replacements = uri_replacements_json
81   				else:
82   					logging.warning("FENCE_GCE_URI_REPLACEMENTS exists, but is not a JSON List")
83   			except ValueError as e:
84   				logging.warning("FENCE_GCE_URI_REPLACEMENTS exists but is not valid JSON")
85   		if "--baremetalsolution" in options:
86   			uri_replacements.append(
87   				{
88   					"matchlength": 4,
89   					"match": r"https://compute.googleapis.com/compute/v1/projects/(.*)/zones/(.*)/instances/(.*)/reset(.*)",
90   					"replace": r"https://baremetalsolution.googleapis.com/v1/projects/\1/locations/\2/instances/\3:resetInstance\4"
91   				})
92   		for uri_replacement in uri_replacements:
93   			# each uri_replacement should have matchlength, match, and replace
94   			if "matchlength" not in uri_replacement or "match" not in uri_replacement or "replace" not in uri_replacement:
95   				logging.warning("FENCE_GCE_URI_REPLACEMENTS missing matchlength, match, or replace in %s" % uri_replacement)
96   				continue
97   			match = re.match(uri_replacement["match"], http_request.uri)
98   			if match is None or len(match.groups()) != uri_replacement["matchlength"]:
99   				continue
100  			replaced_uri = re.sub(uri_replacement["match"], uri_replacement["replace"], http_request.uri)
101  			match = re.match(r"https:\/\/.*.googleapis.com", replaced_uri)
102  			if match is None or match.start() != 0:
103  				logging.warning("FENCE_GCE_URI_REPLACEMENTS replace is not "
104  					"targeting googleapis.com, ignoring it: %s" % replaced_uri)
105  				continue
106  			logging.debug("Replacing googleapis uri %s with %s" % (http_request.uri, replaced_uri))
107  			http_request.uri = replaced_uri
108  			break
109  		return http_request
110  	
111  	def retry_api_execute(options, http_request):
112  		replaced_http_request = replace_api_uri(options, http_request)
113  		action = ACTION_IDS[options["--action"]] if options["--action"] in ACTION_IDS else 0
114  		try:
115  			user_agent_header = USER_AGENT % (VERSION, options["image"], action)
116  		except ValueError:
117  			user_agent_header = USER_AGENT % (VERSION, options["image"], 0)
118  		replaced_http_request.headers["User-Agent"] = user_agent_header
119  		logging.debug("User agent set as %s" % (user_agent_header))
120  		retries = 3
121  		if options.get("--retries"):
122  			retries = int(options.get("--retries"))
123  		retry_sleep = 5
124  		if options.get("--retrysleep"):
125  			retry_sleep = int(options.get("--retrysleep"))
126  		retry = 0
127  		current_err = None
128  		while retry <= retries:
129  			if retry > 0:
130  				time.sleep(retry_sleep)
131  			try:
132  				return replaced_http_request.execute()
133  			except Exception as err:
134  				current_err = err
135  				logging.warning("Could not execute api call to: %s, retry: %s, "
136  					"err: %s" % (replaced_http_request.uri, retry, str(err)))
137  			retry += 1
138  		raise current_err
139  	
140  	
141  	def translate_status(instance_status):
142  		"Returns on | off | unknown."
143  		if instance_status == "RUNNING":
144  			return "on"
145  		elif instance_status == "TERMINATED":
146  			return "off"
147  		return "unknown"
148  	
149  	
150  	def get_nodes_list(conn, options):
151  		result = {}
152  		plug = options["--plug"] if "--plug" in options else ""
153  		zones = options["--zone"] if "--zone" in options else ""
154  		filter = "name="+plug if plug != "" else ""
155  		max_results = 1 if options.get("--action") == "monitor" else 500
156  		if not zones:
157  			zones = get_zone(conn, options, plug) if "--plugzonemap" not in options else options["--plugzonemap"][plug]
158  		try:
159  			for zone in zones.split(","):
160  				request = conn.instances().list(
161  					project=options["--project"],
162  					zone=zone,
163  					filter=filter,
164  					maxResults=max_results)
165  			while request is not None:
166  				instanceList = retry_api_execute(options, request)
167  				if "items" not in instanceList:
168  					break
169  				for instance in instanceList["items"]:
170  					result[instance["id"]] = (instance["name"], translate_status(instance["status"]))
171  				request = conn.instances().list_next(previous_request=request, previous_response=instanceList)
172  		except Exception as err:
173  			fail_fence_agent(options, "Failed: get_nodes_list: {}".format(str(err)))
174  	
175  		return result
176  	
177  	
178  	def get_power_status(conn, options):
179  		logging.debug("get_power_status")
180  		# if this is bare metal we need to just send back the opposite of the
181  		# requested action: if on send off, if off send on
182  		if "--baremetalsolution" in options:
183  			if options.get("--action") == "on":
184  				return "off"
185  			else:
186  				return "on"
187  		# If zone is not listed for an entry we attempt to get it automatically
188  		instance = options["--plug"]
189  		zone = get_zone(conn, options, instance) if "--plugzonemap" not in options else options["--plugzonemap"][instance]
190  		instance_status = get_instance_power_status(conn, options, instance, zone)
191  		# If any of the instances do not match the intended status we return the
192  		# the opposite status so that the fence agent can change it.
193  		if instance_status != options.get("--action"):
194  			return instance_status
195  	
196  		return options.get("--action")
197  	
198  	
199  	def get_instance_power_status(conn, options, instance, zone):
200  		try:
201  			instance = retry_api_execute(
202  					options,
203  					conn.instances().get(project=options["--project"], zone=zone, instance=instance))
204  			return translate_status(instance["status"])
205  		except Exception as err:
206  			fail_fence_agent(options, "Failed: get_instance_power_status: {}".format(str(err)))
207  	
208  	
209  	def check_for_existing_operation(conn, options, instance, zone, operation_type):
210  		logging.debug("check_for_existing_operation")
211  		if "--baremetalsolution" in options:
212  			# There is no API for checking in progress operations
213  			return False
214  	
215  		project = options["--project"]
216  		target_link = INSTANCE_LINK.format(project, zone, instance)
217  		query_filter = '(targetLink = "{}") AND (operationType = "{}") AND (status = "RUNNING")'.format(target_link, operation_type)
218  		result = retry_api_execute(
219  				options,
220  				conn.zoneOperations().list(project=project, zone=zone, filter=query_filter, maxResults=1))
221  	
222  		if "items" in result and result["items"]:
223  			logging.info("Existing %s operation found", operation_type)
224  			return result["items"][0]
225  	
226  	
227  	def wait_for_operation(conn, options, zone, operation):
228  		if 'name' not in operation:
229  			logging.warning('Cannot wait for operation to complete, the'
230  			' requested operation will continue asynchronously')
231  			return False
232  	
233  		wait_time = 0
234  		project = options["--project"]
235  		while True:
236  			result = retry_api_execute(options, conn.zoneOperations().get(
237  				project=project,
238  				zone=zone,
239  				operation=operation['name']))
240  			if result['status'] == 'DONE':
241  				if 'error' in result:
CID (unavailable; MK=34d82493c75a3d798dc1d77350c4c77f) (#1 of 1): Copy-paste error (COPY_PASTE_ERROR):
(2) Event copy_paste_error: "options" in "raise_fence_agent(options, result["error"])" looks like a copy-paste error.
(3) Event remediation: Should it say "result" instead?
Also see events: [original]
242  					raise_fence_agent(options, result['error'])
243  				return True
244  	
245  			if "--errortimeout" in options and wait_time > int(options["--errortimeout"]):
246  				raise_fence_agent(options, "Operation did not complete before the timeout.")
247  	
248  			if "--warntimeout" in options and wait_time > int(options["--warntimeout"]):
249  				logging.warning("Operation did not complete before the timeout.")
250  				if "--runonwarn" in options:
(1) Event original: "run_command(options, options["--runonwarn"])" looks like the original copy.
Also see events: [copy_paste_error][remediation]
251  					run_command(options, options["--runonwarn"])
252  				return False
253  	
254  			wait_time = wait_time + 1
255  			time.sleep(1)
256  	
257  	
258  	def set_power_status(conn, options):
259  		logging.debug("set_power_status")
260  		instance = options["--plug"]
261  		# If zone is not listed for an entry we attempt to get it automatically
262  		zone = get_zone(conn, options, instance) if "--plugzonemap" not in options else options["--plugzonemap"][instance]
263  		set_instance_power_status(conn, options, instance, zone, options["--action"])
264  	
265  	
266  	def set_instance_power_status(conn, options, instance, zone, action):
267  		logging.info("Setting power status of %s in zone %s", instance, zone)
268  		project = options["--project"]
269  	
270  		try:
271  			if action == "off":
272  				logging.info("Issuing poweroff of %s in zone %s", instance, zone)
273  				operation = check_for_existing_operation(conn, options, instance, zone, "stop")
274  				if operation and "--earlyexit" in options:
275  					return
276  				if not operation:
277  					operation = retry_api_execute(
278  							options,
279  							conn.instances().stop(project=project, zone=zone, instance=instance))
280  				logging.info("Poweroff command completed, waiting for the operation to complete")
281  				if wait_for_operation(conn, options, zone, operation):
282  					logging.info("Poweroff of %s in zone %s complete", instance, zone)
283  			elif action == "on":
284  				logging.info("Issuing poweron of %s in zone %s", instance, zone)
285  				operation = check_for_existing_operation(conn, options, instance, zone, "start")
286  				if operation and "--earlyexit" in options:
287  					return
288  				if not operation:
289  					operation = retry_api_execute(
290  							options,
291  							conn.instances().start(project=project, zone=zone, instance=instance))
292  				if wait_for_operation(conn, options, zone, operation):
293  					logging.info("Poweron of %s in zone %s complete", instance, zone)
294  		except Exception as err:
295  			fail_fence_agent(options, "Failed: set_instance_power_status: {}".format(str(err)))
296  	
297  	def power_cycle(conn, options):
298  		logging.debug("power_cycle")
299  		instance = options["--plug"]
300  		# If zone is not listed for an entry we attempt to get it automatically
301  		zone = get_zone(conn, options, instance) if "--plugzonemap" not in options else options["--plugzonemap"][instance]
302  		return power_cycle_instance(conn, options, instance, zone)
303  	
304  	
305  	def power_cycle_instance(conn, options, instance, zone):
306  		logging.info("Issuing reset of %s in zone %s", instance, zone)
307  		project = options["--project"]
308  	
309  		try:
310  			operation = check_for_existing_operation(conn, options, instance, zone, "reset")
311  			if operation and "--earlyexit" in options:
312  				return True
313  			if not operation:
314  				operation = retry_api_execute(
315  						options,
316  						conn.instances().reset(project=project, zone=zone, instance=instance))
317  			logging.info("Reset command sent, waiting for the operation to complete")
318  			if wait_for_operation(conn, options, zone, operation):
319  				logging.info("Reset of %s in zone %s complete", instance, zone)
320  			return True
321  		except Exception as err:
322  			logging.exception("Failed: power_cycle")
323  			raise err
324  	
325  	
326  	def get_zone(conn, options, instance):
327  		logging.debug("get_zone");
328  		project = options['--project']
329  		fl = 'name="%s"' % instance
330  		request = replace_api_uri(options, conn.instances().aggregatedList(project=project, filter=fl))
331  		while request is not None:
332  			response = request.execute()
333  			zones = response.get('items', {})
334  			for zone in zones.values():
335  				for inst in zone.get('instances', []):
336  					if inst['name'] == instance:
337  						return inst['zone'].split("/")[-1]
338  			request = replace_api_uri(options, conn.instances().aggregatedList_next(
339  					previous_request=request, previous_response=response))
340  		raise_fence_agent(options, "Unable to find instance %s" % (instance))
341  	
342  	
343  	def get_metadata(metadata_key, params=None, timeout=None):
344  		"""Performs a GET request with the metadata headers.
345  	
346  		Args:
347  			metadata_key: string, the metadata to perform a GET request on.
348  			params: dictionary, the query parameters in the GET request.
349  			timeout: int, timeout in seconds for metadata requests.
350  	
351  		Returns:
352  			HTTP response from the GET request.
353  	
354  		Raises:
355  			urlerror.HTTPError: raises when the GET request fails.
356  		"""
357  		logging.debug("get_metadata");
358  		timeout = timeout or 60
359  		metadata_url = os.path.join(METADATA_SERVER, metadata_key)
360  		params = urlparse.urlencode(params or {})
361  		url = '%s?%s' % (metadata_url, params)
362  		request = urlrequest.Request(url, headers=METADATA_HEADERS)
363  		request_opener = urlrequest.build_opener(urlrequest.ProxyHandler({}))
364  		return request_opener.open(request, timeout=timeout * 1.1).read().decode("utf-8")
365  	
366  	
367  	def define_new_opts():
368  		all_opt["zone"] = {
369  			"getopt" : ":",
370  			"longopt" : "zone",
371  			"help" : "--zone=[name]                  Zone, e.g. us-central1-b",
372  			"shortdesc" : "Zone.",
373  			"required" : "0",
374  			"order" : 2
375  		}
376  		all_opt["project"] = {
377  			"getopt" : ":",
378  			"longopt" : "project",
379  			"help" : "--project=[name]               Project ID",
380  			"shortdesc" : "Project ID.",
381  			"required" : "0",
382  			"order" : 3
383  		}
384  		all_opt["stackdriver-logging"] = {
385  			"getopt" : "",
386  			"longopt" : "stackdriver-logging",
387  			"help" : "--stackdriver-logging          Enable Logging to Stackdriver",
388  			"shortdesc" : "Stackdriver-logging support.",
389  			"longdesc" : "If enabled IP failover logs will be posted to stackdriver logging.",
390  			"required" : "0",
391  			"order" : 4
392  		}
393  		all_opt["baremetalsolution"] = {
394  			"getopt" : "",
395  			"longopt" : "baremetalsolution",
396  			"help" : "--baremetalsolution            Enable on bare metal",
397  			"shortdesc" : "If enabled this is a bare metal offering from google.",
398  			"required" : "0",
399  			"order" : 5
400  		}
401  		all_opt["apitimeout"] = {
402  			"getopt" : ":",
403  			"type" : "second",
404  			"longopt" : "apitimeout",
405  			"help" : "--apitimeout=[seconds]         Timeout to use for API calls",
406  			"shortdesc" : "Timeout in seconds to use for API calls, default is 60.",
407  			"required" : "0",
408  			"default" : 60,
409  			"order" : 6
410  		}
411  		all_opt["retries"] = {
412  			"getopt" : ":",
413  			"type" : "integer",
414  			"longopt" : "retries",
415  			"help" : "--retries=[retries]            Number of retries on failure for API calls",
416  			"shortdesc" : "Number of retries on failure for API calls, default is 3.",
417  			"required" : "0",
418  			"default" : 3,
419  			"order" : 7
420  		}
421  		all_opt["retrysleep"] = {
422  			"getopt" : ":",
423  			"type" : "second",
424  			"longopt" : "retrysleep",
425  			"help" : "--retrysleep=[seconds]         Time to sleep between API retries",
426  			"shortdesc" : "Time to sleep in seconds between API retries, default is 5.",
427  			"required" : "0",
428  			"default" : 5,
429  			"order" : 8
430  		}
431  		all_opt["serviceaccount"] = {
432  			"getopt" : ":",
433  			"longopt" : "serviceaccount",
434  			"help" : "--serviceaccount=[filename]    Service account json file location e.g. serviceaccount=/somedir/service_account.json",
435  			"shortdesc" : "Service Account to use for authentication to the google cloud APIs.",
436  			"required" : "0",
437  			"order" : 9
438  		}
439  		all_opt["plugzonemap"] = {
440  			"getopt" : ":",
441  			"longopt" : "plugzonemap",
442  			"help" : "--plugzonemap=[plugzonemap]    Comma separated zone map when fencing multiple plugs",
443  			"shortdesc" : "Comma separated zone map when fencing multiple plugs.",
444  			"required" : "0",
445  			"order" : 10
446  		}
447  		all_opt["proxyhost"] = {
448  			"getopt" : ":",
449  			"longopt" : "proxyhost",
450  			"help" : "--proxyhost=[proxy_host]       The proxy host to use, if one is needed to access the internet (Example: 10.122.0.33)",
451  			"shortdesc" : "If a proxy is used for internet access, the proxy host should be specified.",
452  			"required" : "0",
453  			"order" : 11
454  		}
455  		all_opt["proxyport"] = {
456  			"getopt" : ":",
457  			"type" : "integer",
458  			"longopt" : "proxyport",
459  			"help" : "--proxyport=[proxy_port]       The proxy port to use, if one is needed to access the internet (Example: 3127)",
460  			"shortdesc" : "If a proxy is used for internet access, the proxy port should be specified.",
461  			"required" : "0",
462  			"order" : 12
463  		}
464  		all_opt["earlyexit"] = {
465  			"getopt" : "",
466  			"longopt" : "earlyexit",
467  			"help" : "--earlyexit                    Return early if reset is already in progress",
468  			"shortdesc" : "If an existing reset operation is detected, the fence agent will return before the operation completes with a 0 return code.",
469  			"required" : "0",
470  			"order" : 13
471  		}
472  		all_opt["warntimeout"] = {
473  			"getopt" : ":",
474  			"type" : "second",
475  			"longopt" : "warntimeout",
476  			"help" : "--warntimeout=[warn_timeout]   Timeout seconds before logging a warning and returning a 0 status code",
477  			"shortdesc" : "If the operation is not completed within the timeout, the cluster operations are allowed to continue.",
478  			"required" : "0",
479  			"order" : 14
480  		}
481  		all_opt["errortimeout"] = {
482  			"getopt" : ":",
483  			"type" : "second",
484  			"longopt" : "errortimeout",
485  			"help" : "--errortimeout=[error_timeout] Timeout seconds before failing and returning a non-zero status code",
486  			"shortdesc" : "If the operation is not completed within the timeout, cluster is notified of the operation failure.",
487  			"required" : "0",
488  			"order" : 15
489  		}
490  		all_opt["runonwarn"] = {
491  			"getopt" : ":",
492  			"longopt" : "runonwarn",
493  			"help" : "--runonwarn=[run_on_warn]      If a timeout occurs and warning is generated, run the supplied command",
494  			"shortdesc" : "If a timeout would occur while running the agent, then the supplied command is run.",
495  			"required" : "0",
496  			"order" : 16
497  		}
498  		all_opt["runonfail"] = {
499  			"getopt" : ":",
500  			"longopt" : "runonfail",
501  			"help" : "--runonfail=[run_on_fail]      If a failure occurs, run the supplied command",
502  			"shortdesc" : "If a failure would occur while running the agent, then the supplied command is run.",
503  			"required" : "0",
504  			"order" : 17
505  		}
506  	
507  	
508  	def main():
509  		conn = None
510  	
511  		device_opt = ["port", "no_password", "zone", "project", "stackdriver-logging",
512  			"method", "baremetalsolution", "apitimeout", "retries", "retrysleep",
513  			"serviceaccount", "plugzonemap", "proxyhost", "proxyport", "earlyexit",
514  			"warntimeout", "errortimeout", "runonwarn", "runonfail"]
515  	
516  		atexit.register(atexit_handler)
517  	
518  		define_new_opts()
519  	
520  		all_opt["power_timeout"]["default"] = "60"
521  		all_opt["method"]["default"] = "cycle"
522  		all_opt["method"]["help"] = "-m, --method=[method]          Method to fence (onoff|cycle) (Default: cycle)"
523  	
524  		options = check_input(device_opt, process_input(device_opt))
525  	
526  		docs = {}
527  		docs["shortdesc"] = "Fence agent for GCE (Google Cloud Engine)"
528  		docs["longdesc"] = "fence_gce is a Power Fencing agent for GCE (Google Cloud " \
529  				   "Engine). It uses the googleapiclient library to connect to GCE.\n" \
530  				   "googleapiclient can be configured with Google SDK CLI or by " \
531  				   "executing 'gcloud auth application-default login'.\n" \
532  				   "For instructions see: https://cloud.google.com/compute/docs/tutorials/python-guide"
533  		docs["vendorurl"] = "http://cloud.google.com"
534  		show_docs(options, docs)
535  	
536  		run_delay(options)
537  	
538  		# Prepare logging
539  		if options.get('--verbose') is None:
540  			logging.getLogger('googleapiclient').setLevel(logging.ERROR)
541  			logging.getLogger('oauth2client').setLevel(logging.ERROR)
542  		if options.get('--stackdriver-logging') is not None and options.get('--plug'):
543  			try:
544  				import google.cloud.logging.handlers
545  				client = google.cloud.logging.Client()
546  				handler = google.cloud.logging.handlers.CloudLoggingHandler(client, name=options['--plug'])
547  				handler.setLevel(logging.INFO)
548  				formatter = logging.Formatter('gcp:stonith "%(message)s"')
549  				handler.setFormatter(formatter)
550  				root_logger = logging.getLogger()
551  				if options.get('--verbose') is None:
552  					root_logger.setLevel(logging.INFO)
553  				root_logger.addHandler(handler)
554  			except ImportError:
555  				logging.error('Couldn\'t import google.cloud.logging, '
556  					'disabling Stackdriver-logging support')
557  	
558  	  # if apitimeout is defined we set the socket timeout, if not we keep the
559  	  # socket default which is 60s
560  		if options.get("--apitimeout"):
561  			socket.setdefaulttimeout(options["--apitimeout"])
562  	
563  		# Prepare cli
564  		try:
565  			serviceaccount = options.get("--serviceaccount")
566  			if serviceaccount:
567  				scope = ['https://www.googleapis.com/auth/cloud-platform']
568  				logging.debug("using credentials from service account")
569  				try:
570  					from google.oauth2.service_account import Credentials as ServiceAccountCredentials
571  					credentials = ServiceAccountCredentials.from_service_account_file(filename=serviceaccount, scopes=scope)
572  				except ImportError:
573  					from oauth2client.service_account import ServiceAccountCredentials
574  					credentials = ServiceAccountCredentials.from_json_keyfile_name(serviceaccount, scope)
575  			else:
576  				try:
577  					from googleapiclient import _auth
578  					credentials = _auth.default_credentials();
579  				except:
580  					credentials = GoogleCredentials.get_application_default()
581  				logging.debug("using application default credentials")
582  	
583  			if options.get("--proxyhost") and options.get("--proxyport"):
584  				proxy_info = httplib2.ProxyInfo(
585  					proxy_type=socks.PROXY_TYPE_HTTP,
586  					proxy_host=options.get("--proxyhost"),
587  					proxy_port=int(options.get("--proxyport")))
588  				http = credentials.authorize(httplib2.Http(proxy_info=proxy_info))
589  				conn = googleapiclient.discovery.build(
590  					'compute', 'v1', http=http, cache_discovery=False)
591  			else:
592  				conn = googleapiclient.discovery.build(
593  					'compute', 'v1', credentials=credentials, cache_discovery=False)
594  		except SSLError as err:
595  			fail_fence_agent(options, "Failed: Create GCE compute v1 connection: {}\n\nThis might be caused by old versions of httplib2.".format(str(err)))
596  		except Exception as err:
597  			fail_fence_agent(options, "Failed: Create GCE compute v1 connection: {}".format(str(err)))
598  	
599  		# Get project and zone
600  		if not options.get("--project"):
601  			try:
602  				options["--project"] = get_metadata('project/project-id')
603  			except Exception as err:
604  				fail_fence_agent(options, "Failed retrieving GCE project. Please provide --project option: {}".format(str(err)))
605  	
606  		try:
607  			image = get_metadata('instance/image')
608  			options["image"] = image[image.rindex('/')+1:]
609  		except Exception as err:
610  			options["image"] = "unknown"
611  	
612  		if "--baremetalsolution" in options:
613  			options["--zone"] = "none"
614  	
615  		# Populates zone automatically if missing from the command
616  		zones = [] if not "--zone" in options else options["--zone"].split(",")
617  		options["--plugzonemap"] = {}
618  		if "--plug" in options:
619  			for i, instance in enumerate(options["--plug"].split(",")):
620  				if len(zones) == 1:
621  					# If only one zone is specified, use it across all plugs
622  					options["--plugzonemap"][instance] = zones[0]
623  					continue
624  	
625  				if len(zones) - 1 >= i:
626  					# If we have enough zones specified with the --zone flag use the zone at
627  					# the same index as the plug
628  					options["--plugzonemap"][instance] = zones[i]
629  					continue
630  	
631  				try:
632  					# In this case we do not have a zone specified so we attempt to detect it
633  					options["--plugzonemap"][instance] = get_zone(conn, options, instance)
634  				except Exception as err:
635  					fail_fence_agent(options, "Failed retrieving GCE zone. Please provide --zone option: {}".format(str(err)))
636  	
637  		# Operate the fencing device
638  		result = fence_action(conn, options, set_power_status, get_power_status, get_nodes_list, power_cycle)
639  		sys.exit(result)
640  	
641  	if __name__ == "__main__":
642  		main()
643