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