diff --git a/agents/cisco_ucs/fence_cisco_ucs.py b/agents/cisco_ucs/fence_cisco_ucs.py index 2280dbbc..b85379a7 100644 --- a/agents/cisco_ucs/fence_cisco_ucs.py +++ b/agents/cisco_ucs/fence_cisco_ucs.py @@ -1,197 +1,198 @@ #!@PYTHON@ -tt import sys, re import pycurl, io import logging import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, EC_STATUS, EC_LOGIN_DENIED, run_delay RE_COOKIE = re.compile("", int(options["--shell-timeout"])) result = RE_GET_PNDN.search(res) if result == None: pndn = "" else: pndn = result.group(1) if pndn.strip() == "": if "--missing-as-off" in options: return "off" else: fail(EC_STATUS) res = send_command(options, "", int(options["--shell-timeout"])) result = RE_GET_PRESENCE.search(res) if result == None: fail(EC_STATUS) else: presence_status = result.group(1) if presence_status in ["missing", "mismatch"]: return "off" else: result = RE_GET_OPERPOWER.search(res) if result == None: fail(EC_STATUS) else: power_status = result.group(1) if power_status == "on": return "on" else: return "off" def set_power_status(conn, options): del conn action = { 'on' : "admin-up", 'off' : "admin-down" }[options["--action"]] send_command(options, "" + "" + "" + "", int(options["--shell-timeout"])) return def get_list(conn, options): del conn outlets = {} try: res = send_command(options, "", int(options["--shell-timeout"])) lines = res.split("", int(options_global["--shell-timeout"])) except Exception: pass def main(): global options_global device_opt = ["ipaddr", "login", "passwd", "ssl", "notls", "port", "web", "suborg", "missing_as_off"] atexit.register(atexit_handler) atexit.register(logout) define_new_opts() options_global = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for Cisco UCS" docs["longdesc"] = "fence_cisco_ucs is an I/O Fencing agent which can be \ used with Cisco UCS to fence machines." docs["vendorurl"] = "http://www.cisco.com" show_docs(options_global, docs) run_delay(options_global) ### Login try: res = send_command(options_global, "", int(options_global["--login-timeout"])) result = RE_COOKIE.search(res) if result == None: ## Cookie is absenting in response fail(EC_LOGIN_DENIED) except Exception as e: logging.error("Failed: {}".format(str(e))) fail(EC_LOGIN_DENIED) options_global["cookie"] = result.group(1) ## ## Modify suborg to format /suborg if options_global["--suborg"] != "": options_global["--suborg"] = "/" + options_global["--suborg"].lstrip("/").rstrip("/") ## ## Fence operations #### result = fence_action(None, options_global, set_power_status, get_power_status, get_list) ## Logout is done every time at atexit phase sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/docker/fence_docker.py b/agents/docker/fence_docker.py index fef87da8..00440251 100644 --- a/agents/docker/fence_docker.py +++ b/agents/docker/fence_docker.py @@ -1,158 +1,161 @@ #!@PYTHON@ -tt import atexit import sys import io import logging import pycurl import json sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import fail_usage, all_opt, fence_action, atexit_handler, check_input, process_input, show_docs, run_delay def get_power_status(conn, options): del conn status = send_cmd(options, "containers/%s/json" % options["--plug"]) if status is None: return None return "on" if status["State"]["Running"] else "off" def set_power_status(conn, options): del conn if options["--action"] == "on": send_cmd(options, "containers/%s/start" % options["--plug"], True) else: send_cmd(options, "containers/%s/kill" % options["--plug"], True) return def reboot_cycle(conn, options): del conn send_cmd(options, "containers/%s/restart" % options["--plug"], True) return get_power_status(conn, options) def get_list(conn, options): del conn output = send_cmd(options, "containers/json?all=1") containers = {} for container in output: containers[container["Id"]] = ({True:container["Names"][0][1:], False: container["Names"][0]}[container["Names"][0][0:1] == '/'], {True:"off", False: "on"}[container["Status"][:4].lower() == "exit"]) return containers def send_cmd(options, cmd, post = False): - url = "http%s://%s:%s/v%s/%s" % ("s" if "--ssl" in options else "", options["--ip"], options["--ipport"], options["--api-version"], cmd) + url = "http%s://%s:%s/v%s/%s" % ("s" if "--ssl-secure" in options or "--ssl-insecure" in options else "", options["--ip"], options["--ipport"], options["--api-version"], cmd) conn = pycurl.Curl() output_buffer = io.BytesIO() if logging.getLogger().getEffectiveLevel() < logging.WARNING: conn.setopt(pycurl.VERBOSE, True) conn.setopt(pycurl.HTTPGET, 1) conn.setopt(pycurl.URL, url.encode("ascii")) if post: conn.setopt(pycurl.POST, 1) conn.setopt(pycurl.POSTFIELDSIZE, 0) conn.setopt(pycurl.WRITEFUNCTION, output_buffer.write) conn.setopt(pycurl.TIMEOUT, int(options["--shell-timeout"])) - if "--ssl" in options: + + if "--ssl-secure" in options: if not (set(("--tlscert", "--tlskey", "--tlscacert")) <= set(options)): fail_usage("Failed. If --ssl option is used, You have to also \ specify: --tlscert, --tlskey and --tlscacert") conn.setopt(pycurl.SSL_VERIFYPEER, 1) conn.setopt(pycurl.SSLCERT, options["--tlscert"]) conn.setopt(pycurl.SSLKEY, options["--tlskey"]) conn.setopt(pycurl.CAINFO, options["--tlscacert"]) - else: + elif "--ssl-insecure" in options: conn.setopt(pycurl.SSL_VERIFYPEER, 0) conn.setopt(pycurl.SSL_VERIFYHOST, 0) logging.debug("URL: " + url) try: conn.perform() result = output_buffer.getvalue().decode() return_code = conn.getinfo(pycurl.RESPONSE_CODE) logging.debug("RESULT [" + str(return_code) + \ "]: " + result) conn.close() if return_code == 200: return json.loads(result) except pycurl.error: logging.error("Connection failed") except: if result is not None: logging.error(result) logging.error("Cannot parse json") return None def main(): atexit.register(atexit_handler) all_opt["tlscert"] = { "getopt" : ":", "longopt" : "tlscert", "help" : "--tlscert " "Path to client certificate for TLS authentication", "required" : "0", "shortdesc" : "Path to client certificate (PEM format) \ for TLS authentication. Required if --ssl option is used.", "order": 2 } all_opt["tlskey"] = { "getopt" : ":", "longopt" : "tlskey", "help" : "--tlskey " "Path to client key for TLS authentication", "required" : "0", "shortdesc" : "Path to client key (PEM format) for TLS \ authentication. Required if --ssl option is used.", "order": 2 } all_opt["tlscacert"] = { "getopt" : ":", "longopt" : "tlscacert", "help" : "--tlscacert " "Path to CA certificate for TLS authentication", "required" : "0", "shortdesc" : "Path to CA certificate (PEM format) for \ TLS authentication. Required if --ssl option is used.", "order": 2 } all_opt["api_version"] = { "getopt" : ":", "longopt" : "api-version", "help" : "--api-version " "Version of Docker Remote API (default: 1.11)", "required" : "0", "order" : 2, "default" : "1.11", } device_opt = ["ipaddr", "no_password", "no_login", "port", "method", "web", "tlscert", "tlskey", "tlscacert", "ssl", "api_version"] + all_opt["ssl"]["default"] = "1" + options = check_input(device_opt, process_input(device_opt)) docs = { } docs["shortdesc"] = "Fence agent for Docker" docs["longdesc"] = "fence_docker is I/O fencing agent which \ can be used with the Docker Engine containers. You can use this \ fence-agent without any authentication, or you can use TLS authentication \ (use --ssl option, more info about TLS authentication in docker: \ http://docs.docker.com/examples/https/)." docs["vendorurl"] = "www.docker.io" show_docs(options, docs) run_delay(options) result = fence_action(None, options, set_power_status, get_power_status, get_list, reboot_cycle) sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/ibmz/fence_ibmz.py b/agents/ibmz/fence_ibmz.py index 47408ccf..d477adeb 100644 --- a/agents/ibmz/fence_ibmz.py +++ b/agents/ibmz/fence_ibmz.py @@ -1,566 +1,566 @@ #!@PYTHON@ -tt # Copyright (c) 2020 IBM Corp. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library. If not, see # . import atexit import logging import time import sys import requests from requests.packages import urllib3 sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail_usage, run_delay, EC_GENERIC_ERROR DEFAULT_POWER_TIMEOUT = '300' ERROR_NOT_FOUND = ("{obj_type} {obj_name} not found in this HMC. " "Attention: names are case-sensitive.") class ApiClientError(Exception): """ Base exception for all API Client related errors. """ class ApiClientRequestError(ApiClientError): """ Raised when an API request ends in error """ def __init__(self, req_method, req_uri, status, reason, message): self.req_method = req_method self.req_uri = req_uri self.status = status self.reason = reason self.message = message super(ApiClientRequestError, self).__init__() def __str__(self): return ( "API request failed, details:\n" "HTTP Request : {req_method} {req_uri}\n" "HTTP Response status: {status}\n" "Error reason: {reason}\n" "Error message: {message}\n".format( req_method=self.req_method, req_uri=self.req_uri, status=self.status, reason=self.reason, message=self.message) ) class APIClient(object): DEFAULT_CONFIG = { # how many connection-related errors to retry on 'connect_retries': 3, # how many times to retry on read errors (after request was sent to the # server) 'read_retries': 3, # http methods that should be retried 'method_whitelist': ['HEAD', 'GET', 'OPTIONS'], # limit of redirects to perform to avoid loops 'redirect': 5, # how long to wait while establishing a connection 'connect_timeout': 30, # how long to wait for asynchronous operations (jobs) to complete 'operation_timeout': 900, # how long to wait between bytes sent by the remote side 'read_timeout': 300, # default API port 'port': 6794, # validate ssl certificates 'ssl_verify': False, # load on activate is set in the HMC activation profile and therefore # no additional load is executed by the fence agent 'load_on_activate': False } LABEL_BY_OP_MODE = { 'classic': { 'nodes': 'logical-partitions', 'state-on': 'operating', 'start': 'load', 'stop': 'deactivate' }, 'dpm': { 'nodes': 'partitions', 'state-on': 'active', 'start': 'start', 'stop': 'stop' } } def __init__(self, host, user, passwd, config=None): self.host = host if not passwd: raise ValueError('Password cannot be empty') self.passwd = passwd if not user: raise ValueError('Username cannot be empty') self.user = user self._cpc_cache = {} self._session = None self._config = self.DEFAULT_CONFIG.copy() # apply user defined values if config: self._config.update(config) def _create_session(self): """ Create a new requests session and apply config values """ session = requests.Session() retry_obj = urllib3.Retry( # setting a total is necessary to cover SSL related errors total=max(self._config['connect_retries'], self._config['read_retries']), connect=self._config['connect_retries'], read=self._config['read_retries'], method_whitelist=self._config['method_whitelist'], redirect=self._config['redirect'] ) session.mount('http://', requests.adapters.HTTPAdapter( max_retries=retry_obj)) session.mount('https://', requests.adapters.HTTPAdapter( max_retries=retry_obj)) return session def _get_mode_labels(self, cpc): """ Return the map of labels that corresponds to the cpc operation mode """ if self.is_dpm_enabled(cpc): return self.LABEL_BY_OP_MODE['dpm'] return self.LABEL_BY_OP_MODE['classic'] def _get_partition(self, cpc, partition): """ Return the properties of the specified partition. Raises ValueError if it cannot be found. """ # HMC API's documentation says it'll return an empty array when no # matches are found but for a CPC in classic mode it returns in fact # 404, so we handle this accordingly. Remove the extra handling below # once this behavior has been fixed on the API's side. label_map = self._get_mode_labels(cpc) resp = self._request('get', '{}/{}?name={}'.format( self._cpc_cache[cpc]['object-uri'], label_map['nodes'], partition), valid_codes=[200, 404]) if label_map['nodes'] not in resp or not resp[label_map['nodes']]: raise ValueError(ERROR_NOT_FOUND.format( obj_type='LPAR/Partition', obj_name=partition)) return resp[label_map['nodes']][0] def _partition_switch_power(self, cpc, partition, action): """ Perform the API request to start (power on) or stop (power off) the target partition and wait for the job to finish. """ # retrieve partition's uri part_uri = self._get_partition(cpc, partition)['object-uri'] label_map = self._get_mode_labels(cpc) # in dpm mode the request must have empty body if self.is_dpm_enabled(cpc): body = None # in classic mode we make sure the operation is executed # even if the partition is already on else: body = {'force': True} # when powering on the partition must be activated first if action == 'start': op_uri = '{}/operations/activate'.format(part_uri) job_resp = self._request( 'post', op_uri, body=body, valid_codes=[202]) # always wait for activate otherwise the load (start) # operation will fail if self._config['operation_timeout'] == 0: timeout = self.DEFAULT_CONFIG['operation_timeout'] else: timeout = self._config['operation_timeout'] logging.debug( 'waiting for activate (timeout %s secs)', timeout) self._wait_for_job('post', op_uri, job_resp['job-uri'], timeout=timeout) if self._config['load_on_activate']: return # trigger the start job op_uri = '{}/operations/{}'.format(part_uri, label_map[action]) job_resp = self._request('post', op_uri, body=body, valid_codes=[202]) if self._config['operation_timeout'] == 0: return logging.debug('waiting for %s (timeout %s secs)', label_map[action], self._config['operation_timeout']) self._wait_for_job('post', op_uri, job_resp['job-uri'], timeout=self._config['operation_timeout']) def _request(self, method, uri, body=None, headers=None, valid_codes=None): """ Perform a request to the HMC API """ assert method in ('delete', 'head', 'get', 'post', 'put') url = 'https://{host}:{port}{uri}'.format( host=self.host, port=self._config['port'], uri=uri) if not headers: headers = {} if self._session is None: raise ValueError('You need to log on first') method = getattr(self._session, method) timeout = ( self._config['connect_timeout'], self._config['read_timeout']) response = method(url, json=body, headers=headers, verify=self._config['ssl_verify'], timeout=timeout) if valid_codes and response.status_code not in valid_codes: reason = '(no reason)' message = '(no message)' if response.headers.get('content-type') == 'application/json': try: json_resp = response.json() except ValueError: pass else: reason = json_resp.get('reason', reason) message = json_resp.get('message', message) else: message = '{}...'.format(response.text[:500]) raise ApiClientRequestError( response.request.method, response.request.url, response.status_code, reason, message) if response.status_code == 204: return dict() try: json_resp = response.json() except ValueError: raise ApiClientRequestError( response.request.method, response.request.url, response.status_code, '(no reason)', 'Invalid JSON content in response') return json_resp def _update_cpc_cache(self, cpc_props): self._cpc_cache[cpc_props['name']] = { 'object-uri': cpc_props['object-uri'], 'dpm-enabled': cpc_props.get('dpm-enabled', False) } def _wait_for_job(self, req_method, req_uri, job_uri, timeout): """ Perform API requests to check for job status until it has completed or the specified timeout is reached """ op_timeout = time.time() + timeout while time.time() < op_timeout: job_resp = self._request("get", job_uri) if job_resp['status'] == 'complete': if job_resp['job-status-code'] in (200, 201, 204): return raise ApiClientRequestError( req_method, req_uri, job_resp.get('job-status-code', '(no status)'), job_resp.get('job-reason-code', '(no reason)'), job_resp.get('job-results', {}).get( 'message', '(no message)') ) time.sleep(1) raise ApiClientError('Timed out while waiting for job completion') def cpc_list(self): """ Return a list of CPCs in the format {'name': 'cpc-name', 'status': 'operating'} """ list_resp = self._request("get", "/api/cpcs", valid_codes=[200]) ret = [] for cpc_props in list_resp['cpcs']: self._update_cpc_cache(cpc_props) ret.append({ 'name': cpc_props['name'], 'status': cpc_props['status']}) return ret def is_dpm_enabled(self, cpc): """ Return True if CPC is in DPM mode, False for classic mode """ if cpc in self._cpc_cache: return self._cpc_cache[cpc]['dpm-enabled'] list_resp = self._request("get", "/api/cpcs?name={}".format(cpc), valid_codes=[200]) if not list_resp['cpcs']: raise ValueError(ERROR_NOT_FOUND.format( obj_type='CPC', obj_name=cpc)) self._update_cpc_cache(list_resp['cpcs'][0]) return self._cpc_cache[cpc]['dpm-enabled'] def logon(self): """ Open a session with the HMC API and store its ID """ self._session = self._create_session() logon_body = {"userid": self.user, "password": self.passwd} logon_resp = self._request("post", "/api/sessions", body=logon_body, valid_codes=[200, 201]) self._session.headers["X-API-Session"] = logon_resp['api-session'] def logoff(self): """ Close/delete the HMC API session """ if self._session is None: return self._request("delete", "/api/sessions/this-session", valid_codes=[204]) self._cpc_cache = {} self._session = None def partition_list(self, cpc): """ Return a list of partitions in the format {'name': 'part-name', 'status': 'on'} """ label_map = self._get_mode_labels(cpc) list_resp = self._request( 'get', '{}/{}'.format( self._cpc_cache[cpc]['object-uri'], label_map['nodes']), valid_codes=[200]) status_map = {label_map['state-on']: 'on'} return [{'name': part['name'], 'status': status_map.get(part['status'].lower(), 'off')} for part in list_resp[label_map['nodes']]] def partition_start(self, cpc, partition): """ Power on a partition """ self._partition_switch_power(cpc, partition, 'start') def partition_status(self, cpc, partition): """ Return the current status of a partition (on or off) """ label_map = self._get_mode_labels(cpc) part_props = self._get_partition(cpc, partition) if part_props['status'].lower() == label_map['state-on']: return 'on' return 'off' def partition_stop(self, cpc, partition): """ Power off a partition """ self._partition_switch_power(cpc, partition, 'stop') def parse_plug(options): """ Extract cpc and partition from specified plug value """ try: cpc, partition = options['--plug'].strip().split('/', 1) except ValueError: fail_usage('Please specify nodename in format cpc/partition') cpc = cpc.strip() if not cpc or not partition: fail_usage('Please specify nodename in format cpc/partition') return cpc, partition def get_power_status(conn, options): logging.debug('executing get_power_status') status = conn.partition_status(*parse_plug(options)) return status def set_power_status(conn, options): logging.debug('executing set_power_status') if options['--action'] == 'on': conn.partition_start(*parse_plug(options)) elif options['--action'] == 'off': conn.partition_stop(*parse_plug(options)) else: fail_usage('Invalid action {}'.format(options['--action'])) def get_outlet_list(conn, options): logging.debug('executing get_outlet_list') result = {} for cpc in conn.cpc_list(): for part in conn.partition_list(cpc['name']): result['{}/{}'.format(cpc['name'], part['name'])] = ( part['name'], part['status']) return result def disconnect(conn): """ Close the API session """ try: conn.logoff() except Exception as exc: logging.exception('Logoff failed: ') sys.exit(str(exc)) def set_opts(): """ Define the options supported by this agent """ device_opt = [ "ipaddr", "ipport", "login", "passwd", "port", "connect_retries", "connect_timeout", "operation_timeout", "read_retries", "read_timeout", "ssl_secure", "load_on_activate", ] all_opt["ipport"]["default"] = APIClient.DEFAULT_CONFIG['port'] all_opt["power_timeout"]["default"] = DEFAULT_POWER_TIMEOUT port_desc = ("Physical plug id in the format cpc-name/partition-name " "(case-sensitive)") all_opt["port"]["shortdesc"] = port_desc all_opt["port"]["help"] = ( "-n, --plug=[id] {}".format(port_desc)) all_opt["connect_retries"] = { "getopt" : ":", "longopt" : "connect-retries", "help" : "--connect-retries=[number] How many times to " "retry on connection errors", "default" : APIClient.DEFAULT_CONFIG['connect_retries'], "type" : "integer", "required" : "0", "shortdesc" : "How many times to retry on connection errors", "order" : 2 } all_opt["read_retries"] = { "getopt" : ":", "longopt" : "read-retries", "help" : "--read-retries=[number] How many times to " "retry on errors related to reading from server", "default" : APIClient.DEFAULT_CONFIG['read_retries'], "type" : "integer", "required" : "0", "shortdesc" : "How many times to retry on read errors", "order" : 2 } all_opt["connect_timeout"] = { "getopt" : ":", "longopt" : "connect-timeout", "help" : "--connect-timeout=[seconds] How long to wait to " "establish a connection", "default" : APIClient.DEFAULT_CONFIG['connect_timeout'], "type" : "second", "required" : "0", "shortdesc" : "How long to wait to establish a connection", "order" : 2 } all_opt["operation_timeout"] = { "getopt" : ":", "longopt" : "operation-timeout", "help" : "--operation-timeout=[seconds] How long to wait for " "power operation to complete (0 = do not wait)", "default" : APIClient.DEFAULT_CONFIG['operation_timeout'], "type" : "second", "required" : "0", "shortdesc" : "How long to wait for power operation to complete", "order" : 2 } all_opt["read_timeout"] = { "getopt" : ":", "longopt" : "read-timeout", "help" : "--read-timeout=[seconds] How long to wait " "to read data from server", "default" : APIClient.DEFAULT_CONFIG['read_timeout'], "type" : "second", "required" : "0", "shortdesc" : "How long to wait for server data", "order" : 2 } all_opt["load_on_activate"] = { "getopt" : "", "longopt" : "load-on-activate", "help" : "--load-on-activate Rely on the HMC to perform " "a load operation on activation", "required" : "0", "order" : 3 } return device_opt def main(): """ Agent entry point """ # register exit handler used by pacemaker atexit.register(atexit_handler) # prepare accepted options device_opt = set_opts() # parse options provided on input options = check_input(device_opt, process_input(device_opt)) docs = { "shortdesc": "Fence agent for IBM z LPARs", "longdesc": ( "fence_ibmz is a power fencing agent which uses the HMC Web " "Services API to fence IBM z LPARs."), "vendorurl": "http://www.ibm.com" } show_docs(options, docs) run_delay(options) # set underlying library's logging and ssl config according to specified # options requests_log = logging.getLogger("urllib3") requests_log.propagate = True if "--verbose" in options: requests_log.setLevel(logging.DEBUG) - if "--ssl-secure" not in options: + if "--ssl-insecure" in options: urllib3.disable_warnings( category=urllib3.exceptions.InsecureRequestWarning) hmc_address = options["--ip"] hmc_userid = options["--username"] hmc_password = options["--password"] config = { 'connect_retries': int(options['--connect-retries']), 'read_retries': int(options['--read-retries']), 'operation_timeout': int(options['--operation-timeout']), 'connect_timeout': int(options['--connect-timeout']), 'read_timeout': int(options['--read-timeout']), 'port': int(options['--ipport']), - 'ssl_verify': bool('--ssl-secure' in options), + 'ssl_verify': bool('--ssl-insecure' not in options), 'load_on_activate': bool('--load-on-activate' in options), } try: conn = APIClient(hmc_address, hmc_userid, hmc_password, config) conn.logon() atexit.register(disconnect, conn) result = fence_action(conn, options, set_power_status, get_power_status, get_outlet_list) except Exception: logging.exception('Exception occurred: ') result = EC_GENERIC_ERROR sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/pve/fence_pve.py b/agents/pve/fence_pve.py index af339bc0..0d820355 100755 --- a/agents/pve/fence_pve.py +++ b/agents/pve/fence_pve.py @@ -1,238 +1,240 @@ #!@PYTHON@ -tt # This agent uses Proxmox VE API # Thanks to Frank Brendel (author of original perl fence_pve) # for help with writing and testing this agent. import sys import json import pycurl import io import atexit import logging sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import fail, fail_usage, EC_LOGIN_DENIED, atexit_handler, all_opt, check_input, process_input, show_docs, fence_action, run_delay if sys.version_info[0] > 2: import urllib.parse as urllib else: import urllib def get_power_status(conn, options): del conn state = {"running" : "on", "stopped" : "off"} if options["--pve-node"] is None: nodes = send_cmd(options, "nodes") if type(nodes) is not dict or "data" not in nodes or type(nodes["data"]) is not list: return None for node in nodes["data"]: # lookup the node holding the vm if type(node) is not dict or "node" not in node: return None options["--pve-node"] = node["node"] status = get_power_status(None, options) if status is not None: logging.info("vm found on node: " + options["--pve-node"]) break else: options["--pve-node"] = None return status else: cmd = "nodes/" + options["--pve-node"] + "/" + options["--vmtype"] +"/" + options["--plug"] + "/status/current" result = send_cmd(options, cmd) if type(result) is dict and "data" in result: if type(result["data"]) is dict and "status" in result["data"]: if result["data"]["status"] in state: return state[result["data"]["status"]] return None def set_power_status(conn, options): del conn action = { 'on' : "start", 'off': "stop" }[options["--action"]] cmd = "nodes/" + options["--pve-node"] + "/" + options["--vmtype"] +"/" + options["--plug"] + "/status/" + action send_cmd(options, cmd, post={"skiplock":1}) def reboot_cycle(conn, options): del conn cmd = "nodes/" + options["--pve-node"] + "/" + options["--vmtype"] + "/" + options["--plug"] + "/status/reset" result = send_cmd(options, cmd, post={"skiplock":1}) return type(result) is dict and "data" in result def get_outlet_list(conn, options): del conn nodes = send_cmd(options, "nodes") outlets = dict() if type(nodes) is not dict or "data" not in nodes or type(nodes["data"]) is not list: return None for node in nodes["data"]: if type(node) is not dict or "node" not in node: return None vms = send_cmd(options, "nodes/" + node["node"] + "/" + options["--vmtype"]) if type(vms) is not dict or "data" not in vms or type(vms["data"]) is not list: return None for vm in vms["data"]: outlets[vm["vmid"]] = [vm["name"], vm["status"]] return outlets def get_ticket(options): post = {'username': options["--username"], 'password': options["--password"]} result = send_cmd(options, "access/ticket", post=post) if type(result) is dict and "data" in result: if type(result["data"]) is dict and "ticket" in result["data"] and "CSRFPreventionToken" in result["data"]: return { "ticket" : str("PVEAuthCookie=" + result["data"]["ticket"] + "; " + \ "version=0; path=/; domain=" + options["--ip"] + \ "; port=" + str(options["--ipport"]) + "; path_spec=0; secure=1; " + \ "expires=7200; discard=0"), "CSRF_token" : str("CSRFPreventionToken: " + result["data"]["CSRFPreventionToken"]) } return None def send_cmd(options, cmd, post=None): url = options["url"] + cmd conn = pycurl.Curl() output_buffer = io.BytesIO() if logging.getLogger().getEffectiveLevel() < logging.WARNING: conn.setopt(pycurl.VERBOSE, True) conn.setopt(pycurl.HTTPGET, 1) conn.setopt(pycurl.URL, url.encode("ascii")) if "auth" in options and options["auth"] is not None: conn.setopt(pycurl.COOKIE, options["auth"]["ticket"]) conn.setopt(pycurl.HTTPHEADER, [options["auth"]["CSRF_token"]]) if post is not None: if "skiplock" in post: conn.setopt(conn.CUSTOMREQUEST, 'POST') else: conn.setopt(pycurl.POSTFIELDS, urllib.urlencode(post)) conn.setopt(pycurl.WRITEFUNCTION, output_buffer.write) conn.setopt(pycurl.TIMEOUT, int(options["--shell-timeout"])) - if "--ssl" in options or "--ssl-secure" in options: - conn.setopt(pycurl.SSL_VERIFYPEER, 1) - conn.setopt(pycurl.SSL_VERIFYHOST, 2) - else: + + if "--ssl-insecure" in options: conn.setopt(pycurl.SSL_VERIFYPEER, 0) conn.setopt(pycurl.SSL_VERIFYHOST, 0) + else: + conn.setopt(pycurl.SSL_VERIFYPEER, 1) + conn.setopt(pycurl.SSL_VERIFYHOST, 2) logging.debug("URL: " + url) try: conn.perform() result = output_buffer.getvalue().decode() logging.debug("RESULT [" + str(conn.getinfo(pycurl.RESPONSE_CODE)) + \ "]: " + result) conn.close() return json.loads(result) except pycurl.error: logging.error("Connection failed") except: logging.error("Cannot parse json") return None def main(): atexit.register(atexit_handler) all_opt["pve_node_auto"] = { "getopt" : "A", "longopt" : "pve-node-auto", "help" : "-A, --pve-node-auto " "Automatically select proxmox node", "required" : "0", "shortdesc" : "Automatically select proxmox node. " "(This option overrides --pve-node)", "type": "boolean", "order": 2 } all_opt["pve_node"] = { "getopt" : "N:", "longopt" : "pve-node", "help" : "-N, --pve-node=[node_name] " "Proxmox node name on which machine is located", "required" : "0", "shortdesc" : "Proxmox node name on which machine is located. " "(Must be specified if not using --pve-node-auto)", "order": 2 } all_opt["node_name"] = { "getopt" : ":", "longopt" : "nodename", "help" : "--nodename " "Replaced by --pve-node", "required" : "0", "shortdesc" : "Replaced by --pve-node", "order": 3 } all_opt["vmtype"] = { "getopt" : ":", "longopt" : "vmtype", "default" : "qemu", "help" : "--vmtype " "Virtual machine type lxc or qemu (default: qemu)", "required" : "1", "shortdesc" : "Virtual machine type lxc or qemu. " "(Default: qemu)", "order": 2 } - device_opt = ["ipaddr", "login", "passwd", "web", "port", "pve_node", "pve_node_auto", "node_name", "vmtype", "method"] + device_opt = ["ipaddr", "login", "passwd", "ssl", "web", "port", "pve_node", "pve_node_auto", "node_name", "vmtype", "method"] all_opt["login"]["required"] = "0" all_opt["login"]["default"] = "root@pam" all_opt["ipport"]["default"] = "8006" + all_opt["ssl"]["default"] = "1" all_opt["port"]["shortdesc"] = "Id of the virtual machine." all_opt["ipaddr"]["shortdesc"] = "IP Address or Hostname of a node " +\ "within the Proxmox cluster." options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fencing agent for the Proxmox Virtual Environment" docs["longdesc"] = "The fence_pve agent can be used to fence virtual \ machines acting as nodes in a virtualized cluster." docs["vendorurl"] = "http://www.proxmox.com/" show_docs(options, docs) run_delay(options) if "--pve-node-auto" in options: # Force pve-node to None to allow autodiscovery options["--pve-node"] = None elif "--pve-node" in options and options["--pve-node"]: # Leave pve-node alone pass elif "--nodename" in options and options["--nodename"]: # map nodename into pve-node to support legacy implementations options["--pve-node"] = options["--nodename"] else: fail_usage("At least one of pve-node-auto or pve-node must be supplied") if options["--vmtype"] != "qemu": # For vmtypes other than qemu, only the onoff method is valid options["--method"] = "onoff" options["url"] = "https://" + options["--ip"] + ":" + str(options["--ipport"]) + "/api2/json/" options["auth"] = get_ticket(options) if options["auth"] is None: fail(EC_LOGIN_DENIED) # Workaround for unsupported API call on some Proxmox hosts outlets = get_outlet_list(None, options) # Unsupported API-Call will result in value: None if outlets is None: result = fence_action(None, options, set_power_status, get_power_status, None, reboot_cycle) sys.exit(result) result = fence_action(None, options, set_power_status, get_power_status, get_outlet_list, reboot_cycle) sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/rhevm/fence_rhevm.py b/agents/rhevm/fence_rhevm.py index 25aecbe5..5f74d06f 100644 --- a/agents/rhevm/fence_rhevm.py +++ b/agents/rhevm/fence_rhevm.py @@ -1,249 +1,249 @@ #!@PYTHON@ -tt import sys, re import pycurl, io import logging import atexit import tempfile sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, EC_FETCH_VM_UUID, run_delay RE_GET_ID = re.compile("(.*?)", re.IGNORECASE) RE_STATE = re.compile("(.*?)", re.IGNORECASE) RE_GET_NAME = re.compile("(.*?)", re.IGNORECASE) def get_power_status(conn, options): del conn ### Obtain real ID from name res = send_command(options, "vms/?search=name%3D" + options["--plug"]) result = RE_GET_ID.search(res) if result == None: # Unable to obtain ID needed to access virtual machine fail(EC_FETCH_VM_UUID) options["id"] = result.group(2) if tuple(map(int, options["--api-version"].split(".")))[0] > 3: result = RE_STATUS.search(res) else: result = RE_STATE.search(res) if result == None: # We were able to parse ID so output is correct # in some cases it is possible that RHEV-M output does not # contain line. We can assume machine is OFF then return "off" else: status = result.group(1) if status.lower() == "down": return "off" else: return "on" def set_power_status(conn, options): del conn action = { 'on' : "start", 'off' : "stop" }[options["--action"]] url = "vms/" + options["id"] + "/" + action send_command(options, url, "POST") def get_list(conn, options): del conn outlets = {} try: res = send_command(options, "vms") lines = res.split(" 3: status = RE_STATUS.search(lines[i]).group(1) else: status = RE_STATE.search(lines[i]).group(1) outlets[name] = ("", status) except AttributeError: return {} except IndexError: return {} return outlets def send_command(opt, command, method="GET"): if opt["--api-version"] == "auto": opt["--api-version"] = "4" res = send_command(opt, "") if re.search("Error", res): opt["--api-version"] = "3" logging.debug("auto-detected API version: " + opt["--api-version"]) ## setup correct URL - if "--ssl" in opt or "--ssl-secure" in opt or "--ssl-insecure" in opt: + if "--ssl-secure" in opt or "--ssl-insecure" in opt: url = "https:" else: url = "http:" if "--api-path" in opt: api_path = opt["--api-path"] else: api_path = "/ovirt-engine/api" if "--disable-http-filter" in opt: http_filter = 'false' else: http_filter = 'true' url += "//" + opt["--ip"] + ":" + str(opt["--ipport"]) + api_path + "/" + command ## send command through pycurl conn = pycurl.Curl() web_buffer = io.BytesIO() conn.setopt(pycurl.URL, url.encode("UTF-8")) conn.setopt(pycurl.HTTPHEADER, [ "Version: {}".format(opt["--api-version"]), "Content-type: application/xml", "Accept: application/xml", "Prefer: persistent-auth", "Filter: {}".format(http_filter), ]) if "cookie" in opt: conn.setopt(pycurl.COOKIE, opt["cookie"]) else: conn.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC) conn.setopt(pycurl.USERPWD, opt["--username"] + ":" + opt["--password"]) if "--use-cookies" in opt: if "--cookie-file" in opt: cookie_file = opt["--cookie-file"] else: cookie_file = tempfile.gettempdir() + "/fence_rhevm_" + opt["--ip"] + "_" + opt["--username"] + "_cookie.dat" conn.setopt(pycurl.COOKIEFILE, cookie_file) conn.setopt(pycurl.COOKIEJAR, cookie_file) conn.setopt(pycurl.TIMEOUT, int(opt["--shell-timeout"])) - if "--ssl" in opt or "--ssl-secure" in opt: + + if "--ssl-secure" in opt: conn.setopt(pycurl.SSL_VERIFYPEER, 1) conn.setopt(pycurl.SSL_VERIFYHOST, 2) - - if "--ssl-insecure" in opt: + elif "--ssl-insecure" in opt: conn.setopt(pycurl.SSL_VERIFYPEER, 0) conn.setopt(pycurl.SSL_VERIFYHOST, 0) if method == "POST": conn.setopt(pycurl.POSTFIELDS, "") conn.setopt(pycurl.WRITEFUNCTION, web_buffer.write) conn.perform() if "cookie" not in opt and "--use-cookies" in opt: cookie = "" for c in conn.getinfo(pycurl.INFO_COOKIELIST): tokens = c.split("\t",7) cookie = cookie + tokens[5] + "=" + tokens[6] + ";" opt["cookie"] = cookie result = web_buffer.getvalue().decode("UTF-8") logging.debug("url: %s\n", url.encode("UTF-8")) logging.debug("command: %s\n", command.encode("UTF-8")) logging.debug("result: %s\n", result.encode("UTF-8")) return result def define_new_opts(): all_opt["port"] = { "getopt" : "n:", "longopt" : "plug", "help" : "-n, --plug=[name] " "VM name in RHV", "required" : "1", "order" : 1} all_opt["use_cookies"] = { "getopt" : "", "longopt" : "use-cookies", "help" : "--use-cookies Reuse cookies for authentication", "required" : "0", "shortdesc" : "Reuse cookies for authentication", "order" : 1} all_opt["cookie_file"] = { "getopt" : ":", "longopt" : "cookie-file", "help" : "--cookie-file Path to cookie file for authentication\n" "\t\t\t\t (Default: /tmp/fence_rhevm_ip_username_cookie.dat)", "required" : "0", "shortdesc" : "Path to cookie file for authentication", "order" : 2} all_opt["api_version"] = { "getopt" : ":", "longopt" : "api-version", "help" : "--api-version " "Version of RHEV API (default: auto)", "required" : "0", "order" : 2, "default" : "auto", } all_opt["api_path"] = { "getopt" : ":", "longopt" : "api-path", "help" : "--api-path=[path] The path part of the API URL", "default" : "/ovirt-engine/api", "required" : "0", "shortdesc" : "The path part of the API URL", "order" : 3} all_opt["disable_http_filter"] = { "getopt" : "", "longopt" : "disable-http-filter", "help" : "--disable-http-filter Set HTTP Filter header to false", "required" : "0", "shortdesc" : "Set HTTP Filter header to false", "order" : 4} def main(): device_opt = [ "ipaddr", "login", "passwd", "ssl", "notls", "web", "port", "use_cookies", "cookie_file", "api_version", "api_path", "disable_http_filter", ] atexit.register(atexit_handler) define_new_opts() all_opt["power_wait"]["default"] = "1" all_opt["shell_timeout"]["default"] = "5" options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for RHEV-M REST API" docs["longdesc"] = "fence_rhevm is an I/O Fencing agent which can be \ used with RHEV-M REST API to fence virtual machines." docs["vendorurl"] = "http://www.redhat.com" show_docs(options, docs) ## ## Fence operations #### run_delay(options) result = fence_action(None, options, set_power_status, get_power_status, get_list) sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/skalar/fence_skalar.py b/agents/skalar/fence_skalar.py index 95952741..0e11d83f 100644 --- a/agents/skalar/fence_skalar.py +++ b/agents/skalar/fence_skalar.py @@ -1,226 +1,226 @@ #!@PYTHON@ -tt # -*- coding: utf-8 -*- import sys import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, EC_STATUS, EC_LOGIN_DENIED, EC_INVALID_PRIVILEGES, run_delay import requests import ast import urllib3 import json import logging from requests.exceptions import ConnectionError ################################################################################### # Inner functions def authorize_and_get_cookie(skala_ip, login, password, options): URL0 = proto + str(skala_ip) + '/api/0/auth' cred = { "login" : str(login), "password" : str(password) } try: with requests.Session() as session: session.post(url=URL0, data=cred, verify=ssl_verify) cookie = session.cookies.get_dict() except: logging.exception('Exception occured.') fail(EC_LOGIN_DENIED) if 'api_token' in cookie: return cookie else: fail(EC_LOGIN_DENIED) def logout(skala_ip): URL1 = proto + str(skala_ip) + '/api/0/logout' try: with requests.Session() as session: session.post(url=URL1, verify=ssl_verify, cookies=cookie) except: ## Logout; we do not care about result as we will end in any case pass def get_vm_id(skala_ip, uuid, options, cookie): URL2 = proto + str(skala_ip) + '/api/0/vm' parameters = { "uuid": str(uuid) } vm_info = requests.get(url=URL2, verify=ssl_verify, params=parameters, cookies=cookie) jvm_info = vm_info.json() if jvm_info["vm_list"]["items"] == []: raise NameError('Can not find VM by uuid.') logging.debug("VM_INFO:\n{}".format(json.dumps(vm_info.json(), indent=4, sort_keys=True))) return jvm_info["vm_list"]["items"][0]["vm_id"] def vm_task(skala_ip, vm_id, command, options, cookie): # command(str) – Command for vm: ‘vm_start’, ‘vm_stop’, ‘vm_restart’, # ‘vm_suspend’, ‘vm_resume’, ‘vm_pause’, ‘vm_reset’ # main_request_id(TaskId) – Parent task id # graceful(bool) – vm_stop command parameter, graceful or not, default # false - *args[0] # force(bool) – vm_stop command parameter, force stop or not, default # false - *args[1] if "--graceful" in options: graceful = True else: graceful = False if "--force" in options: force = True else: force = False URL3 = proto + str(skala_ip) + '/api/0/vm/' + str(vm_id) + '/task' logging.debug("vm_task skala_ip: " + str(skala_ip)) logging.debug("vm_task vm_id: " + str(vm_id)) logging.debug("vm_task command: " + str(command)) logging.debug("vm_task cookie: " + str(cookie)) def checking(vm_id, command, graceful, force): firstcondition = type(vm_id) is int secondcondition = command in ['vm_start', 'vm_stop', 'vm_restart', 'vm_suspend', 'vm_resume', 'vm_pause', 'vm_reset'] thirdcondition = type(graceful) is bool fourthcondition = type(force) is bool return firstcondition * secondcondition * thirdcondition * fourthcondition if not checking(vm_id, command, graceful, force): print('Wrong parameters! \n' 'command(str) – Command for vm: ‘vm_start’, ‘vm_stop’, \n' '‘vm_restart’,‘vm_suspend’, ‘vm_resume’, ‘vm_pause’, ‘vm_reset’ \n' 'graceful(bool) – vm_stop command parameter, graceful or not, default false \n' 'force(bool) – vm_stop command parameter, force stop or not, default false \n' ) else: parameters = { "command": command, "graceful": graceful, "force": force } with requests.Session() as session: response = session.post(url=URL3, params=parameters, verify=ssl_verify, cookies=cookie) if response.status_code != 200: raise Exception('Invalid response code from server: {}.'.format(response.status_code)) return ###################################################################################### def get_power_status(conn, options): state = {"RUNNING": "on", "PAUSED": "on", "STOPPED": "off", "SUSPENDED": "off", "ERROR": "off", "DELETED": "off", "CREATING": "off", "FAILED_TO_CREATE": "off", "NODE_OFFLINE": "off", "STARTING": "off", "STOPPING": "on"} URL4 = proto + options["--ip"] + '/api/0/vm/' parameters = { "uuid": str(options["--plug"]) } vm_info = requests.get(url=URL4, params=parameters, verify=ssl_verify, cookies=cookie) jvm_info = vm_info.json() if jvm_info["vm_list"]["items"] == []: raise NameError('Can not find VM by uuid.') logging.debug("VM_INFO:\n{}".format(json.dumps(vm_info.json(), indent=4, sort_keys=True))) status_v = jvm_info["vm_list"]["items"][0]["status"] if status_v not in state: raise Exception('Unknown VM state: {}.'.format(status_v)) return state[status_v] def set_power_status(conn, options): action = { "on" : "vm_start", "reboot": "vm_restart", "off" : "vm_stop" } vm_id_v = get_vm_id(options["--ip"], options["--plug"], options, cookie) vm_task(options["--ip"], vm_id_v, action[options["--action"]], options, cookie) return def get_list(conn, options): outlets = {} URL5 = proto + options["--ip"] + '/api/0/vm' vm_info = requests.get(url=URL5, verify=ssl_verify, cookies=cookie) jvm_info = vm_info.json() list_jvm = jvm_info["vm_list"]["items"] for elem in list_jvm: outlets[elem["name"]] = (elem["uuid"], None) return outlets def define_new_opts(): all_opt["graceful"] = { "getopt" : "", "longopt" : "graceful", "help" : "--graceful vm_stop command parameter, graceful stop or not, default false", "required" : "0", "shortdesc" : "vm_stop command parameter, graceful stop or not, default false", "order" : 1} all_opt["force"] = { "getopt" : "", "longopt" : "force", "help" : "--force vm_stop command parameter, force stop or not, default false", "required" : "0", "shortdesc" : "vm_stop command parameter, force stop or not, default false", "order" : 1} def main(): global cookie, proto, ssl_verify define_new_opts() device_opt = ["ipaddr", "login", "passwd", "port", "web", "ssl", "verbose", "graceful", "force"] atexit.register(atexit_handler) options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Skala-R Fence agent" docs["longdesc"] = "A fence agent for Skala-R." docs["vendorurl"] = "https://www.skala-r.ru/" show_docs(options, docs) options["eol"] = "\r" run_delay(options) proto = "https://" - if "--ssl" in options or "--ssl-secure" in options: + if "--ssl-secure" in options: ssl_verify = True elif "--ssl-insecure" in options: ssl_verify = False urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) else: proto = "http://" ssl_verify = False urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) cookie = authorize_and_get_cookie(options["--ip"], options["--username"], options["--password"], options) atexit.register(logout, options["--ip"]) logging.debug("OPTIONS: " + str(options) + "\n") try: result = fence_action(None, options, set_power_status, get_power_status, get_list) sys.exit(result) except Exception: logging.exception('Exception occured.') fail(EC_STATUS) if __name__ == "__main__": main() diff --git a/agents/vmware_rest/fence_vmware_rest.py b/agents/vmware_rest/fence_vmware_rest.py index e49fd566..4b884fc6 100644 --- a/agents/vmware_rest/fence_vmware_rest.py +++ b/agents/vmware_rest/fence_vmware_rest.py @@ -1,229 +1,229 @@ #!@PYTHON@ -tt import sys import pycurl, io, json import logging import atexit sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, run_delay, EC_LOGIN_DENIED, EC_STATUS if sys.version_info[0] > 2: import urllib.parse as urllib else: import urllib state = {"POWERED_ON": "on", 'POWERED_OFF': "off", 'SUSPENDED': "off"} def get_power_status(conn, options): try: res = send_command(conn, "vcenter/vm?filter.names={}".format(urllib.quote(options["--plug"])))["value"] except Exception as e: logging.debug("Failed: {}".format(e)) fail(EC_STATUS) if len(res) == 0: fail(EC_STATUS) options["id"] = res[0]["vm"] result = res[0]["power_state"] return state[result] def set_power_status(conn, options): action = { "on" : "start", "off" : "stop" }[options["--action"]] try: send_command(conn, "vcenter/vm/{}/power/{}".format(options["id"], action), "POST") except Exception as e: logging.debug("Failed: {}".format(e)) fail(EC_STATUS) def get_list(conn, options): outlets = {} try: command = "vcenter/vm" if "--filter" in options: command = command + "?" + options["--filter"] res = send_command(conn, command) except Exception as e: logging.debug("Failed: {}".format(e)) if str(e).startswith("400"): if options.get("--original-action") == "monitor": return outlets else: logging.error("More than 1000 VMs returned. Use --filter parameter to limit which VMs to list.") fail(EC_STATUS) else: fail(EC_STATUS) for r in res["value"]: outlets[r["name"]] = ("", state[r["power_state"]]) return outlets def connect(opt): conn = pycurl.Curl() ## setup correct URL - if "--ssl" in opt or "--ssl-secure" in opt or "--ssl-insecure" in opt: + if "--ssl-secure" in opt or "--ssl-insecure" in opt: conn.base_url = "https:" else: conn.base_url = "http:" if "--api-path" in opt: api_path = opt["--api-path"] else: api_path = "/rest" conn.base_url += "//" + opt["--ip"] + ":" + str(opt["--ipport"]) + api_path + "/" ## send command through pycurl conn.setopt(pycurl.HTTPHEADER, [ "Accept: application/json", ]) conn.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC) conn.setopt(pycurl.USERPWD, opt["--username"] + ":" + opt["--password"]) conn.setopt(pycurl.TIMEOUT, int(opt["--shell-timeout"])) - if "--ssl" in opt or "--ssl-secure" in opt: + + if "--ssl-secure" in opt: conn.setopt(pycurl.SSL_VERIFYPEER, 1) conn.setopt(pycurl.SSL_VERIFYHOST, 2) - - if "--ssl-insecure" in opt: + elif "--ssl-insecure" in opt: conn.setopt(pycurl.SSL_VERIFYPEER, 0) conn.setopt(pycurl.SSL_VERIFYHOST, 0) try: result = send_command(conn, "com/vmware/cis/session", "POST") except Exception as e: logging.debug("Failed: {}".format(e)) fail(EC_LOGIN_DENIED) # set session id for later requests conn.setopt(pycurl.HTTPHEADER, [ "Accept: application/json", "vmware-api-session-id: {}".format(result["value"]), ]) return conn def disconnect(conn): try: send_command(conn, "com/vmware/cis/session", "DELETE") except Exception as e: logging.debug("Failed: {}".format(e)) conn.close() def send_command(conn, command, method="GET"): url = conn.base_url + command conn.setopt(pycurl.URL, url.encode("ascii")) web_buffer = io.BytesIO() if method == "GET": conn.setopt(pycurl.POST, 0) if method == "POST": conn.setopt(pycurl.POSTFIELDS, "") if method == "DELETE": conn.setopt(pycurl.CUSTOMREQUEST, "DELETE") conn.setopt(pycurl.WRITEFUNCTION, web_buffer.write) try: conn.perform() except Exception as e: raise(e) rc = conn.getinfo(pycurl.HTTP_CODE) result = web_buffer.getvalue().decode("UTF-8") web_buffer.close() if len(result) > 0: result = json.loads(result) if rc != 200: if len(result) > 0: raise Exception("{}: {}".format(rc, result["value"]["messages"][0]["default_message"])) else: raise Exception("Remote returned {} for request to {}".format(rc, url)) logging.debug("url: {}".format(url)) logging.debug("method: {}".format(method)) logging.debug("response code: {}".format(rc)) logging.debug("result: {}\n".format(result)) return result def define_new_opts(): all_opt["api_path"] = { "getopt" : ":", "longopt" : "api-path", "help" : "--api-path=[path] The path part of the API URL", "default" : "/rest", "required" : "0", "shortdesc" : "The path part of the API URL", "order" : 2} all_opt["filter"] = { "getopt" : ":", "longopt" : "filter", "help" : "--filter=[filter] Filter to only return relevant VMs" " (e.g. \"filter.names=node1&filter.names=node2\").", "required" : "0", "shortdesc" : "Filter to only return relevant VMs. It can be used to avoid " "the agent failing when more than 1000 VMs should be returned.", "order" : 2} def main(): device_opt = [ "ipaddr", "api_path", "login", "passwd", "ssl", "notls", "web", "port", "filter", ] atexit.register(atexit_handler) define_new_opts() all_opt["shell_timeout"]["default"] = "5" all_opt["power_wait"]["default"] = "1" options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for VMware REST API" docs["longdesc"] = """fence_vmware_rest is an I/O Fencing agent which can be \ used with VMware API to fence virtual machines. NOTE: If there's more than 1000 VMs there is a filter parameter to work around \ the API limit. See https://code.vmware.com/apis/62/vcenter-management#/VM%20/get_vcenter_vm \ for full list of filters.""" docs["vendorurl"] = "https://www.vmware.com" show_docs(options, docs) #### ## Fence operations #### run_delay(options) conn = connect(options) atexit.register(disconnect, conn) result = fence_action(conn, options, set_power_status, get_power_status, get_list) sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/vmware_soap/fence_vmware_soap.py b/agents/vmware_soap/fence_vmware_soap.py index 2cd45e0b..51fb0f14 100644 --- a/agents/vmware_soap/fence_vmware_soap.py +++ b/agents/vmware_soap/fence_vmware_soap.py @@ -1,265 +1,265 @@ #!@PYTHON@ -tt import sys import shutil, tempfile, suds import logging, requests import atexit, signal sys.path.append("@FENCEAGENTSLIBDIR@") from suds.client import Client from suds.sudsobject import Property from suds.transport.http import HttpAuthenticated from suds.transport import Reply, TransportError from fencing import * from fencing import fail, fail_usage, EC_STATUS, EC_LOGIN_DENIED, EC_INVALID_PRIVILEGES, EC_WAITING_ON, EC_WAITING_OFF from fencing import run_delay options_global = None conn_global = None class RequestsTransport(HttpAuthenticated): def __init__(self, **kwargs): self.cert = kwargs.pop('cert', None) self.verify = kwargs.pop('verify', True) self.session = requests.Session() # super won't work because not using new style class HttpAuthenticated.__init__(self, **kwargs) def send(self, request): self.addcredentials(request) resp = self.session.post(request.url, data=request.message, headers=request.headers, cert=self.cert, verify=self.verify) result = Reply(resp.status_code, resp.headers, resp.content) return result def soap_login(options): run_delay(options) - if "--ssl" in options or "--ssl-secure" in options or "--ssl-insecure" in options: + if "--ssl-secure" in options or "--ssl-insecure" in options: if "--ssl-insecure" in options: import ssl import urllib3 if hasattr(ssl, '_create_unverified_context'): ssl._create_default_https_context = ssl._create_unverified_context urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) verify = False else: verify = True url = "https://" else: verify = False url = "http://" url += options["--ip"] + ":" + str(options["--ipport"]) + "/sdk" tmp_dir = tempfile.mkdtemp() tempfile.tempdir = tmp_dir atexit.register(remove_tmp_dir, tmp_dir) try: headers = {"Content-Type" : "text/xml;charset=UTF-8", "SOAPAction" : "vim25"} login_timeout = int(options["--login-timeout"]) or 60 conn = Client(url + "/vimService.wsdl", location=url, transport=RequestsTransport(verify=verify), headers=headers, timeout=login_timeout) mo_ServiceInstance = Property('ServiceInstance') mo_ServiceInstance._type = 'ServiceInstance' ServiceContent = conn.service.RetrieveServiceContent(mo_ServiceInstance) mo_SessionManager = Property(ServiceContent.sessionManager.value) mo_SessionManager._type = 'SessionManager' conn.service.Login(mo_SessionManager, options["--username"], options["--password"]) except requests.exceptions.SSLError as ex: fail_usage("Server side certificate verification failed: %s" % ex) except Exception as e: logging.error("Server side certificate verification failed: {}".format(str(e))) fail(EC_LOGIN_DENIED) options["ServiceContent"] = ServiceContent options["mo_SessionManager"] = mo_SessionManager return conn def process_results(results, machines, uuid, mappingToUUID): for m in results.objects: info = {} for i in m.propSet: info[i.name] = i.val # Prevent error KeyError: 'config.uuid' when reaching systems which P2V failed, # since these systems don't have a valid UUID if "config.uuid" in info: machines[info["name"]] = (info["config.uuid"], info["summary.runtime.powerState"]) uuid[info["config.uuid"]] = info["summary.runtime.powerState"] mappingToUUID[m.obj.value] = info["config.uuid"] return (machines, uuid, mappingToUUID) def get_power_status(conn, options): mo_ViewManager = Property(options["ServiceContent"].viewManager.value) mo_ViewManager._type = "ViewManager" mo_RootFolder = Property(options["ServiceContent"].rootFolder.value) mo_RootFolder._type = "Folder" mo_PropertyCollector = Property(options["ServiceContent"].propertyCollector.value) mo_PropertyCollector._type = 'PropertyCollector' ContainerView = conn.service.CreateContainerView(mo_ViewManager, recursive=1, container=mo_RootFolder, type=['VirtualMachine']) mo_ContainerView = Property(ContainerView.value) mo_ContainerView._type = "ContainerView" FolderTraversalSpec = conn.factory.create('ns0:TraversalSpec') FolderTraversalSpec.name = "traverseEntities" FolderTraversalSpec.path = "view" FolderTraversalSpec.skip = False FolderTraversalSpec.type = "ContainerView" objSpec = conn.factory.create('ns0:ObjectSpec') objSpec.obj = mo_ContainerView objSpec.selectSet = [FolderTraversalSpec] objSpec.skip = True propSpec = conn.factory.create('ns0:PropertySpec') propSpec.all = False propSpec.pathSet = ["name", "summary.runtime.powerState", "config.uuid"] propSpec.type = "VirtualMachine" propFilterSpec = conn.factory.create('ns0:PropertyFilterSpec') propFilterSpec.propSet = [propSpec] propFilterSpec.objectSet = [objSpec] try: raw_machines = conn.service.RetrievePropertiesEx(mo_PropertyCollector, propFilterSpec) except Exception as e: logging.error("Failed: {}".format(str(e))) fail(EC_STATUS) (machines, uuid, mappingToUUID) = process_results(raw_machines, {}, {}, {}) # Probably need to loop over the ContinueRetreive if there are more results after 1 iteration. while hasattr(raw_machines, 'token'): try: raw_machines = conn.service.ContinueRetrievePropertiesEx(mo_PropertyCollector, raw_machines.token) except Exception as e: logging.error("Failed: {}".format(str(e))) fail(EC_STATUS) (more_machines, more_uuid, more_mappingToUUID) = process_results(raw_machines, {}, {}, {}) machines.update(more_machines) uuid.update(more_uuid) mappingToUUID.update(more_mappingToUUID) # Do not run unnecessary SOAP requests if "--uuid" in options and options["--uuid"] in uuid: break if ["list", "monitor"].count(options["--action"]) == 1: return machines else: if "--uuid" not in options: if options["--plug"].startswith('/'): ## Transform InventoryPath to UUID mo_SearchIndex = Property(options["ServiceContent"].searchIndex.value) mo_SearchIndex._type = "SearchIndex" vm = conn.service.FindByInventoryPath(mo_SearchIndex, options["--plug"]) try: options["--uuid"] = mappingToUUID[vm.value] except KeyError: fail(EC_STATUS) except AttributeError: fail(EC_STATUS) else: ## Name of virtual machine instead of path ## warning: if you have same names of machines this won't work correctly try: (options["--uuid"], _) = machines[options["--plug"]] except KeyError: fail(EC_STATUS) except AttributeError: fail(EC_STATUS) try: if uuid[options["--uuid"]] == "poweredOn": return "on" else: return "off" except KeyError: fail(EC_STATUS) def set_power_status(conn, options): mo_SearchIndex = Property(options["ServiceContent"].searchIndex.value) mo_SearchIndex._type = "SearchIndex" vm = conn.service.FindByUuid(mo_SearchIndex, vmSearch=1, uuid=options["--uuid"]) mo_machine = Property(vm.value) mo_machine._type = "VirtualMachine" try: if options["--action"] == "on": conn.service.PowerOnVM_Task(mo_machine) else: conn.service.PowerOffVM_Task(mo_machine) except suds.WebFault as ex: if (str(ex).find("Permission to perform this operation was denied")) >= 0: fail(EC_INVALID_PRIVILEGES) else: if options["--action"] == "on": fail(EC_WAITING_ON) else: fail(EC_WAITING_OFF) def remove_tmp_dir(tmp_dir): shutil.rmtree(tmp_dir) def logout(): try: conn_global.service.Logout(options_global["mo_SessionManager"]) except Exception: pass def signal_handler(signum, frame): raise Exception("Signal \"%d\" received which has triggered an exit of the process." % signum) def main(): global options_global global conn_global device_opt = ["ipaddr", "login", "passwd", "web", "ssl", "notls", "port"] atexit.register(atexit_handler) atexit.register(logout) signal.signal(signal.SIGTERM, signal_handler) options_global = check_input(device_opt, process_input(device_opt)) ## ## Fence agent specific defaults ##### docs = {} docs["shortdesc"] = "Fence agent for VMWare over SOAP API" docs["longdesc"] = "fence_vmware_soap is an I/O Fencing agent \ which can be used with the virtual machines managed by VMWare products \ that have SOAP API v4.1+. \ \n.P\n\ Name of virtual machine (-n / port) has to be used in inventory path \ format (e.g. /datacenter/vm/Discovered virtual machine/myMachine). \ In the cases when name of yours VM is unique you can use it instead. \ Alternatively you can always use UUID to access virtual machine." docs["vendorurl"] = "http://www.vmware.com" show_docs(options_global, docs) logging.basicConfig(level=logging.INFO) logging.getLogger('suds.client').setLevel(logging.CRITICAL) logging.getLogger("requests").setLevel(logging.CRITICAL) logging.getLogger("urllib3").setLevel(logging.CRITICAL) ## ## Operate the fencing device #### conn_global = soap_login(options_global) result = fence_action(conn_global, options_global, set_power_status, get_power_status, get_power_status) ## Logout from system is done automatically via atexit() sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/vmware_vcloud/fence_vmware_vcloud.py b/agents/vmware_vcloud/fence_vmware_vcloud.py index 42372a83..7626b82b 100644 --- a/agents/vmware_vcloud/fence_vmware_vcloud.py +++ b/agents/vmware_vcloud/fence_vmware_vcloud.py @@ -1,214 +1,214 @@ #!@PYTHON@ -tt import sys import pycurl, io import logging import atexit import xml.etree.ElementTree as etree sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, run_delay, EC_LOGIN_DENIED, EC_STATUS state = {"POWERED_ON": "on", 'POWERED_OFF': "off", 'SUSPENDED': "off"} def get_power_status(conn, options): try: VM = send_command(conn, "vApp/vm-{}".format(options["--plug"])) except Exception as e: logging.debug("Failed: {}".format(e)) fail(EC_STATUS) options["id"] = VM.attrib['href'].split('/vm-', 1)[1] if (VM.attrib['status'] == '3'): return state['SUSPENDED'] elif (VM.attrib['status'] == '4'): return state['POWERED_ON'] elif (VM.attrib['status'] == '8'): return state['POWERED_OFF'] return EC_STATUS def set_power_status(conn, options): action = { "on" : "powerOn", "off" : "powerOff", "shutdown": "shutdown", "suspend": "suspend", "reset": "reset" }[options["--action"]] try: VM = send_command(conn, "vApp/vm-{}/power/action/{}".format(options["--plug"], action), "POST") except Exception as e: logging.debug("Failed: {}".format(e)) fail(EC_STATUS) def get_list(conn, options): outlets = {} VMsResponse = send_command(conn, "vms/query") for VM in VMsResponse.iter('{http://www.vmware.com/vcloud/v1.5}VMRecord'): if '/vApp/' not in VM.attrib['href']: continue uuid = (VM.attrib['href'].split('/vm-', 1))[1] outlets['['+ uuid + '] ' + VM.attrib['containerName'] + '\\' + VM.attrib['name']] = (VM.attrib['status'], state[VM.attrib['status']]) return outlets def connect(opt): conn = pycurl.Curl() ## setup correct URL - if "--ssl" in opt or "--ssl-secure" in opt or "--ssl-insecure" in opt: + if "--ssl-secure" in opt or "--ssl-insecure" in opt: conn.base_url = "https:" else: conn.base_url = "http:" conn.base_url += "//" + opt["--ip"] + ":" + str(opt["--ipport"]) + opt["--api-path"] + "/" ## send command through pycurl conn.setopt(pycurl.HTTPHEADER, [ "Accept: application/*+xml;version=1.5", ]) conn.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC) conn.setopt(pycurl.USERPWD, opt["--username"] + ":" + opt["--password"]) conn.setopt(pycurl.TIMEOUT, int(opt["--shell-timeout"])) - if "--ssl" in opt or "--ssl-secure" in opt: + if "--ssl-secure" in opt: conn.setopt(pycurl.SSL_VERIFYPEER, 1) conn.setopt(pycurl.SSL_VERIFYHOST, 2) elif "--ssl-insecure" in opt: conn.setopt(pycurl.SSL_VERIFYPEER, 0) conn.setopt(pycurl.SSL_VERIFYHOST, 0) headers = {} try: result = send_command(conn, "sessions", "POST", headers) except Exception as e: logging.debug("Failed: {}".format(e)) fail(EC_LOGIN_DENIED) # set session id for later requests conn.setopt(pycurl.HTTPHEADER, [ "Accept: application/*+xml;version=1.5", "x-vcloud-authorization: {}".format(headers['x-vcloud-authorization']), ]) return conn def disconnect(conn): send_command(conn, "session", "DELETE") conn.close() def parse_headers(data): headers = {} data = data.split("\r\n") for header_line in data[1:]: if ':' not in header_line: break name, value = header_line.split(':', 1) name = name.strip() value = value.strip() name = name.lower() headers[name] = value return headers def send_command(conn, command, method="GET", headers={}): url = conn.base_url + command conn.setopt(pycurl.URL, url.encode("ascii")) web_buffer = io.BytesIO() headers_buffer = io.BytesIO() if method == "GET": conn.setopt(pycurl.POST, 0) elif method == "POST": conn.setopt(pycurl.POSTFIELDS, "") elif method == "DELETE": conn.setopt(pycurl.CUSTOMREQUEST, "DELETE") conn.setopt(pycurl.WRITEFUNCTION, web_buffer.write) conn.setopt(pycurl.HEADERFUNCTION, headers_buffer.write) try: conn.perform() except Exception as e: raise(e) rc = conn.getinfo(pycurl.HTTP_CODE) result = web_buffer.getvalue().decode() headers.update(parse_headers(headers_buffer.getvalue().decode())) headers_buffer.close() web_buffer.close() if len(result) > 0: result = etree.fromstring(result) if rc != 200 and rc != 202 and rc != 204: if len(result) > 0: raise Exception("{}: {}".format(rc, result["value"]["messages"][0]["default_message"])) else: raise Exception("Remote returned {} for request to {}".format(rc, url)) logging.debug("url: {}".format(url)) logging.debug("method: {}".format(method)) logging.debug("response code: {}".format(rc)) logging.debug("result: {}\n".format(result)) return result def define_new_opts(): all_opt["api_path"] = { "getopt" : ":", "longopt" : "api-path", "help" : "--api-path=[path] The path part of the API URL", "default" : "/api", "required" : "0", "shortdesc" : "The path part of the API URL", "order" : 2} def main(): device_opt = [ "ipaddr", "api_path", "login", "passwd", "ssl", "notls", "web", "port", ] atexit.register(atexit_handler) define_new_opts() all_opt["shell_timeout"]["default"] = "5" all_opt["power_wait"]["default"] = "1" options = check_input(device_opt, process_input(device_opt)) docs = {} docs["shortdesc"] = "Fence agent for VMware vCloud Director API" docs["longdesc"] = "fence_vmware_vcloud is an I/O Fencing agent which can be used with VMware vCloud Director API to fence virtual machines." docs["vendorurl"] = "https://www.vmware.com" show_docs(options, docs) #### ## Fence operations #### run_delay(options) conn = connect(options) atexit.register(disconnect, conn) result = fence_action(conn, options, set_power_status, get_power_status, get_list) sys.exit(result) if __name__ == "__main__": main() diff --git a/agents/zvm/fence_zvmip.py b/agents/zvm/fence_zvmip.py index e8f849ed..90ca95d4 100644 --- a/agents/zvm/fence_zvmip.py +++ b/agents/zvm/fence_zvmip.py @@ -1,219 +1,221 @@ #!@PYTHON@ -tt import sys import atexit import socket import struct import logging sys.path.append("@FENCEAGENTSLIBDIR@") from fencing import * from fencing import fail, fail_usage, run_delay, EC_LOGIN_DENIED, EC_TIMED_OUT INT4 = 4 def open_socket(options): try: if "--inet6-only" in options: protocol = socket.AF_INET6 elif "--inet4-only" in options: protocol = socket.AF_INET else: protocol = 0 (_, _, _, _, addr) = socket.getaddrinfo( \ options["--ip"], options["--ipport"], protocol, 0, socket.IPPROTO_TCP, socket.AI_PASSIVE )[0] except socket.gaierror: fail(EC_LOGIN_DENIED) - if "--ssl" in options: + if "--ssl-secure" in options or "--ssl-insecure" in options: import ssl sock = socket.socket() sslcx = ssl.create_default_context() if "--ssl-insecure" in options: sslcx.check_hostname = False sslcx.verify_mode = ssl.CERT_NONE conn = sslcx.wrap_socket(sock, server_hostname=options["--ip"]) else: conn = socket.socket() conn.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) conn.settimeout(float(options["--shell-timeout"]) or None) try: conn.connect(addr) except socket.error as e: logging.debug(e) fail(EC_LOGIN_DENIED) return conn def smapi_pack_string(string): return struct.pack("!i%ds" % (len(string)), len(string), string.encode("UTF-8")) def prepare_smapi_command(options, smapi_function, additional_args): packet_size = 3*INT4 + len(smapi_function) + len(options["--username"]) + len(options["--password"]) for arg in additional_args: packet_size += INT4 + len(arg) command = struct.pack("!i", packet_size) command += smapi_pack_string(smapi_function) command += smapi_pack_string(options["--username"]) command += smapi_pack_string(options["--password"]) for arg in additional_args: command += smapi_pack_string(arg) return command def get_power_status(conn, options): del conn if options.get("--original-action", None) == "monitor": (return_code, reason_code, images_active) = \ get_list_of_images(options, "Check_Authentication", None) logging.debug("Check_Authenticate (%d,%d)", return_code, reason_code) if return_code == 0: return {} else: fail(EC_LOGIN_DENIED) if options["--action"] == "list": # '*' = list all active images options["--plug"] = "*" (return_code, reason_code, images_active) = \ get_list_of_images(options, "Image_Status_Query", options["--plug"]) logging.debug("Image_Status_Query results are (%d,%d)", return_code, reason_code) if not options["--action"] == "list": if (return_code == 0) and (reason_code == 0): return "on" elif (return_code == 0) and (reason_code == 12): # We are running always with --missing-as-off because we can not check if image # is defined or not (look at rhbz#1188750) return "off" else: return "unknown" else: (return_code, reason_code, images_defined) = \ get_list_of_images(options, "Image_Name_Query_DM", options["--username"]) logging.debug("Image_Name_Query_DM results are (%d,%d)", return_code, reason_code) return dict([(i, ("", "on" if i in images_active else "off")) for i in images_defined]) def set_power_status(conn, options): conn = open_socket(options) packet = None if options["--action"] == "on": packet = prepare_smapi_command(options, "Image_Activate", [options["--plug"]]) elif options["--action"] == "off": packet = prepare_smapi_command(options, "Image_Deactivate", [options["--plug"], "IMMED"]) conn.send(packet) request_id = struct.unpack("!i", conn.recv(INT4))[0] (output_len, request_id, return_code, reason_code) = struct.unpack("!iiii", conn.recv(INT4 * 4)) logging.debug("Image_(De)Activate results are (%d,%d)", return_code, reason_code) conn.close() return def get_list_of_images(options, command, data_as_plug): conn = open_socket(options) if data_as_plug is None: packet = prepare_smapi_command(options, command, []) else: packet = prepare_smapi_command(options, command, [data_as_plug]) conn.send(packet) request_id = struct.unpack("!i", conn.recv(INT4))[0] (output_len, request_id, return_code, reason_code) = struct.unpack("!iiii", conn.recv(INT4 * 4)) images = set() if output_len > 3*INT4: - recvflag = socket.MSG_WAITALL if "--ssl" not in options else 0 + recvflag = socket.MSG_WAITALL if "--ssl-secure" not in options and "--ssl-insecure" not in options else 0 array_len = struct.unpack("!i", conn.recv(INT4))[0] data = "" while True: read_data = conn.recv(1024, recvflag).decode("UTF-8") data += read_data if array_len == len(data): break elif not read_data: logging.error("Failed: Not enough data read from socket") fail(EC_TIMED_OUT) parsed_len = 0 while parsed_len < array_len: string_len = struct.unpack("!i", data[parsed_len:parsed_len+INT4].encode("UTF-8"))[0] parsed_len += INT4 image_name = struct.unpack("!%ds" % (string_len), data[parsed_len:parsed_len+string_len].encode("UTF-8"))[0].decode("UTF-8") parsed_len += string_len images.add(image_name) conn.close() return (return_code, reason_code, images) def define_new_opts(): all_opt["disable_ssl"] = { "getopt" : "", "longopt" : "disable-ssl", "help" : "--disable-ssl Don't use SSL connection", "required" : "0", "shortdesc" : "Don't use SSL", "order": 2 } def main(): device_opt = ["ipaddr", "login", "passwd", "port", "method", "missing_as_off", "inet4_only", "inet6_only", "ssl", "disable_ssl"] atexit.register(atexit_handler) define_new_opts() all_opt["ssl"]["help"] = "-z, --ssl Use SSL connection with verifying certificate (Default)" all_opt["ipport"]["default"] = "44444" all_opt["shell_timeout"]["default"] = "5" all_opt["missing_as_off"]["default"] = "1" all_opt["ssl"]["default"] = "1" options = check_input(device_opt, process_input(device_opt), other_conditions=True) if "--disable-ssl" in options or options["--ssl"] == "0": - del options["--ssl"] + for k in ["--ssl", "--ssl-secure", "--ssl-insecure"]: + if k in options: + del options[k] if len(options.get("--plug", "")) > 8: fail_usage("Failed: Name of image can not be longer than 8 characters") if options["--action"] == "validate-all": sys.exit(0) docs = {} docs["shortdesc"] = "Fence agent for use with z/VM Virtual Machines" docs["longdesc"] = """The fence_zvm agent is intended to be used with with z/VM SMAPI service via TCP/IP To use this agent the z/VM SMAPI service needs to be configured to allow the virtual machine running this agent to connect to it and issue the image_recycle operation. This involves updating the VSMWORK1 AUTHLIST VMSYS:VSMWORK1. file. The entry should look something similar to this: Column 1 Column 66 Column 131 | | | V V V XXXXXXXX ALL IMAGE_CHARACTERISTICS Where XXXXXXX is the name of the virtual machine used in the authuser field of the request. This virtual machine also has to be authorized to access the system's directory manager. """ docs["vendorurl"] = "http://www.ibm.com" show_docs(options, docs) run_delay(options) result = fence_action(None, options, set_power_status, get_power_status, get_power_status) sys.exit(result) if __name__ == "__main__": main() diff --git a/lib/fencing.py.py b/lib/fencing.py.py index 696388d5..b746ede8 100644 --- a/lib/fencing.py.py +++ b/lib/fencing.py.py @@ -1,1626 +1,1632 @@ #!@PYTHON@ -tt import sys, getopt, time, os, uuid, pycurl, stat import pexpect, re, syslog import logging import subprocess import threading import shlex import socket import textwrap import __main__ import itertools RELEASE_VERSION = "@RELEASE_VERSION@" __all__ = ['atexit_handler', 'check_input', 'process_input', 'all_opt', 'show_docs', 'fence_login', 'fence_action', 'fence_logout'] EC_OK = 0 EC_GENERIC_ERROR = 1 EC_BAD_ARGS = 2 EC_LOGIN_DENIED = 3 EC_CONNECTION_LOST = 4 EC_TIMED_OUT = 5 EC_WAITING_ON = 6 EC_WAITING_OFF = 7 EC_STATUS = 8 EC_STATUS_HMC = 9 EC_PASSWORD_MISSING = 10 EC_INVALID_PRIVILEGES = 11 EC_FETCH_VM_UUID = 12 LOG_FORMAT = "%(asctime)-15s %(levelname)s: %(message)s" all_opt = { "help" : { "getopt" : "h", "longopt" : "help", "help" : "-h, --help Display this help and exit", "required" : "0", "shortdesc" : "Display help and exit", "order" : 55}, "version" : { "getopt" : "V", "longopt" : "version", "help" : "-V, --version Display version information and exit", "required" : "0", "shortdesc" : "Display version information and exit", "order" : 54}, "verbose" : { "getopt" : "v", "longopt" : "verbose", "help" : "-v, --verbose Verbose mode. " "Multiple -v flags can be stacked on the command line " "(e.g., -vvv) to increase verbosity.", "required" : "0", "order" : 51}, "verbose_level" : { "getopt" : ":", "longopt" : "verbose-level", "type" : "integer", "help" : "--verbose-level " "Level of debugging detail in output. Defaults to the " "number of --verbose flags specified on the command " "line, or to 1 if verbose=1 in a stonith device " "configuration (i.e., on stdin).", "required" : "0", "order" : 52}, "debug" : { "getopt" : "D:", "longopt" : "debug-file", "help" : "-D, --debug-file=[debugfile] Debugging to output file", "required" : "0", "shortdesc" : "Write debug information to given file", "order" : 53}, "delay" : { "getopt" : ":", "longopt" : "delay", "type" : "second", "help" : "--delay=[seconds] Wait X seconds before fencing is started", "required" : "0", "default" : "0", "order" : 200}, "agent" : { "getopt" : "", "help" : "", "order" : 1}, "web" : { "getopt" : "", "help" : "", "order" : 1}, "force_on" : { "getopt" : "", "help" : "", "order" : 1}, "action" : { "getopt" : "o:", "longopt" : "action", "help" : "-o, --action=[action] Action: status, reboot (default), off or on", "required" : "1", "shortdesc" : "Fencing action", "default" : "reboot", "order" : 1}, "fabric_fencing" : { "getopt" : "", "help" : "", "order" : 1}, "ipaddr" : { "getopt" : "a:", "longopt" : "ip", "help" : "-a, --ip=[ip] IP address or hostname of fencing device", "required" : "1", "order" : 1}, "ipport" : { "getopt" : "u:", "longopt" : "ipport", "type" : "integer", "help" : "-u, --ipport=[port] TCP/UDP port to use for connection", "required" : "0", "shortdesc" : "TCP/UDP port to use for connection with device", "order" : 1}, "login" : { "getopt" : "l:", "longopt" : "username", "help" : "-l, --username=[name] Login name", "required" : "?", "order" : 1}, "no_login" : { "getopt" : "", "help" : "", "order" : 1}, "no_password" : { "getopt" : "", "help" : "", "order" : 1}, "no_port" : { "getopt" : "", "help" : "", "order" : 1}, "no_status" : { "getopt" : "", "help" : "", "order" : 1}, "no_on" : { "getopt" : "", "help" : "", "order" : 1}, "no_off" : { "getopt" : "", "help" : "", "order" : 1}, "telnet" : { "getopt" : "", "help" : "", "order" : 1}, "diag" : { "getopt" : "", "help" : "", "order" : 1}, "passwd" : { "getopt" : "p:", "longopt" : "password", "help" : "-p, --password=[password] Login password or passphrase", "required" : "0", "order" : 1}, "passwd_script" : { "getopt" : "S:", "longopt" : "password-script", "help" : "-S, --password-script=[script] Script to run to retrieve password", "required" : "0", "order" : 1}, "identity_file" : { "getopt" : "k:", "longopt" : "identity-file", "help" : "-k, --identity-file=[filename] Identity file (private key) for SSH", "required" : "0", "order" : 1}, "cmd_prompt" : { "getopt" : "c:", "longopt" : "command-prompt", "help" : "-c, --command-prompt=[prompt] Force Python regex for command prompt", "required" : "0", "order" : 1}, "secure" : { "getopt" : "x", "longopt" : "ssh", "help" : "-x, --ssh Use SSH connection", "required" : "0", "order" : 1}, "ssh_options" : { "getopt" : ":", "longopt" : "ssh-options", "help" : "--ssh-options=[options] SSH options to use", "required" : "0", "order" : 1}, "ssl" : { "getopt" : "z", "longopt" : "ssl", "help" : "-z, --ssl Use SSL connection with verifying certificate", "required" : "0", "order" : 1}, "ssl_insecure" : { "getopt" : "", "longopt" : "ssl-insecure", "help" : "--ssl-insecure Use SSL connection without verifying certificate", "required" : "0", "order" : 1}, "ssl_secure" : { "getopt" : "", "longopt" : "ssl-secure", "help" : "--ssl-secure Use SSL connection with verifying certificate", "required" : "0", "order" : 1}, "notls" : { "getopt" : "t", "longopt" : "notls", "help" : "-t, --notls " "Disable TLS negotiation and force SSL3.0. " "This should only be used for devices that do not support TLS1.0 and up.", "required" : "0", "order" : 1}, "tls1.0" : { "getopt" : "", "longopt" : "tls1.0", "help" : "--tls1.0 " "Disable TLS negotiation and force TLS1.0. " "This should only be used for devices that do not support TLS1.1 and up.", "required" : "0", "order" : 1}, "port" : { "getopt" : "n:", "longopt" : "plug", "help" : "-n, --plug=[id] " "Physical plug number on device, UUID or identification of machine", "required" : "1", "order" : 1}, "switch" : { "getopt" : "s:", "longopt" : "switch", "help" : "-s, --switch=[id] Physical switch number on device", "required" : "0", "order" : 1}, "exec" : { "getopt" : "e:", "longopt" : "exec", "help" : "-e, --exec=[command] Command to execute", "required" : "0", "order" : 1}, "vmware_type" : { "getopt" : "d:", "longopt" : "vmware_type", "help" : "-d, --vmware_type=[type] Type of VMware to connect", "required" : "0", "order" : 1}, "vmware_datacenter" : { "getopt" : "s:", "longopt" : "vmware-datacenter", "help" : "-s, --vmware-datacenter=[dc] VMWare datacenter filter", "required" : "0", "order" : 2}, "snmp_version" : { "getopt" : "d:", "longopt" : "snmp-version", "help" : "-d, --snmp-version=[version] Specifies SNMP version to use (1|2c|3)", "required" : "0", "shortdesc" : "Specifies SNMP version to use", "choices" : ["1", "2c", "3"], "order" : 1}, "community" : { "getopt" : "c:", "longopt" : "community", "help" : "-c, --community=[community] Set the community string", "required" : "0", "order" : 1}, "snmp_auth_prot" : { "getopt" : "b:", "longopt" : "snmp-auth-prot", "help" : "-b, --snmp-auth-prot=[prot] Set authentication protocol (MD5|SHA)", "required" : "0", "shortdesc" : "Set authentication protocol", "choices" : ["MD5", "SHA"], "order" : 1}, "snmp_sec_level" : { "getopt" : "E:", "longopt" : "snmp-sec-level", "help" : "-E, --snmp-sec-level=[level] " "Set security level (noAuthNoPriv|authNoPriv|authPriv)", "required" : "0", "shortdesc" : "Set security level", "choices" : ["noAuthNoPriv", "authNoPriv", "authPriv"], "order" : 1}, "snmp_priv_prot" : { "getopt" : "B:", "longopt" : "snmp-priv-prot", "help" : "-B, --snmp-priv-prot=[prot] Set privacy protocol (DES|AES)", "required" : "0", "shortdesc" : "Set privacy protocol", "choices" : ["DES", "AES"], "order" : 1}, "snmp_priv_passwd" : { "getopt" : "P:", "longopt" : "snmp-priv-passwd", "help" : "-P, --snmp-priv-passwd=[pass] Set privacy protocol password", "required" : "0", "order" : 1}, "snmp_priv_passwd_script" : { "getopt" : "R:", "longopt" : "snmp-priv-passwd-script", "help" : "-R, --snmp-priv-passwd-script Script to run to retrieve privacy password", "required" : "0", "order" : 1}, "inet4_only" : { "getopt" : "4", "longopt" : "inet4-only", "help" : "-4, --inet4-only Forces agent to use IPv4 addresses only", "required" : "0", "order" : 1}, "inet6_only" : { "getopt" : "6", "longopt" : "inet6-only", "help" : "-6, --inet6-only Forces agent to use IPv6 addresses only", "required" : "0", "order" : 1}, "separator" : { "getopt" : "C:", "longopt" : "separator", "help" : "-C, --separator=[char] Separator for CSV created by 'list' operation", "default" : ",", "required" : "0", "order" : 100}, "login_timeout" : { "getopt" : ":", "longopt" : "login-timeout", "type" : "second", "help" : "--login-timeout=[seconds] Wait X seconds for cmd prompt after login", "default" : "5", "required" : "0", "order" : 200}, "shell_timeout" : { "getopt" : ":", "longopt" : "shell-timeout", "type" : "second", "help" : "--shell-timeout=[seconds] Wait X seconds for cmd prompt after issuing command", "default" : "3", "required" : "0", "order" : 200}, "power_timeout" : { "getopt" : ":", "longopt" : "power-timeout", "type" : "second", "help" : "--power-timeout=[seconds] Test X seconds for status change after ON/OFF", "default" : "20", "required" : "0", "order" : 200}, "disable_timeout" : { "getopt" : ":", "longopt" : "disable-timeout", "help" : "--disable-timeout=[true/false] Disable timeout (true/false) (default: true when run from Pacemaker 2.0+)", "required" : "0", "order" : 200}, "power_wait" : { "getopt" : ":", "longopt" : "power-wait", "type" : "second", "help" : "--power-wait=[seconds] Wait X seconds after issuing ON/OFF", "default" : "0", "required" : "0", "order" : 200}, "stonith_status_sleep" : { "getopt" : ":", "longopt" : "stonith-status-sleep", "type" : "second", "help" : "--stonith-status-sleep=[seconds] Sleep X seconds between status calls during a STONITH action", "default" : "1", "required" : "0", "order" : 200}, "missing_as_off" : { "getopt" : "", "longopt" : "missing-as-off", "help" : "--missing-as-off Missing port returns OFF instead of failure", "required" : "0", "order" : 200}, "retry_on" : { "getopt" : ":", "longopt" : "retry-on", "type" : "integer", "help" : "--retry-on=[attempts] Count of attempts to retry power on", "default" : "1", "required" : "0", "order" : 201}, "session_url" : { "getopt" : "s:", "longopt" : "session-url", "help" : "-s, --session-url URL to connect to XenServer on", "required" : "1", "order" : 1}, "sudo" : { "getopt" : "", "longopt" : "use-sudo", "help" : "--use-sudo Use sudo (without password) when calling 3rd party software", "required" : "0", "order" : 205}, "method" : { "getopt" : "m:", "longopt" : "method", "help" : "-m, --method=[method] Method to fence (onoff|cycle) (Default: onoff)", "required" : "0", "shortdesc" : "Method to fence", "default" : "onoff", "choices" : ["onoff", "cycle"], "order" : 1}, "telnet_path" : { "getopt" : ":", "longopt" : "telnet-path", "help" : "--telnet-path=[path] Path to telnet binary", "required" : "0", "default" : "@TELNET_PATH@", "order": 300}, "ssh_path" : { "getopt" : ":", "longopt" : "ssh-path", "help" : "--ssh-path=[path] Path to ssh binary", "required" : "0", "default" : "@SSH_PATH@", "order": 300}, "gnutlscli_path" : { "getopt" : ":", "longopt" : "gnutlscli-path", "help" : "--gnutlscli-path=[path] Path to gnutls-cli binary", "required" : "0", "default" : "@GNUTLSCLI_PATH@", "order": 300}, "sudo_path" : { "getopt" : ":", "longopt" : "sudo-path", "help" : "--sudo-path=[path] Path to sudo binary", "required" : "0", "default" : "@SUDO_PATH@", "order": 300}, "snmpwalk_path" : { "getopt" : ":", "longopt" : "snmpwalk-path", "help" : "--snmpwalk-path=[path] Path to snmpwalk binary", "required" : "0", "default" : "@SNMPWALK_PATH@", "order" : 300}, "snmpset_path" : { "getopt" : ":", "longopt" : "snmpset-path", "help" : "--snmpset-path=[path] Path to snmpset binary", "required" : "0", "default" : "@SNMPSET_PATH@", "order" : 300}, "snmpget_path" : { "getopt" : ":", "longopt" : "snmpget-path", "help" : "--snmpget-path=[path] Path to snmpget binary", "required" : "0", "default" : "@SNMPGET_PATH@", "order" : 300}, "snmp": { "getopt" : "", "help" : "", "order" : 1}, "port_as_ip": { "getopt" : "", "longopt" : "port-as-ip", "help" : "--port-as-ip Make \"port/plug\" to be an alias to IP address", "required" : "0", "order" : 200}, "on_target": { "getopt" : "", "help" : "", "order" : 1}, "quiet": { "getopt" : "q", "longopt": "quiet", "help" : "-q, --quiet Disable logging to stderr. Does not affect --verbose or --debug-file or logging to syslog.", "required" : "0", "order" : 50} } # options which are added automatically if 'key' is encountered ("default" is always added) DEPENDENCY_OPT = { "default" : ["help", "debug", "verbose", "verbose_level", "version", "action", "agent", "power_timeout", "shell_timeout", "login_timeout", "disable_timeout", "power_wait", "stonith_status_sleep", "retry_on", "delay", "quiet"], "passwd" : ["passwd_script"], "sudo" : ["sudo_path"], "secure" : ["identity_file", "ssh_options", "ssh_path", "inet4_only", "inet6_only"], "telnet" : ["telnet_path"], "ipaddr" : ["ipport"], "port" : ["separator"], "ssl" : ["ssl_secure", "ssl_insecure", "gnutlscli_path"], "snmp" : ["snmp_auth_prot", "snmp_sec_level", "snmp_priv_prot", \ "snmp_priv_passwd", "snmp_priv_passwd_script", "community", \ "snmpset_path", "snmpget_path", "snmpwalk_path"] } class fspawn(pexpect.spawn): def __init__(self, options, command, **kwargs): if sys.version_info[0] > 2: kwargs.setdefault('encoding', 'utf-8') logging.info("Running command: %s", command) pexpect.spawn.__init__(self, command, **kwargs) self.opt = options def log_expect(self, pattern, timeout): result = self.expect(pattern, timeout if timeout != 0 else None) logging.debug("Received: %s", self.before + self.after) return result def read_nonblocking(self, size, timeout): return pexpect.spawn.read_nonblocking(self, size=100, timeout=timeout if timeout != 0 else None) def send(self, message): logging.debug("Sent: %s", message) return pexpect.spawn.send(self, message) # send EOL according to what was detected in login process (telnet) def send_eol(self, message): return self.send(message + self.opt["eol"]) def frun(command, timeout=30, withexitstatus=False, events=None, extra_args=None, logfile=None, cwd=None, env=None, **kwargs): if sys.version_info[0] > 2: kwargs.setdefault('encoding', 'utf-8') return pexpect.run(command, timeout=timeout if timeout != 0 else None, withexitstatus=withexitstatus, events=events, extra_args=extra_args, logfile=logfile, cwd=cwd, env=env, **kwargs) def atexit_handler(): try: sys.stdout.close() os.close(1) except IOError: logging.error("%s failed to close standard output\n", sys.argv[0]) sys.exit(EC_GENERIC_ERROR) def _add_dependency_options(options): ## Add also options which are available for every fence agent added_opt = [] for opt in options + ["default"]: if opt in DEPENDENCY_OPT: added_opt.extend([y for y in DEPENDENCY_OPT[opt] if options.count(y) == 0]) if not "port" in (options + added_opt) and \ not "nodename" in (options + added_opt) and \ "ipaddr" in (options + added_opt): added_opt.append("port_as_ip") all_opt["port"]["help"] = "-n, --plug=[ip] IP address or hostname of fencing device " \ "(together with --port-as-ip)" return added_opt def fail_usage(message="", stop=True): if len(message) > 0: logging.error("%s\n", message) if stop: logging.error("Please use '-h' for usage\n") sys.exit(EC_GENERIC_ERROR) def fail(error_code, stop=True): message = { EC_LOGIN_DENIED : "Unable to connect/login to fencing device", EC_CONNECTION_LOST : "Connection lost", EC_TIMED_OUT : "Connection timed out", EC_WAITING_ON : "Failed: Timed out waiting to power ON", EC_WAITING_OFF : "Failed: Timed out waiting to power OFF", EC_STATUS : "Failed: Unable to obtain correct plug status or plug is not available", EC_STATUS_HMC : "Failed: Either unable to obtain correct plug status, " "partition is not available or incorrect HMC version used", EC_PASSWORD_MISSING : "Failed: You have to set login password", EC_INVALID_PRIVILEGES : "Failed: The user does not have the correct privileges to do the requested action.", EC_FETCH_VM_UUID : "Failed: Can not find VM UUID by its VM name given in the parameter." }[error_code] + "\n" logging.error("%s\n", message) if stop: sys.exit(EC_GENERIC_ERROR) def usage(avail_opt): print("Usage:") print("\t" + os.path.basename(sys.argv[0]) + " [options]") print("Options:") sorted_list = [(key, all_opt[key]) for key in avail_opt] sorted_list.sort(key=lambda x: x[1]["order"]) for key, value in sorted_list: if len(value["help"]) != 0: print(" " + _join_wrap([value["help"]], first_indent=3)) def metadata(options, avail_opt, docs): # avail_opt has to be unique, if there are duplicities then they should be removed sorted_list = [(key, all_opt[key]) for key in list(set(avail_opt)) if "longopt" in all_opt[key]] # Find keys that are going to replace inconsistent names mapping = dict([(opt["longopt"].replace("-", "_"), key) for (key, opt) in sorted_list if (key != opt["longopt"].replace("-", "_"))]) new_options = [(key, all_opt[mapping[key]]) for key in mapping] sorted_list.extend(new_options) sorted_list.sort(key=lambda x: (x[1]["order"], x[0])) if options["--action"] == "metadata": docs["longdesc"] = re.sub("\\\\f[BPIR]|\.P|\.TP|\.br\n", "", docs["longdesc"]) print("") print("") for (symlink, desc) in docs.get("symlink", []): print("") print("" + docs["longdesc"] + "") print("" + docs["vendorurl"] + "") print("") for (key, opt) in sorted_list: info = "" if key in all_opt: if key != all_opt[key].get('longopt', key).replace("-", "_"): info = "deprecated=\"1\"" else: info = "obsoletes=\"%s\"" % (mapping.get(key)) if "help" in opt and len(opt["help"]) > 0: if info != "": info = " " + info print("\t") default = "" if "default" in opt: default = "default=\"" + _encode_html_entities(str(opt["default"])) + "\" " mixed = opt["help"] ## split it between option and help text res = re.compile(r"^(.*?--\S+)\s+", re.IGNORECASE | re.S).search(mixed) if None != res: mixed = res.group(1) mixed = _encode_html_entities(mixed) if not "shortdesc" in opt: shortdesc = re.sub(".*\s\s+", "", opt["help"][31:]) else: shortdesc = opt["shortdesc"] print("\t\t") if "choices" in opt: print("\t\t") for choice in opt["choices"]: print("\t\t\t") elif opt["getopt"].count(":") > 0: t = opt.get("type", "string") print("\t\t") else: print("\t\t") print("\t\t" + shortdesc + "") print("\t") print("") print("") (available_actions, _) = _get_available_actions(avail_opt) if "on" in available_actions: available_actions.remove("on") on_target = ' on_target="1"' if avail_opt.count("on_target") else '' print("\t" % (on_target, avail_opt.count("fabric_fencing"))) for action in available_actions: print("\t" % (action)) print("") print("") def process_input(avail_opt): avail_opt.extend(_add_dependency_options(avail_opt)) # @todo: this should be put elsewhere? os.putenv("LANG", "C") os.putenv("LC_ALL", "C") if "port_as_ip" in avail_opt: avail_opt.append("port") if len(sys.argv) > 1: opt = _parse_input_cmdline(avail_opt) else: opt = _parse_input_stdin(avail_opt) if "--port-as-ip" in opt and "--plug" in opt: opt["--ip"] = opt["--plug"] return opt ## ## This function checks input and answers if we want to have same answers ## in each of the fencing agents. It looks for possible errors and run ## password script to set a correct password ###### def check_input(device_opt, opt, other_conditions = False): device_opt.extend(_add_dependency_options(device_opt)) options = dict(opt) options["device_opt"] = device_opt _update_metadata(options) options = _set_default_values(options) options["--action"] = options["--action"].lower() ## In special cases (show help, metadata or version) we don't need to check anything ##### # OCF compatibility if options["--action"] == "meta-data": options["--action"] = "metadata" if options["--action"] in ["metadata", "manpage"] or any(k in options for k in ("--help", "--version")): return options try: options["--verbose-level"] = int(options["--verbose-level"]) except ValueError: options["--verbose-level"] = -1 if options["--verbose-level"] < 0: logging.warning("Parse error: Option 'verbose_level' must " "be an integer greater than or equal to 0. " "Setting verbose_level to 0.") options["--verbose-level"] = 0 if options["--verbose-level"] == 0 and "--verbose" in options: logging.warning("Parse error: Ignoring option 'verbose' " "because it conflicts with verbose_level=0") del options["--verbose"] if options["--verbose-level"] > 0: # Ensure verbose key exists options["--verbose"] = 1 if "--verbose" in options: logging.getLogger().setLevel(logging.DEBUG) formatter = logging.Formatter(LOG_FORMAT) ## add logging to syslog logging.getLogger().addHandler(SyslogLibHandler()) if "--quiet" not in options: ## add logging to stderr stderrHandler = logging.StreamHandler(sys.stderr) stderrHandler.setFormatter(formatter) logging.getLogger().addHandler(stderrHandler) (acceptable_actions, _) = _get_available_actions(device_opt) if 1 == device_opt.count("fabric_fencing"): acceptable_actions.extend(["enable", "disable"]) if 0 == acceptable_actions.count(options["--action"]): fail_usage("Failed: Unrecognised action '" + options["--action"] + "'") ## Compatibility layer ##### if options["--action"] == "enable": options["--action"] = "on" if options["--action"] == "disable": options["--action"] = "off" if options["--action"] == "validate-all" and not other_conditions: if not _validate_input(options, False): fail_usage("validate-all failed") sys.exit(EC_OK) else: _validate_input(options, True) if "--debug-file" in options: try: debug_file = logging.FileHandler(options["--debug-file"]) debug_file.setLevel(logging.DEBUG) debug_file.setFormatter(formatter) logging.getLogger().addHandler(debug_file) except IOError: logging.error("Unable to create file %s", options["--debug-file"]) fail_usage("Failed: Unable to create file " + options["--debug-file"]) if "--snmp-priv-passwd-script" in options: options["--snmp-priv-passwd"] = os.popen(options["--snmp-priv-passwd-script"]).read().rstrip() if "--password-script" in options: options["--password"] = os.popen(options["--password-script"]).read().rstrip() + if "--ssl-secure" in options or "--ssl-insecure" in options: + options["--ssl"] = "" + + if "--ssl" in options and "--ssl-insecure" not in options: + options["--ssl-secure"] = "" + if os.environ.get("PCMK_service") == "pacemaker-fenced" and "--disable-timeout" not in options: options["--disable-timeout"] = "1" if options.get("--disable-timeout", "").lower() in ["1", "yes", "on", "true"]: options["--power-timeout"] = options["--shell-timeout"] = options["--login-timeout"] = 0 return options ## Obtain a power status from possibly more than one plug ## "on" is returned if at least one plug is ON ###### def get_multi_power_fn(connection, options, get_power_fn): status = "off" plugs = options["--plugs"] if "--plugs" in options else [""] for plug in plugs: try: options["--uuid"] = str(uuid.UUID(plug)) except ValueError: pass except KeyError: pass options["--plug"] = plug plug_status = get_power_fn(connection, options) if plug_status != "off": status = plug_status return status def async_set_multi_power_fn(connection, options, set_power_fn, get_power_fn, retry_attempts): plugs = options["--plugs"] if "--plugs" in options else [""] for _ in range(retry_attempts): for plug in plugs: try: options["--uuid"] = str(uuid.UUID(plug)) except ValueError: pass except KeyError: pass options["--plug"] = plug set_power_fn(connection, options) time.sleep(int(options["--power-wait"])) for _ in itertools.count(1): if get_multi_power_fn(connection, options, get_power_fn) != options["--action"]: time.sleep(int(options["--stonith-status-sleep"])) else: return True if int(options["--power-timeout"]) > 0 and _ >= int(options["--power-timeout"]): break return False def sync_set_multi_power_fn(connection, options, sync_set_power_fn, retry_attempts): success = True plugs = options["--plugs"] if "--plugs" in options else [""] for plug in plugs: try: options["--uuid"] = str(uuid.UUID(plug)) except ValueError: pass except KeyError: pass options["--plug"] = plug for retry in range(retry_attempts): if sync_set_power_fn(connection, options): break if retry == retry_attempts-1: success = False time.sleep(int(options["--power-wait"])) return success def set_multi_power_fn(connection, options, set_power_fn, get_power_fn, sync_set_power_fn, retry_attempts=1): if set_power_fn != None: if get_power_fn != None: return async_set_multi_power_fn(connection, options, set_power_fn, get_power_fn, retry_attempts) elif sync_set_power_fn != None: return sync_set_multi_power_fn(connection, options, sync_set_power_fn, retry_attempts) return False def multi_reboot_cycle_fn(connection, options, reboot_cycle_fn, retry_attempts=1): success = True plugs = options["--plugs"] if "--plugs" in options else [""] for plug in plugs: try: options["--uuid"] = str(uuid.UUID(plug)) except ValueError: pass except KeyError: pass options["--plug"] = plug for retry in range(retry_attempts): if reboot_cycle_fn(connection, options): break if retry == retry_attempts-1: success = False time.sleep(int(options["--power-wait"])) return success def show_docs(options, docs=None): device_opt = options["device_opt"] if docs == None: docs = {} docs["shortdesc"] = "Fence agent" docs["longdesc"] = "" if "--help" in options: usage(device_opt) sys.exit(0) if options.get("--action", "") in ["metadata", "manpage"]: if "port_as_ip" in device_opt: device_opt.remove("separator") metadata(options, device_opt, docs) sys.exit(0) if "--version" in options: print(RELEASE_VERSION) sys.exit(0) def fence_action(connection, options, set_power_fn, get_power_fn, get_outlet_list=None, reboot_cycle_fn=None, sync_set_power_fn=None): result = 0 try: if "--plug" in options: options["--plugs"] = options["--plug"].split(",") ## Process options that manipulate fencing device ##### if (options["--action"] in ["list", "list-status"]) or \ ((options["--action"] == "monitor") and 1 == options["device_opt"].count("port") and \ 0 == options["device_opt"].count("port_as_ip")): if 0 == options["device_opt"].count("port"): print("N/A") elif get_outlet_list == None: ## @todo: exception? ## This is just temporal solution, we will remove default value ## None as soon as all existing agent will support this operation print("NOTICE: List option is not working on this device yet") else: options["--original-action"] = options["--action"] options["--action"] = "list" outlets = get_outlet_list(connection, options) options["--action"] = options["--original-action"] del options["--original-action"] ## keys can be numbers (port numbers) or strings (names of VM, UUID) for outlet_id in list(outlets.keys()): (alias, status) = outlets[outlet_id] if status is None or (not status.upper() in ["ON", "OFF"]): status = "UNKNOWN" status = status.upper() if options["--action"] == "list": try: print(outlet_id + options["--separator"] + alias) except UnicodeEncodeError as e: print((outlet_id + options["--separator"] + alias).encode("utf-8")) elif options["--action"] == "list-status": try: print(outlet_id + options["--separator"] + alias + options["--separator"] + status) except UnicodeEncodeError as e: print((outlet_id + options["--separator"] + alias).encode("utf-8") + options["--separator"] + status) return if options["--action"] == "monitor" and not "port" in options["device_opt"] and "no_status" in options["device_opt"]: # Unable to do standard monitoring because 'status' action is not available return 0 status = None if not "no_status" in options["device_opt"]: status = get_multi_power_fn(connection, options, get_power_fn) if status != "on" and status != "off": fail(EC_STATUS) if options["--action"] == status: if not (status == "on" and "force_on" in options["device_opt"]): print("Success: Already %s" % (status.upper())) return 0 if options["--action"] == "on": if set_multi_power_fn(connection, options, set_power_fn, get_power_fn, sync_set_power_fn, 1 + int(options["--retry-on"])): print("Success: Powered ON") else: fail(EC_WAITING_ON) elif options["--action"] == "off": if set_multi_power_fn(connection, options, set_power_fn, get_power_fn, sync_set_power_fn): print("Success: Powered OFF") else: fail(EC_WAITING_OFF) elif options["--action"] == "reboot": power_on = False if options.get("--method", "").lower() == "cycle" and reboot_cycle_fn is not None: try: power_on = multi_reboot_cycle_fn(connection, options, reboot_cycle_fn, 1 + int(options["--retry-on"])) except Exception as ex: # an error occured during reboot action logging.warning("%s", str(ex)) if not power_on: fail(EC_TIMED_OUT) else: if status != "off": options["--action"] = "off" if not set_multi_power_fn(connection, options, set_power_fn, get_power_fn, sync_set_power_fn): fail(EC_WAITING_OFF) options["--action"] = "on" try: power_on = set_multi_power_fn(connection, options, set_power_fn, get_power_fn, sync_set_power_fn, int(options["--retry-on"])) except Exception as ex: # an error occured during power ON phase in reboot # fence action was completed succesfully even in that case logging.warning("%s", str(ex)) # switch back to original action for the case it is used lateron options["--action"] = "reboot" if power_on == False: # this should not fail as node was fenced succesfully logging.error('Timed out waiting to power ON\n') print("Success: Rebooted") elif options["--action"] == "status": print("Status: " + status.upper()) if status.upper() == "OFF": result = 2 elif options["--action"] == "monitor": pass except pexpect.EOF: fail(EC_CONNECTION_LOST) except pexpect.TIMEOUT: fail(EC_TIMED_OUT) except pycurl.error as ex: logging.error("%s\n", str(ex)) fail(EC_TIMED_OUT) except socket.timeout as ex: logging.error("%s\n", str(ex)) fail(EC_TIMED_OUT) return result def fence_login(options, re_login_string=r"(login\s*: )|((?!Last )Login Name: )|(username: )|(User Name :)"): run_delay(options) if "eol" not in options: options["eol"] = "\r\n" if "--command-prompt" in options and type(options["--command-prompt"]) is not list: options["--command-prompt"] = [options["--command-prompt"]] try: if "--ssl" in options: conn = _open_ssl_connection(options) elif "--ssh" in options and "--identity-file" not in options: conn = _login_ssh_with_password(options, re_login_string) elif "--ssh" in options and "--identity-file" in options: conn = _login_ssh_with_identity_file(options) else: conn = _login_telnet(options, re_login_string) except pexpect.EOF as exception: logging.debug("%s", str(exception)) fail(EC_LOGIN_DENIED) except pexpect.TIMEOUT as exception: logging.debug("%s", str(exception)) fail(EC_LOGIN_DENIED) return conn def is_executable(path): if os.path.exists(path): stats = os.stat(path) if stat.S_ISREG(stats.st_mode) and os.access(path, os.X_OK): return True return False def run_command(options, command, timeout=None, env=None, log_command=None): if timeout is None and "--power-timeout" in options: timeout = options["--power-timeout"] if timeout is not None: timeout = float(timeout) logging.info("Executing: %s\n", log_command or command) try: process = subprocess.Popen(shlex.split(command), stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env, # decodes newlines and in python3 also converts bytes to str universal_newlines=(sys.version_info[0] > 2)) except OSError: fail_usage("Unable to run %s\n" % command) thread = threading.Thread(target=process.wait) thread.start() thread.join(timeout if timeout else None) if thread.is_alive(): process.kill() fail(EC_TIMED_OUT, stop=(int(options.get("retry", 0)) < 1)) status = process.wait() (pipe_stdout, pipe_stderr) = process.communicate() process.stdout.close() process.stderr.close() logging.debug("%s %s %s\n", str(status), str(pipe_stdout), str(pipe_stderr)) return (status, pipe_stdout, pipe_stderr) def run_delay(options, reserve=0, result=0): ## Delay is important for two-node clusters fencing ## but we do not need to delay 'status' operations ## and get us out quickly if we already know that we are gonna fail ## still wanna do something right before fencing? - reserve some time if options["--action"] in ["off", "reboot"] \ and options["--delay"] != "0" \ and result == 0 \ and reserve >= 0: time_left = 1 + int(options["--delay"]) - (time.time() - run_delay.time_start) - reserve if time_left > 0: logging.info("Delay %d second(s) before logging in to the fence device", time_left) time.sleep(time_left) # mark time when fence-agent is started run_delay.time_start = time.time() def fence_logout(conn, logout_string, sleep=0): # Logout is not required part of fencing but we should attempt to do it properly # In some cases our 'exit' command is faster and we can not close connection as it # was already closed by fencing device try: conn.send_eol(logout_string) time.sleep(sleep) conn.close() except OSError: pass except pexpect.ExceptionPexpect: pass def source_env(env_file): # POSIX: name shall not contain '=', value doesn't contain '\0' output = subprocess.check_output("source {} && env -0".format(env_file), shell=True, executable="/bin/sh") # replace env os.environ.clear() os.environ.update(line.partition('=')[::2] for line in output.decode("utf-8").split('\0')) # Convert array of format [[key1, value1], [key2, value2], ... [keyN, valueN]] to dict, where key is # in format a.b.c.d...z and returned dict has key only z def array_to_dict(array): return dict([[x[0].split(".")[-1], x[1]] for x in array]) ## Own logger handler that uses old-style syslog handler as otherwise everything is sourced ## from /dev/syslog class SyslogLibHandler(logging.StreamHandler): """ A handler class that correctly push messages into syslog """ def emit(self, record): syslog_level = { logging.CRITICAL:syslog.LOG_CRIT, logging.ERROR:syslog.LOG_ERR, logging.WARNING:syslog.LOG_WARNING, logging.INFO:syslog.LOG_INFO, logging.DEBUG:syslog.LOG_DEBUG, logging.NOTSET:syslog.LOG_DEBUG, }[record.levelno] msg = self.format(record) # syslos.syslog can not have 0x00 character inside or exception is thrown syslog.syslog(syslog_level, msg.replace("\x00", "\n")) return def _open_ssl_connection(options): gnutls_opts = "" ssl_opts = "" if "--notls" in options: gnutls_opts = "--priority \"NORMAL:-VERS-TLS1.2:-VERS-TLS1.1:-VERS-TLS1.0:+VERS-SSL3.0\"" elif "--tls1.0" in options: gnutls_opts = "--priority \"NORMAL:-VERS-TLS1.2:-VERS-TLS1.1:+VERS-TLS1.0:%LATEST_RECORD_VERSION\"" # --ssl is same as the --ssl-secure; it means we want to verify certificate in these cases if "--ssl-insecure" in options: ssl_opts = "--insecure" command = '%s %s %s --crlf -p %s %s' % \ (options["--gnutlscli-path"], gnutls_opts, ssl_opts, options["--ipport"], options["--ip"]) try: conn = fspawn(options, command) except pexpect.ExceptionPexpect as ex: logging.error("%s\n", str(ex)) sys.exit(EC_GENERIC_ERROR) return conn def _login_ssh_with_identity_file(options): if "--inet6-only" in options: force_ipvx = "-6 " elif "--inet4-only" in options: force_ipvx = "-4 " else: force_ipvx = "" command = '%s %s %s@%s -i %s -p %s' % \ (options["--ssh-path"], force_ipvx, options["--username"], options["--ip"], \ options["--identity-file"], options["--ipport"]) if "--ssh-options" in options: command += ' ' + options["--ssh-options"] conn = fspawn(options, command) result = conn.log_expect(["Enter passphrase for key '" + options["--identity-file"] + "':", \ "Are you sure you want to continue connecting (yes/no)?"] + \ options["--command-prompt"], int(options["--login-timeout"])) if result == 1: conn.sendline("yes") result = conn.log_expect( ["Enter passphrase for key '" + options["--identity-file"]+"':"] + \ options["--command-prompt"], int(options["--login-timeout"])) if result == 0: if "--password" in options: conn.sendline(options["--password"]) conn.log_expect(options["--command-prompt"], int(options["--login-timeout"])) else: fail_usage("Failed: You have to enter passphrase (-p) for identity file") return conn def _login_telnet(options, re_login_string): re_login = re.compile(re_login_string, re.IGNORECASE) re_pass = re.compile("(password)|(pass phrase)", re.IGNORECASE) conn = fspawn(options, options["--telnet-path"]) conn.send("set binary\n") conn.send("open %s -%s\n"%(options["--ip"], options["--ipport"])) conn.log_expect(re_login, int(options["--login-timeout"])) conn.send_eol(options["--username"]) ## automatically change end of line separator screen = conn.read_nonblocking(size=100, timeout=int(options["--shell-timeout"])) if re_login.search(screen) != None: options["eol"] = "\n" conn.send_eol(options["--username"]) conn.log_expect(re_pass, int(options["--login-timeout"])) elif re_pass.search(screen) == None: conn.log_expect(re_pass, int(options["--shell-timeout"])) try: conn.send_eol(options["--password"]) valid_password = conn.log_expect([re_login] + \ options["--command-prompt"], int(options["--shell-timeout"])) if valid_password == 0: ## password is invalid or we have to change EOL separator options["eol"] = "\r" conn.send_eol("") screen = conn.read_nonblocking(size=100, timeout=int(options["--shell-timeout"])) ## after sending EOL the fence device can either show 'Login' or 'Password' if re_login.search(conn.after + screen) != None: conn.send_eol("") conn.send_eol(options["--username"]) conn.log_expect(re_pass, int(options["--login-timeout"])) conn.send_eol(options["--password"]) conn.log_expect(options["--command-prompt"], int(options["--login-timeout"])) except KeyError: fail(EC_PASSWORD_MISSING) return conn def _login_ssh_with_password(options, re_login_string): re_login = re.compile(re_login_string, re.IGNORECASE) re_pass = re.compile("(password)|(pass phrase)", re.IGNORECASE) if "--inet6-only" in options: force_ipvx = "-6 " elif "--inet4-only" in options: force_ipvx = "-4 " else: force_ipvx = "" command = '%s %s %s@%s -p %s -o PubkeyAuthentication=no' % \ (options["--ssh-path"], force_ipvx, options["--username"], options["--ip"], options["--ipport"]) if "--ssh-options" in options: command += ' ' + options["--ssh-options"] conn = fspawn(options, command) if "telnet_over_ssh" in options: # This is for stupid ssh servers (like ALOM) which behave more like telnet # (ignore name and display login prompt) result = conn.log_expect( \ [re_login, "Are you sure you want to continue connecting (yes/no)?"], int(options["--login-timeout"])) if result == 1: conn.sendline("yes") # Host identity confirm conn.log_expect(re_login, int(options["--login-timeout"])) conn.sendline(options["--username"]) conn.log_expect(re_pass, int(options["--login-timeout"])) else: result = conn.log_expect( \ ["ssword:", "Are you sure you want to continue connecting (yes/no)?"], int(options["--login-timeout"])) if result == 1: conn.sendline("yes") conn.log_expect("ssword:", int(options["--login-timeout"])) conn.sendline(options["--password"]) conn.log_expect(options["--command-prompt"], int(options["--login-timeout"])) return conn # # To update metadata, we change values in all_opt def _update_metadata(options): device_opt = options["device_opt"] if device_opt.count("login") and device_opt.count("no_login") == 0: all_opt["login"]["required"] = "1" else: all_opt["login"]["required"] = "0" if device_opt.count("port_as_ip"): all_opt["ipaddr"]["required"] = "0" all_opt["port"]["required"] = "0" (available_actions, default_value) = _get_available_actions(device_opt) all_opt["action"]["default"] = default_value actions_with_default = \ [x if not x == all_opt["action"]["default"] else x + " (default)" for x in available_actions] all_opt["action"]["help"] = \ "-o, --action=[action] Action: %s" % (_join_wrap(actions_with_default, last_separator=" or ")) if device_opt.count("ipport"): default_value = None default_string = None if "default" in all_opt["ipport"]: default_value = all_opt["ipport"]["default"] elif device_opt.count("web") and device_opt.count("ssl"): default_value = "80" default_string = "(default 80, 443 if --ssl option is used)" elif device_opt.count("telnet") and device_opt.count("secure"): default_value = "23" default_string = "(default 23, 22 if --ssh option is used)" else: tcp_ports = {"community" : "161", "secure" : "22", "telnet" : "23", "web" : "80", "ssl" : "443"} # all cases where next command returns multiple results are covered by previous blocks protocol = [x for x in ["community", "secure", "ssl", "web", "telnet"] if device_opt.count(x)][0] default_value = tcp_ports[protocol] if default_string is None: all_opt["ipport"]["help"] = "-u, --ipport=[port] TCP/UDP port to use (default %s)" % \ (default_value) else: all_opt["ipport"]["help"] = "-u, --ipport=[port] TCP/UDP port to use\n" + " "*40 + default_string def _set_default_values(options): if "ipport" in options["device_opt"]: if not "--ipport" in options: if "default" in all_opt["ipport"]: options["--ipport"] = all_opt["ipport"]["default"] elif "community" in options["device_opt"]: options["--ipport"] = "161" elif "--ssh" in options or all_opt["secure"].get("default", "0") == "1": options["--ipport"] = "22" elif "--ssl" in options or all_opt["ssl"].get("default", "0") == "1": options["--ipport"] = "443" elif "--ssl-secure" in options or all_opt["ssl_secure"].get("default", "0") == "1": options["--ipport"] = "443" elif "--ssl-insecure" in options or all_opt["ssl_insecure"].get("default", "0") == "1": options["--ipport"] = "443" elif "web" in options["device_opt"]: options["--ipport"] = "80" elif "telnet" in options["device_opt"]: options["--ipport"] = "23" if "--ipport" in options: all_opt["ipport"]["default"] = options["--ipport"] for opt in options["device_opt"]: if "default" in all_opt[opt] and not opt == "ipport": getopt_long = "--" + all_opt[opt]["longopt"] if getopt_long not in options: options[getopt_long] = all_opt[opt]["default"] return options # stop = True/False : exit fence agent when problem is encountered def _validate_input(options, stop = True): device_opt = options["device_opt"] valid_input = True if "--username" not in options and \ device_opt.count("login") and (device_opt.count("no_login") == 0): valid_input = False fail_usage("Failed: You have to set login name", stop) if device_opt.count("ipaddr") and "--ip" not in options and "--managed" not in options and "--target" not in options: valid_input = False fail_usage("Failed: You have to enter fence address", stop) if device_opt.count("no_password") == 0: if 0 == device_opt.count("identity_file"): if not ("--password" in options or "--password-script" in options): valid_input = False fail_usage("Failed: You have to enter password or password script", stop) else: if not ("--password" in options or \ "--password-script" in options or "--identity-file" in options): valid_input = False fail_usage("Failed: You have to enter password, password script or identity file", stop) if "--ssh" not in options and "--identity-file" in options: valid_input = False fail_usage("Failed: You have to use identity file together with ssh connection (-x)", stop) if "--identity-file" in options and not os.path.isfile(options["--identity-file"]): valid_input = False fail_usage("Failed: Identity file " + options["--identity-file"] + " does not exist", stop) if (0 == ["list", "list-status", "monitor"].count(options["--action"])) and \ "--plug" not in options and device_opt.count("port") and \ device_opt.count("no_port") == 0 and not device_opt.count("port_as_ip"): valid_input = False fail_usage("Failed: You have to enter plug number or machine identification", stop) for failed_opt in _get_opts_with_invalid_choices(options): valid_input = False fail_usage("Failed: You have to enter a valid choice for %s from the valid values: %s" % \ ("--" + all_opt[failed_opt]["longopt"], str(all_opt[failed_opt]["choices"])), stop) for failed_opt in _get_opts_with_invalid_types(options): valid_input = False if all_opt[failed_opt]["type"] == "second": fail_usage("Failed: The value you have entered for %s is not a valid time in seconds" % \ ("--" + all_opt[failed_opt]["longopt"]), stop) else: fail_usage("Failed: The value you have entered for %s is not a valid %s" % \ ("--" + all_opt[failed_opt]["longopt"], all_opt[failed_opt]["type"]), stop) return valid_input def _encode_html_entities(text): return text.replace("&", "&").replace('"', """).replace('<', "<"). \ replace('>', ">").replace("'", "'") def _prepare_getopt_args(options): getopt_string = "" longopt_list = [] for k in options: if k in all_opt and all_opt[k]["getopt"] != ":": # getopt == ":" means that opt is without short getopt, but has value getopt_string += all_opt[k]["getopt"] elif k not in all_opt: fail_usage("Parse error: unknown option '"+k+"'") if k in all_opt and "longopt" in all_opt[k]: if all_opt[k]["getopt"].endswith(":"): longopt_list.append(all_opt[k]["longopt"] + "=") else: longopt_list.append(all_opt[k]["longopt"]) return (getopt_string, longopt_list) def _parse_input_stdin(avail_opt): opt = {} name = "" mapping_longopt_names = dict([(all_opt[o].get("longopt"), o) for o in avail_opt]) for line in sys.stdin.readlines(): line = line.strip() if (line.startswith("#")) or (len(line) == 0): continue (name, value) = (line + "=").split("=", 1) value = value[:-1] value = re.sub("^\"(.*)\"$", "\\1", value) if name.replace("-", "_") in mapping_longopt_names: name = mapping_longopt_names[name.replace("-", "_")] elif name.replace("_", "-") in mapping_longopt_names: name = mapping_longopt_names[name.replace("_", "-")] if avail_opt.count(name) == 0 and name in ["nodename"]: continue elif avail_opt.count(name) == 0: logging.warning("Parse error: Ignoring unknown option '%s'\n", line) continue if all_opt[name]["getopt"].endswith(":"): opt["--"+all_opt[name]["longopt"].rstrip(":")] = value elif value.lower() in ["1", "yes", "on", "true"]: opt["--"+all_opt[name]["longopt"]] = "1" elif value.lower() in ["0", "no", "off", "false"]: opt["--"+all_opt[name]["longopt"]] = "0" else: logging.warning("Parse error: Ignoring option '%s' because it does not have value\n", name) opt.setdefault("--verbose-level", opt.get("--verbose", 0)) return opt def _parse_input_cmdline(avail_opt): filtered_opts = {} _verify_unique_getopt(avail_opt) (getopt_string, longopt_list) = _prepare_getopt_args(avail_opt) try: (entered_opt, left_arg) = getopt.gnu_getopt(sys.argv[1:], getopt_string, longopt_list) if len(left_arg) > 0: logging.warning("Unused arguments on command line: %s" % (str(left_arg))) except getopt.GetoptError as error: fail_usage("Parse error: " + error.msg) for opt in avail_opt: filtered_opts.update({opt : all_opt[opt]}) # Short and long getopt names are changed to consistent "--" + long name (e.g. --username) long_opts = {} verbose_count = 0 for arg_name in [k for (k, v) in entered_opt]: all_key = [key for (key, value) in list(filtered_opts.items()) \ if "--" + value.get("longopt", "") == arg_name or "-" + value.get("getopt", "").rstrip(":") == arg_name][0] long_opts["--" + filtered_opts[all_key]["longopt"]] = dict(entered_opt)[arg_name] if all_key == "verbose": verbose_count += 1 long_opts.setdefault("--verbose-level", verbose_count) # This test is specific because it does not apply to input on stdin if "port_as_ip" in avail_opt and not "--port-as-ip" in long_opts and "--plug" in long_opts: fail_usage("Parser error: option -n/--plug is not recognized") return long_opts # for ["John", "Mary", "Eli"] returns "John, Mary and Eli" def _join2(words, normal_separator=", ", last_separator=" and "): if len(words) <= 1: return "".join(words) else: return last_separator.join([normal_separator.join(words[:-1]), words[-1]]) def _join_wrap(words, normal_separator=", ", last_separator=" and ", first_indent=42): x = _join2(words, normal_separator, last_separator) wrapper = textwrap.TextWrapper() wrapper.initial_indent = " "*first_indent wrapper.subsequent_indent = " "*40 wrapper.width = 85 wrapper.break_on_hyphens = False wrapper.break_long_words = False wrapped_text = "" for line in wrapper.wrap(x): wrapped_text += line + "\n" return wrapped_text.lstrip().rstrip("\n") def _get_opts_with_invalid_choices(options): options_failed = [] device_opt = options["device_opt"] for opt in device_opt: if "choices" in all_opt[opt]: longopt = "--" + all_opt[opt]["longopt"] possible_values_upper = [y.upper() for y in all_opt[opt]["choices"]] if longopt in options: options[longopt] = options[longopt].upper() if not options["--" + all_opt[opt]["longopt"]] in possible_values_upper: options_failed.append(opt) return options_failed def _get_opts_with_invalid_types(options): options_failed = [] device_opt = options["device_opt"] for opt in device_opt: if "type" in all_opt[opt]: longopt = "--" + all_opt[opt]["longopt"] if longopt in options: if all_opt[opt]["type"] in ["integer", "second"]: try: number = int(options["--" + all_opt[opt]["longopt"]]) except ValueError: options_failed.append(opt) return options_failed def _verify_unique_getopt(avail_opt): used_getopt = set() for opt in avail_opt: getopt_value = all_opt[opt].get("getopt", "").rstrip(":") if getopt_value and getopt_value in used_getopt: fail_usage("Short getopt for %s (-%s) is not unique" % (opt, getopt_value)) else: used_getopt.add(getopt_value) def _get_available_actions(device_opt): available_actions = ["on", "off", "reboot", "status", "list", "list-status", \ "monitor", "metadata", "manpage", "validate-all"] default_value = "reboot" if device_opt.count("fabric_fencing"): available_actions.remove("reboot") default_value = "off" if device_opt.count("no_status"): available_actions.remove("status") if device_opt.count("no_on"): available_actions.remove("on") if device_opt.count("no_off"): available_actions.remove("off") if not device_opt.count("separator"): available_actions.remove("list") available_actions.remove("list-status") if device_opt.count("diag"): available_actions.append("diag") return (available_actions, default_value) diff --git a/tests/data/metadata/fence_docker.xml b/tests/data/metadata/fence_docker.xml index 723e7228..51c7c470 100644 --- a/tests/data/metadata/fence_docker.xml +++ b/tests/data/metadata/fence_docker.xml @@ -1,176 +1,176 @@ fence_docker is I/O fencing agent which can be used with the Docker Engine containers. You can use this fence-agent without any authentication, or you can use TLS authentication (use --ssl option, more info about TLS authentication in docker: http://docs.docker.com/examples/https/). www.docker.io Fencing action IP address or hostname of fencing device IP address or hostname of fencing device - + TCP/UDP port to use for connection with device Method to fence Physical plug number on device, UUID or identification of machine Physical plug number on device, UUID or identification of machine - + Use SSL connection with verifying certificate Use SSL connection without verifying certificate Use SSL connection with verifying certificate Version of Docker Remote API (default: 1.11) Path to CA certificate (PEM format) for TLS authentication. Required if --ssl option is used. Path to client certificate (PEM format) for TLS authentication. Required if --ssl option is used. Path to client key (PEM format) for TLS authentication. Required if --ssl option is used. Disable logging to stderr. Does not affect --verbose or --debug-file or logging to syslog. Verbose mode. Multiple -v flags can be stacked on the command line (e.g., -vvv) to increase verbosity. Level of debugging detail in output. Defaults to the number of --verbose flags specified on the command line, or to 1 if verbose=1 in a stonith device configuration (i.e., on stdin). Write debug information to given file Write debug information to given file Display version information and exit Display help and exit Separator for CSV created by 'list' operation Wait X seconds before fencing is started Disable timeout (true/false) (default: true when run from Pacemaker 2.0+) Wait X seconds for cmd prompt after login Test X seconds for status change after ON/OFF Wait X seconds after issuing ON/OFF Wait X seconds for cmd prompt after issuing command Sleep X seconds between status calls during a STONITH action Count of attempts to retry power on Path to gnutls-cli binary diff --git a/tests/data/metadata/fence_pve.xml b/tests/data/metadata/fence_pve.xml index 9033bf54..afc231ac 100644 --- a/tests/data/metadata/fence_pve.xml +++ b/tests/data/metadata/fence_pve.xml @@ -1,192 +1,211 @@ The fence_pve agent can be used to fence virtual machines acting as nodes in a virtualized cluster. http://www.proxmox.com/ Fencing action IP Address or Hostname of a node within the Proxmox cluster. IP Address or Hostname of a node within the Proxmox cluster. TCP/UDP port to use for connection with device Login name Method to fence Login password or passphrase Script to run to retrieve password Login password or passphrase Script to run to retrieve password Id of the virtual machine. Id of the virtual machine. + + + + Use SSL connection with verifying certificate + + + + + Use SSL connection without verifying certificate + + + + + Use SSL connection with verifying certificate + Login name Proxmox node name on which machine is located. (Must be specified if not using --pve-node-auto) Automatically select proxmox node. (This option overrides --pve-node) Virtual machine type lxc or qemu. (Default: qemu) Replaced by --pve-node Replaced by --pve-node Disable logging to stderr. Does not affect --verbose or --debug-file or logging to syslog. Verbose mode. Multiple -v flags can be stacked on the command line (e.g., -vvv) to increase verbosity. Level of debugging detail in output. Defaults to the number of --verbose flags specified on the command line, or to 1 if verbose=1 in a stonith device configuration (i.e., on stdin). Write debug information to given file Write debug information to given file Display version information and exit Display help and exit Separator for CSV created by 'list' operation Wait X seconds before fencing is started Disable timeout (true/false) (default: true when run from Pacemaker 2.0+) Wait X seconds for cmd prompt after login Test X seconds for status change after ON/OFF Wait X seconds after issuing ON/OFF Wait X seconds for cmd prompt after issuing command Sleep X seconds between status calls during a STONITH action Count of attempts to retry power on + + + Path to gnutls-cli binary +