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