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" % (choice))
print("\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.ioFencing actionIP address or hostname of fencing deviceIP address or hostname of fencing device
-
+ TCP/UDP port to use for connection with deviceMethod to fencePhysical plug number on device, UUID or identification of machinePhysical plug number on device, UUID or identification of machine
-
+ Use SSL connection with verifying certificateUse SSL connection without verifying certificateUse SSL connection with verifying certificateVersion 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 fileWrite debug information to given fileDisplay version information and exitDisplay help and exitSeparator for CSV created by 'list' operationWait X seconds before fencing is startedDisable timeout (true/false) (default: true when run from Pacemaker 2.0+)Wait X seconds for cmd prompt after loginTest X seconds for status change after ON/OFFWait X seconds after issuing ON/OFFWait X seconds for cmd prompt after issuing commandSleep X seconds between status calls during a STONITH actionCount of attempts to retry power onPath 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 actionIP 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 deviceLogin nameMethod to fenceLogin password or passphraseScript to run to retrieve passwordLogin password or passphraseScript to run to retrieve passwordId 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 nameProxmox 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-nodeReplaced by --pve-nodeDisable 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 fileWrite debug information to given fileDisplay version information and exitDisplay help and exitSeparator for CSV created by 'list' operationWait X seconds before fencing is startedDisable timeout (true/false) (default: true when run from Pacemaker 2.0+)Wait X seconds for cmd prompt after loginTest X seconds for status change after ON/OFFWait X seconds after issuing ON/OFFWait X seconds for cmd prompt after issuing commandSleep X seconds between status calls during a STONITH actionCount of attempts to retry power on
+
+
+ Path to gnutls-cli binary
+