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