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