summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/Makefile.am34
-rw-r--r--lib/XenAPI.py.py212
-rw-r--r--lib/azure_fence.py.py393
-rw-r--r--lib/check_used_options.py68
-rw-r--r--lib/fence.rng.head7
-rw-r--r--lib/fence.rng.tail13
-rw-r--r--lib/fence2man.xsl78
-rw-r--r--lib/fence2rng.xsl184
-rw-r--r--lib/fence2wiki.xsl14
-rw-r--r--lib/fencing.py.py1748
-rw-r--r--lib/fencing_snmp.py.py128
-rw-r--r--lib/metadata.rng80
-rw-r--r--lib/tests/test_fencing.py123
13 files changed, 3082 insertions, 0 deletions
diff --git a/lib/Makefile.am b/lib/Makefile.am
new file mode 100644
index 0000000..0fe7096
--- /dev/null
+++ b/lib/Makefile.am
@@ -0,0 +1,34 @@
+MAINTAINERCLEANFILES = Makefile.in
+
+TARGET = fencing.py fencing_snmp.py azure_fence.py
+
+if BUILD_XENAPILIB
+TARGET += XenAPI.py
+endif
+
+SRC = fencing.py.py fencing_snmp.py.py XenAPI.py.py azure_fence.py.py check_used_options.py
+
+XSL = fence2man.xsl fence2rng.xsl fence2wiki.xsl
+
+FASRNG = fence.rng.head fence.rng.tail metadata.rng
+
+EXTRA_DIST = $(SRC) $(XSL) $(FASRNG)
+
+fencelibdir = ${FENCEAGENTSLIBDIR}
+
+fencelib_DATA = $(TARGET)
+
+rngdir = ${CLUSTERDATA}/relaxng
+
+rng_DATA = $(XSL) $(FASRNG)
+
+azure_fence.py: fencing.py
+fencing_snmp.py: fencing.py
+check_used_options.py: fencing.py
+
+include $(top_srcdir)/make/fencebuild.mk
+
+xml-check: all
+xml-upload: all
+
+clean-man:
diff --git a/lib/XenAPI.py.py b/lib/XenAPI.py.py
new file mode 100644
index 0000000..6fe11ef
--- /dev/null
+++ b/lib/XenAPI.py.py
@@ -0,0 +1,212 @@
+#============================================================================
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of version 2.1 of the GNU Lesser General Public
+# License as published by the Free Software Foundation.
+#
+# 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, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#============================================================================
+# Copyright (C) 2006 XenSource Inc.
+#============================================================================
+#
+# Parts of this file are based upon xmlrpclib.py, the XML-RPC client
+# interface included in the Python distribution.
+#
+# Copyright (c) 1999-2002 by Secret Labs AB
+# Copyright (c) 1999-2002 by Fredrik Lundh
+#
+# By obtaining, using, and/or copying this software and/or its
+# associated documentation, you agree that you have read, understood,
+# and will comply with the following terms and conditions:
+#
+# Permission to use, copy, modify, and distribute this software and
+# its associated documentation for any purpose and without fee is
+# hereby granted, provided that the above copyright notice appears in
+# all copies, and that both that copyright notice and this permission
+# notice appear in supporting documentation, and that the name of
+# Secret Labs AB or the author not be used in advertising or publicity
+# pertaining to distribution of the software without specific, written
+# prior permission.
+#
+# SECRET LABS AB AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
+# TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANT-
+# ABILITY AND FITNESS. IN NO EVENT SHALL SECRET LABS AB OR THE AUTHOR
+# BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY
+# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
+# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
+# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
+# OF THIS SOFTWARE.
+# --------------------------------------------------------------------
+
+import sys
+import gettext
+import socket
+import logging
+
+if sys.version_info[0] > 2:
+ import xmlrpc.client as xmlrpclib
+ import http.client as httplib
+else:
+ import xmlrpclib
+ import httplib
+
+translation = gettext.translation('xen-xm', fallback=True)
+
+class Failure(Exception):
+ def __init__(self, details):
+ try:
+ # If this failure is MESSAGE_PARAMETER_COUNT_MISMATCH, then we
+ # correct the return values here, to account for the fact that we
+ # transparently add the session handle as the first argument.
+ if details[0] == 'MESSAGE_PARAMETER_COUNT_MISMATCH':
+ details[2] = str(int(details[2]) - 1)
+ details[3] = str(int(details[3]) - 1)
+
+ self.details = details
+ except Exception as exn:
+ self.details = ['INTERNAL_ERROR', 'Client-side: ' + str(exn)]
+
+ def __str__(self):
+ try:
+ return translation.ugettext(self.details[0]) % self._details_map()
+ except TypeError as exn:
+ return "Message database broken: %s.\nXen-API failure: %s" % \
+ (exn, str(self.details))
+ except Exception as exn:
+ logging.error("%s\n", str(exn))
+ return "Xen-API failure: %s" % str(self.details)
+
+ def _details_map(self):
+ return dict([(str(i), self.details[i])
+ for i in range(len(self.details))])
+
+
+_RECONNECT_AND_RETRY = (lambda _: ())
+
+class UDSHTTPConnection(httplib.HTTPConnection):
+ """ Stupid hacked up HTTPConnection subclass to allow HTTP over Unix domain
+ sockets. """
+ def connect(self):
+ path = self.host.replace("_", "/")
+ self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ self.sock.connect(path)
+
+class UDSTransport(xmlrpclib.Transport):
+ def make_connection(self, host):
+ return httplib.HTTPConnection(host)
+
+class Session(xmlrpclib.ServerProxy):
+ """A server proxy and session manager for communicating with Xend using
+ the Xen-API.
+
+ Example:
+
+ session = Session('http://localhost:9363/')
+ session.login_with_password('me', 'mypassword')
+ session.xenapi.VM.start(vm_uuid)
+ session.xenapi.session.logout()
+
+ For now, this class also supports the legacy XML-RPC API, using
+ session.xend.domain('Domain-0') and similar. This support will disappear
+ once there is a working Xen-API replacement for every call in the legacy
+ API.
+ """
+
+ def __init__(self, uri, transport=None, encoding=None, verbose=0,
+ allow_none=1):
+ xmlrpclib.ServerProxy.__init__(self, uri, transport, encoding,
+ verbose, allow_none)
+ self._session = None
+ self.last_login_method = None
+ self.last_login_params = None
+
+
+ def xenapi_request(self, methodname, params):
+ if methodname.startswith('login'):
+ self._login(methodname, params)
+ return None
+ else:
+ retry_count = 0
+ while retry_count < 3:
+ full_params = (self._session,) + params
+ result = _parse_result(getattr(self, methodname)(*full_params))
+ if result == _RECONNECT_AND_RETRY:
+ retry_count += 1
+ if self.last_login_method:
+ self._login(self.last_login_method,
+ self.last_login_params)
+ else:
+ raise xmlrpclib.Fault(401, 'You must log in')
+ else:
+ return result
+ raise xmlrpclib.Fault(
+ 500, 'Tried 3 times to get a valid session, but failed')
+
+
+ def _login(self, method, params):
+ result = _parse_result(getattr(self, 'session.%s' % method)(*params))
+ if result == _RECONNECT_AND_RETRY:
+ raise xmlrpclib.Fault(
+ 500, 'Received SESSION_INVALID when logging in')
+ self._session = result
+ self.last_login_method = method
+ self.last_login_params = params
+
+
+ def __getattr__(self, name):
+ if name == 'xenapi':
+ return _Dispatcher(self.xenapi_request, None)
+ elif name.startswith('login'):
+ return lambda *params: self._login(name, params)
+ else:
+ return xmlrpclib.ServerProxy.__getattr__(self, name)
+
+def xapi_local():
+ return Session("http://_var_xapi_xapi/", transport=UDSTransport())
+
+def _parse_result(result):
+ if type(result) != dict or 'Status' not in result:
+ raise xmlrpclib.Fault(500, 'Missing Status in response from server' + result)
+ if result['Status'] == 'Success':
+ if 'Value' in result:
+ return result['Value']
+ else:
+ raise xmlrpclib.Fault(500,
+ 'Missing Value in response from server')
+ else:
+ if 'ErrorDescription' in result:
+ if result['ErrorDescription'][0] == 'SESSION_INVALID':
+ return _RECONNECT_AND_RETRY
+ else:
+ raise Failure(result['ErrorDescription'])
+ else:
+ raise xmlrpclib.Fault(
+ 500, 'Missing ErrorDescription in response from server')
+
+
+# Based upon _Method from xmlrpclib.
+class _Dispatcher:
+ def __init__(self, send, name):
+ self.__send = send
+ self.__name = name
+
+ def __repr__(self):
+ if self.__name:
+ return '<XenAPI._Dispatcher for %s>' % self.__name
+ else:
+ return '<XenAPI._Dispatcher>'
+
+ def __getattr__(self, name):
+ if self.__name is None:
+ return _Dispatcher(self.__send, name)
+ else:
+ return _Dispatcher(self.__send, "%s.%s" % (self.__name, name))
+
+ def __call__(self, *args):
+ return self.__send(self.__name, args)
diff --git a/lib/azure_fence.py.py b/lib/azure_fence.py.py
new file mode 100644
index 0000000..5ca71eb
--- /dev/null
+++ b/lib/azure_fence.py.py
@@ -0,0 +1,393 @@
+import logging, re, time
+from fencing import fail_usage
+
+FENCE_SUBNET_NAME = "fence-subnet"
+FENCE_INBOUND_RULE_NAME = "FENCE_DENY_ALL_INBOUND"
+FENCE_INBOUND_RULE_DIRECTION = "Inbound"
+FENCE_OUTBOUND_RULE_NAME = "FENCE_DENY_ALL_OUTBOUND"
+FENCE_OUTBOUND_RULE_DIRECTION = "Outbound"
+FENCE_STATE_OFF = "off"
+FENCE_STATE_ON = "on"
+FENCE_TAG_SUBNET_ID = "FENCE_TAG_SUBNET_ID"
+FENCE_TAG_IP_TYPE = "FENCE_TAG_IP_TYPE"
+FENCE_TAG_IP = "FENCE_TAG_IP"
+IP_TYPE_DYNAMIC = "Dynamic"
+MAX_RETRY = 10
+RETRY_WAIT = 5
+
+class AzureSubResource:
+ Type = None
+ Name = None
+
+class AzureResource:
+ Id = None
+ SubscriptionId = None
+ ResourceGroupName = None
+ ResourceName = None
+ SubResources = []
+
+class AzureConfiguration:
+ RGName = None
+ VMName = None
+ SubscriptionId = None
+ Cloud = None
+ UseMSI = None
+ Tenantid = None
+ ApplicationId = None
+ ApplicationKey = None
+ Verbose = None
+
+def get_from_metadata(parameter):
+ import requests
+ try:
+ r = requests.get('http://169.254.169.254/metadata/instance?api-version=2017-08-01', headers = {"Metadata":"true"})
+ logging.debug("metadata: " + str(r.json()))
+ return str(r.json()["compute"][parameter])
+ except:
+ logging.warning("Not able to use metadata service. Am I running in Azure?")
+
+ return None
+
+def get_azure_resource(id):
+ match = re.match('(/subscriptions/([^/]*)/resourceGroups/([^/]*))(/providers/([^/]*/[^/]*)/([^/]*))?((/([^/]*)/([^/]*))*)', id)
+ if not match:
+ fail_usage("{get_azure_resource} cannot parse resource id %s" % id)
+
+ logging.debug("{get_azure_resource} found %s matches for %s" % (len(match.groups()), id))
+ iGroup = 0
+ while iGroup < len(match.groups()):
+ logging.debug("{get_azure_resource} group %s: %s" %(iGroup, match.group(iGroup)))
+ iGroup += 1
+
+ resource = AzureResource()
+ resource.Id = id
+ resource.SubscriptionId = match.group(2)
+ resource.SubResources = []
+
+ if len(match.groups()) > 3:
+ resource.ResourceGroupName = match.group(3)
+ logging.debug("{get_azure_resource} resource group %s" % resource.ResourceGroupName)
+
+ if len(match.groups()) > 6:
+ resource.ResourceName = match.group(6)
+ logging.debug("{get_azure_resource} resource name %s" % resource.ResourceName)
+
+ if len(match.groups()) > 7 and match.group(7):
+ splits = match.group(7).split("/")
+ logging.debug("{get_azure_resource} splitting subtypes '%s' (%s)" % (match.group(7), len(splits)))
+ i = 1 # the string starts with / so the first split is empty
+ while i < len(splits) - 1:
+ logging.debug("{get_azure_resource} creating subresource with type %s and name %s" % (splits[i], splits[i+1]))
+ subRes = AzureSubResource()
+ subRes.Type = splits[i]
+ subRes.Name = splits[i+1]
+ resource.SubResources.append(subRes)
+ i += 2
+
+ return resource
+
+def get_fence_subnet_for_config(ipConfig, network_client):
+ subnetResource = get_azure_resource(ipConfig.subnet.id)
+ logging.debug("{get_fence_subnet_for_config} testing virtual network %s in resource group %s for a fence subnet" %(subnetResource.ResourceName, subnetResource.ResourceGroupName))
+ vnet = network_client.virtual_networks.get(subnetResource.ResourceGroupName, subnetResource.ResourceName)
+ return get_subnet(vnet, FENCE_SUBNET_NAME)
+
+def get_subnet(vnet, subnetName):
+ for avSubnet in vnet.subnets:
+ logging.debug("{get_subnet} searching subnet %s testing subnet %s" % (subnetName, avSubnet.name))
+ if (avSubnet.name.lower() == subnetName.lower()):
+ logging.debug("{get_subnet} subnet found %s" % avSubnet)
+ return avSubnet
+
+def test_fence_subnet(fenceSubnet, nic, network_client):
+ logging.info("{test_fence_subnet}")
+ testOk = True
+ if not fenceSubnet:
+ testOk = False
+ logging.info("{test_fence_subnet} No fence subnet found for virtual network of network interface %s" % nic.id)
+ else:
+ if not fenceSubnet.network_security_group:
+ testOk = False
+ logging.info("{test_fence_subnet} Fence subnet %s has not network security group" % fenceSubnet.id)
+ else:
+ nsgResource = get_azure_resource(fenceSubnet.network_security_group.id)
+ logging.info("{test_fence_subnet} Getting network security group %s in resource group %s" % (nsgResource.ResourceName, nsgResource.ResourceGroupName))
+ nsg = network_client.network_security_groups.get(nsgResource.ResourceGroupName, nsgResource.ResourceName)
+ inboundRule = get_inbound_rule_for_nsg(nsg)
+ outboundRule = get_outbound_rule_for_nsg(nsg)
+ if not outboundRule:
+ testOk = False
+ logging.info("{test_fence_subnet} Network Securiy Group %s of fence subnet %s has no outbound security rule that blocks all traffic" % (nsgResource.ResourceName, fenceSubnet.id))
+ elif not inboundRule:
+ testOk = False
+ logging.info("{test_fence_subnet} Network Securiy Group %s of fence subnet %s has no inbound security rule that blocks all traffic" % (nsgResource.ResourceName, fenceSubnet.id))
+
+ return testOk
+
+def get_inbound_rule_for_nsg(nsg):
+ return get_rule_for_nsg(nsg, FENCE_INBOUND_RULE_NAME, FENCE_INBOUND_RULE_DIRECTION)
+
+def get_outbound_rule_for_nsg(nsg):
+ return get_rule_for_nsg(nsg, FENCE_OUTBOUND_RULE_NAME, FENCE_OUTBOUND_RULE_DIRECTION)
+
+def get_rule_for_nsg(nsg, ruleName, direction):
+ logging.info("{get_rule_for_nsg} Looking for security rule %s with direction %s" % (ruleName, direction))
+ if not nsg:
+ logging.info("{get_rule_for_nsg} Network security group not set")
+ return None
+
+ for rule in nsg.security_rules:
+ logging.info("{get_rule_for_nsg} Testing a %s securiy rule %s" % (rule.direction, rule.name))
+ if (rule.access == "Deny") and (rule.direction == direction) \
+ and (rule.source_port_range == "*") and (rule.destination_port_range == "*") \
+ and (rule.protocol == "*") and (rule.destination_address_prefix == "*") \
+ and (rule.source_address_prefix == "*") and (rule.provisioning_state == "Succeeded") \
+ and (rule.priority == 100) and (rule.name == ruleName):
+ logging.info("{get_rule_for_nsg} %s rule found" % direction)
+ return rule
+
+ return None
+
+def get_network_state(compute_client, network_client, rgName, vmName):
+ result = FENCE_STATE_ON
+
+ try:
+ vm = compute_client.virtual_machines.get(rgName, vmName, "instanceView")
+
+ allNICOK = True
+ for nicRef in vm.network_profile.network_interfaces:
+ nicresource = get_azure_resource(nicRef.id)
+ nic = network_client.network_interfaces.get(nicresource.ResourceGroupName, nicresource.ResourceName)
+ for ipConfig in nic.ip_configurations:
+ logging.info("{get_network_state} Testing ip configuration %s" % ipConfig.name)
+ fenceSubnet = get_fence_subnet_for_config(ipConfig, network_client)
+ testOk = test_fence_subnet(fenceSubnet, nic, network_client)
+ if not testOk:
+ allNICOK = False
+ elif fenceSubnet.id.lower() != ipConfig.subnet.id.lower():
+ logging.info("{get_network_state} IP configuration %s is not in fence subnet (ip subnet: %s, fence subnet: %s)" % (ipConfig.name, ipConfig.subnet.id.lower(), fenceSubnet.id.lower()))
+ allNICOK = False
+ if allNICOK:
+ logging.info("{get_network_state} All IP configurations of all network interfaces are in the fence subnet. Declaring VM as off")
+ result = FENCE_STATE_OFF
+ except Exception as e:
+ fail_usage("{get_network_state} Failed: %s" % e)
+
+ return result
+
+def set_network_state(compute_client, network_client, rgName, vmName, operation):
+ import msrestazure.azure_exceptions
+ logging.info("{set_network_state} Setting state %s for %s in resource group %s" % (operation, vmName, rgName))
+
+ vm = compute_client.virtual_machines.get(rgName, vmName, "instanceView")
+
+ operations = []
+ for nicRef in vm.network_profile.network_interfaces:
+ for attempt in range(0, MAX_RETRY):
+ try:
+ nicresource = get_azure_resource(nicRef.id)
+ nic = network_client.network_interfaces.get(nicresource.ResourceGroupName, nicresource.ResourceName)
+
+ if not nic.tags and operation == "block":
+ nic.tags = {}
+
+ logging.info("{set_network_state} Searching for tags required to unfence this virtual machine")
+ for ipConfig in nic.ip_configurations:
+ if operation == "block":
+ fenceSubnet = get_fence_subnet_for_config(ipConfig, network_client)
+ testOk = test_fence_subnet(fenceSubnet, nic, network_client)
+ if testOk:
+ logging.info("{set_network_state} Changing subnet of ip config of nic %s" % nic.id)
+ nic.tags[("%s_%s" % (FENCE_TAG_SUBNET_ID, ipConfig.name))] = ipConfig.subnet.id
+ nic.tags[("%s_%s" % (FENCE_TAG_IP_TYPE, ipConfig.name))] = ipConfig.private_ip_allocation_method
+ nic.tags[("%s_%s" % (FENCE_TAG_IP, ipConfig.name))] = ipConfig.private_ip_address
+ ipConfig.subnet = fenceSubnet
+ ipConfig.private_ip_allocation_method = IP_TYPE_DYNAMIC
+ else:
+ fail_usage("{set_network_state} Network interface id %s does not have a network security group." % nic.id)
+ elif operation == "unblock":
+ if not nic.tags:
+ fail_usage("{set_network_state} IP configuration %s is missing the required resource tags (empty)" % ipConfig.name)
+
+ subnetId = nic.tags.pop("%s_%s" % (FENCE_TAG_SUBNET_ID, ipConfig.name))
+ ipType = nic.tags.pop("%s_%s" % (FENCE_TAG_IP_TYPE, ipConfig.name))
+ ipAddress = nic.tags.pop("%s_%s" % (FENCE_TAG_IP, ipConfig.name))
+
+ if (subnetId and ipType and (ipAddress or (ipType.lower() == IP_TYPE_DYNAMIC.lower()))):
+ logging.info("{set_network_state} tags found (subnetId: %s, ipType: %s, ipAddress: %s)" % (subnetId, ipType, ipAddress))
+
+ subnetResource = get_azure_resource(subnetId)
+ vnet = network_client.virtual_networks.get(subnetResource.ResourceGroupName, subnetResource.ResourceName)
+ logging.info("{set_network_state} looking for subnet %s" % len(subnetResource.SubResources))
+ oldSubnet = get_subnet(vnet, subnetResource.SubResources[0].Name)
+ if not oldSubnet:
+ fail_usage("{set_network_state} subnet %s not found" % subnetId)
+
+ ipConfig.subnet = oldSubnet
+ ipConfig.private_ip_allocation_method = ipType
+ if ipAddress:
+ ipConfig.private_ip_address = ipAddress
+ else:
+ fail_usage("{set_network_state} IP configuration %s is missing the required resource tags(subnetId: %s, ipType: %s, ipAddress: %s)" % (ipConfig.name, subnetId, ipType, ipAddress))
+
+ logging.info("{set_network_state} updating nic %s" % (nic.id))
+ op = network_client.network_interfaces.create_or_update(nicresource.ResourceGroupName, nicresource.ResourceName, nic)
+ operations.append(op)
+ break
+ except msrestazure.azure_exceptions.CloudError as cex:
+ logging.error("{set_network_state} CloudError in attempt %s '%s'" % (attempt, cex))
+ if cex.error and cex.error.error and cex.error.error.lower() == "PrivateIPAddressIsBeingCleanedUp":
+ logging.error("{set_network_state} PrivateIPAddressIsBeingCleanedUp")
+ time.sleep(RETRY_WAIT)
+
+ except Exception as ex:
+ logging.error("{set_network_state} Exception of type %s: %s" % (type(ex).__name__, ex))
+ break
+
+def get_azure_config(options):
+ config = AzureConfiguration()
+
+ config.RGName = options.get("--resourceGroup")
+ config.VMName = options.get("--plug")
+ config.SubscriptionId = options.get("--subscriptionId")
+ config.Cloud = options.get("--cloud")
+ config.UseMSI = "--msi" in options
+ config.Tenantid = options.get("--tenantId")
+ config.ApplicationId = options.get("--username")
+ config.ApplicationKey = options.get("--password")
+ config.Verbose = options.get("--verbose")
+
+ if not config.RGName:
+ logging.info("resourceGroup not provided. Using metadata service")
+ config.RGName = get_from_metadata("resourceGroupName")
+
+ if not config.SubscriptionId:
+ logging.info("subscriptionId not provided. Using metadata service")
+ config.SubscriptionId = get_from_metadata("subscriptionId")
+
+ return config
+
+def get_azure_cloud_environment(config):
+ cloud_environment = None
+ if config.Cloud:
+ if (config.Cloud.lower() == "china"):
+ from msrestazure.azure_cloud import AZURE_CHINA_CLOUD
+ cloud_environment = AZURE_CHINA_CLOUD
+ elif (config.Cloud.lower() == "germany"):
+ from msrestazure.azure_cloud import AZURE_GERMAN_CLOUD
+ cloud_environment = AZURE_GERMAN_CLOUD
+ elif (config.Cloud.lower() == "usgov"):
+ from msrestazure.azure_cloud import AZURE_US_GOV_CLOUD
+ cloud_environment = AZURE_US_GOV_CLOUD
+
+ return cloud_environment
+
+def get_azure_credentials(config):
+ credentials = None
+ cloud_environment = get_azure_cloud_environment(config)
+ if config.UseMSI and cloud_environment:
+ try:
+ from azure.identity import ManagedIdentityCredential
+ credentials = ManagedIdentityCredential(cloud_environment=cloud_environment)
+ except ImportError:
+ from msrestazure.azure_active_directory import MSIAuthentication
+ credentials = MSIAuthentication(cloud_environment=cloud_environment)
+ elif config.UseMSI:
+ try:
+ from azure.identity import ManagedIdentityCredential
+ credentials = ManagedIdentityCredential()
+ except ImportError:
+ from msrestazure.azure_active_directory import MSIAuthentication
+ credentials = MSIAuthentication()
+ elif cloud_environment:
+ try:
+ # try to use new libraries ClientSecretCredential (azure.identity, based on azure.core)
+ from azure.identity import ClientSecretCredential
+ credentials = ClientSecretCredential(
+ client_id = config.ApplicationId,
+ client_secret = config.ApplicationKey,
+ tenant_id = config.Tenantid,
+ cloud_environment=cloud_environment
+ )
+ except ImportError:
+ # use old libraries ServicePrincipalCredentials (azure.common) if new one is not available
+ from azure.common.credentials import ServicePrincipalCredentials
+ credentials = ServicePrincipalCredentials(
+ client_id = config.ApplicationId,
+ secret = config.ApplicationKey,
+ tenant = config.Tenantid,
+ cloud_environment=cloud_environment
+ )
+ else:
+ try:
+ # try to use new libraries ClientSecretCredential (azure.identity, based on azure.core)
+ from azure.identity import ClientSecretCredential
+ credentials = ClientSecretCredential(
+ client_id = config.ApplicationId,
+ client_secret = config.ApplicationKey,
+ tenant_id = config.Tenantid
+ )
+ except ImportError:
+ # use old libraries ServicePrincipalCredentials (azure.common) if new one is not available
+ from azure.common.credentials import ServicePrincipalCredentials
+ credentials = ServicePrincipalCredentials(
+ client_id = config.ApplicationId,
+ secret = config.ApplicationKey,
+ tenant = config.Tenantid
+ )
+
+ return credentials
+
+def get_azure_compute_client(config):
+ from azure.mgmt.compute import ComputeManagementClient
+
+ cloud_environment = get_azure_cloud_environment(config)
+ credentials = get_azure_credentials(config)
+
+ if cloud_environment:
+ try:
+ compute_client = ComputeManagementClient(
+ credentials,
+ config.SubscriptionId,
+ base_url=cloud_environment.endpoints.resource_manager,
+ credential_scopes=[cloud_environment.endpoints.resource_manager + "/.default"]
+ )
+ except TypeError:
+ compute_client = ComputeManagementClient(
+ credentials,
+ config.SubscriptionId,
+ base_url=cloud_environment.endpoints.resource_manager
+ )
+ else:
+ compute_client = ComputeManagementClient(
+ credentials,
+ config.SubscriptionId
+ )
+ return compute_client
+
+def get_azure_network_client(config):
+ from azure.mgmt.network import NetworkManagementClient
+
+ cloud_environment = get_azure_cloud_environment(config)
+ credentials = get_azure_credentials(config)
+
+ if cloud_environment:
+ try:
+ network_client = NetworkManagementClient(
+ credentials,
+ config.SubscriptionId,
+ base_url=cloud_environment.endpoints.resource_manager,
+ credential_scopes=[cloud_environment.endpoints.resource_manager + "/.default"]
+ )
+ except TypeError:
+ network_client = NetworkManagementClient(
+ credentials,
+ config.SubscriptionId,
+ base_url=cloud_environment.endpoints.resource_manager
+ )
+ else:
+ network_client = NetworkManagementClient(
+ credentials,
+ config.SubscriptionId
+ )
+ return network_client
diff --git a/lib/check_used_options.py b/lib/check_used_options.py
new file mode 100644
index 0000000..ad4a387
--- /dev/null
+++ b/lib/check_used_options.py
@@ -0,0 +1,68 @@
+## Check if fence agent uses only options["--??"] which are defined in fencing library or
+## fence agent itself
+##
+## Usage: ./check_used_options.py fence-agent (e.g. lpar/fence_lpar.py)
+##
+
+import sys, re
+sys.path.append("@FENCEAGENTSLIBDIR@")
+from fencing import all_opt
+
+def main():
+ agent = sys.argv[1]
+
+ available = {}
+
+ ## all_opt from fencing library are imported
+ for k in list(all_opt.keys()):
+ if "longopt" in all_opt[k]:
+ available["--" + all_opt[k]["longopt"]] = True
+
+ ## add UUID which is derived automatically from --plug if possible
+ available["--uuid"] = True
+
+ ## all_opt defined in fence agent are found
+ agent_file = open(agent)
+ opt_re = re.compile(r"\s*all_opt\[\"([^\"]*)\"\] = {")
+ opt_longopt_re = re.compile(r"\"longopt\"\s*:\s*\"([^\"]*)\"")
+
+ in_opt = False
+ for line in agent_file:
+ if opt_re.search(line) != None:
+ in_opt = True
+ if in_opt and opt_longopt_re.search(line) != None:
+ available["--" + opt_longopt_re.search(line).group(1)] = True
+ in_opt = False
+
+ ## check if all options are defined
+ agent_file = open(agent)
+ option_use_re = re.compile(r"options\[\"(-[^\"]*)\"\]")
+ option_in_re = re.compile(r"\"(-[^\"]*)\" in options")
+ option_has_re = re.compile(r"options.has_key\(\"(-[^\"]*)\"\)")
+
+ counter = 0
+ without_errors = True
+ for line in agent_file:
+ counter += 1
+
+ for option in option_use_re.findall(line):
+ if option not in available:
+ print("ERROR on line %d in %s: option %s is not defined" % (counter, agent, option_use_re.search(line).group(1)))
+ without_errors = False
+
+ for option in option_in_re.findall(line):
+ if option not in available:
+ print("ERROR on line %d in %s: option %s is not defined" % (counter, agent, option_has_re.search(line).group(1)))
+ without_errors = False
+
+ for option in option_has_re.findall(line):
+ print("ERROR on line %d in %s: option %s: has_key() not supported in Python 3" % (counter, agent, option_has_re.search(line).group(1)))
+ without_errors = False
+
+ if without_errors:
+ sys.exit(0)
+ else:
+ sys.exit(1)
+
+if __name__ == "__main__":
+ main()
diff --git a/lib/fence.rng.head b/lib/fence.rng.head
new file mode 100644
index 0000000..22f4452
--- /dev/null
+++ b/lib/fence.rng.head
@@ -0,0 +1,7 @@
+<!-- Autogenerated fence definitions -->
+ <define name="FENCEDEVICEOPTIONS">
+ <optional>
+ <choice>
+ <!-- begin specific fence devices -->
+
+ <!-- begin auto-generated device definitions -->
diff --git a/lib/fence.rng.tail b/lib/fence.rng.tail
new file mode 100644
index 0000000..2feab91
--- /dev/null
+++ b/lib/fence.rng.tail
@@ -0,0 +1,13 @@
+ <!-- end auto-generated device definitions -->
+
+ <group>
+ <optional>
+ <empty/>
+ </optional>
+ </group>
+
+ <!-- end specific fence devices -->
+ </choice>
+ </optional>
+ </define>
+<!-- end fence attribute group definitions -->
diff --git a/lib/fence2man.xsl b/lib/fence2man.xsl
new file mode 100644
index 0000000..3dc0b39
--- /dev/null
+++ b/lib/fence2man.xsl
@@ -0,0 +1,78 @@
+<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+<xsl:output method="text" indent="no"/>
+<xsl:template match="parameter">
+<xsl:param name="show" />
+<xsl:if test="not(@deprecated)">
+.TP
+<xsl:if test="$show = 'getopt'">.B <xsl:value-of select="getopt/@mixed" /></xsl:if>
+<xsl:if test="$show = 'stdin'">.B <xsl:value-of select="@name"/></xsl:if>
+.
+<xsl:value-of select="normalize-space(shortdesc)"/>
+<xsl:if test="count(content/option) > 1">
+ <xsl:text> (</xsl:text>
+ <xsl:for-each select="content/option">
+ <xsl:value-of select="@value" />
+ <xsl:if test="position() &lt; last()">
+ <xsl:text>|</xsl:text>
+ </xsl:if>
+ </xsl:for-each>
+ <xsl:text>)</xsl:text>
+</xsl:if>
+<xsl:if test="not(content/@default)"><xsl:if test="@required = 1"> This parameter is always required.</xsl:if></xsl:if>
+<xsl:if test="content/@default"> (Default Value: <xsl:value-of select="content/@default"/>)</xsl:if>
+<xsl:if test="$show = 'stdin'">
+<xsl:if test="@obsoletes"> Obsoletes: <xsl:value-of select="@obsoletes" /></xsl:if>
+</xsl:if>
+
+</xsl:if>
+</xsl:template>
+
+<xsl:template match="action">
+.TP
+\fB<xsl:value-of select="@name"/> \fP
+<xsl:choose>
+<xsl:when test="@name = 'on'">Power on machine.</xsl:when>
+<xsl:when test="@name = 'off'">Power off machine.</xsl:when>
+<xsl:when test="@name = 'enable'">Enable fabric access.</xsl:when>
+<xsl:when test="@name = 'disable'">Disable fabric access.</xsl:when>
+<xsl:when test="@name = 'reboot'">Reboot machine.</xsl:when>
+<xsl:when test="@name = 'diag'">Pulse a diagnostic interrupt to the processor(s).</xsl:when>
+<xsl:when test="@name = 'monitor'">Check the health of fence device</xsl:when>
+<xsl:when test="@name = 'metadata'">Display the XML metadata describing this resource.</xsl:when>
+<xsl:when test="@name = 'list'">List available plugs with aliases/virtual machines if there is support for more then one device. Returns N/A otherwise.</xsl:when>
+<xsl:when test="@name = 'list-status'">List available plugs with aliases/virtual machines and their power state if it can be obtained without additional commands.</xsl:when>
+<xsl:when test="@name = 'status'">This returns the status of the plug/virtual machine.</xsl:when>
+<xsl:when test="@name = 'validate-all'">Validate if all required parameters are entered.</xsl:when>
+<!-- Ehhh -->
+<xsl:otherwise> The operational behavior of this is not known.</xsl:otherwise>
+</xsl:choose>
+</xsl:template>
+
+<xsl:template match="/resource-agent">
+.TH FENCE_AGENT 8 2009-10-20 "<xsl:value-of select="@name"/> (Fence Agent)"
+.SH NAME
+<xsl:value-of select="@name" /> - <xsl:value-of select="@shortdesc" />
+<xsl:for-each select="symlink">
+.P
+<xsl:value-of select="@name" /> - <xsl:value-of select="@shortdesc" /> (symlink)
+</xsl:for-each>
+.SH DESCRIPTION
+.P
+<xsl:value-of select="longdesc"/>
+.P
+<xsl:value-of select="@name" /> accepts options on the command line as well
+as from stdin. Fenced sends parameters through stdin when it execs the
+agent. <xsl:value-of select="@name" /> can be run by itself with command
+line options. This is useful for testing and for turning outlets on or off
+from scripts.
+<xsl:if test="vendor-url">
+Vendor URL: <xsl:value-of select="vendor-url" />
+</xsl:if>
+.SH PARAMETERS
+<xsl:apply-templates select="parameters"><xsl:with-param name="show">getopt</xsl:with-param></xsl:apply-templates>
+.SH ACTIONS
+<xsl:apply-templates select="actions"/>
+.SH STDIN PARAMETERS
+<xsl:apply-templates select="parameters"><xsl:with-param name="show">stdin</xsl:with-param></xsl:apply-templates>
+</xsl:template>
+</xsl:stylesheet>
diff --git a/lib/fence2rng.xsl b/lib/fence2rng.xsl
new file mode 100644
index 0000000..f6d465e
--- /dev/null
+++ b/lib/fence2rng.xsl
@@ -0,0 +1,184 @@
+<xsl:stylesheet version="1.0"
+ xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+<xsl:output method="text" indent="no"/>
+
+<xsl:param name="init-indent" select="' '"/>
+<xsl:param name="indent" select="' '"/>
+
+
+<!--
+ helpers
+ -->
+
+<xsl:variable name="SP" select="' '"/>
+<xsl:variable name="NL" select="'&#xA;'"/>
+<xsl:variable name="Q" select="'&quot;'"/>
+<xsl:variable name="TS" select="'&lt;'"/>
+<xsl:variable name="TSc" select="'&lt;/'"/>
+<xsl:variable name="TE" select="'&gt;'"/>
+<xsl:variable name="TEc" select="'/&gt;'"/>
+
+<xsl:template name="comment">
+ <xsl:param name="text" select="''"/>
+ <xsl:param name="indent" select="''"/>
+ <xsl:if test="$indent != 'none'">
+ <xsl:value-of select="concat($init-indent, $indent)"/>
+ </xsl:if>
+ <xsl:value-of select="concat($TS, '!-- ', $text, ' --',$TE)"/>
+</xsl:template>
+
+<xsl:template name="tag-start">
+ <xsl:param name="name"/>
+ <xsl:param name="attrs" select="''"/>
+ <xsl:param name="indent" select="''"/>
+ <xsl:if test="$indent != 'none'">
+ <xsl:value-of select="concat($init-indent, $indent)"/>
+ </xsl:if>
+ <xsl:value-of select="concat($TS, $name)"/>
+ <xsl:if test="$attrs != ''">
+ <xsl:value-of select="concat($SP, $attrs)"/>
+ </xsl:if>
+ <xsl:value-of select="$TE"/>
+</xsl:template>
+
+<xsl:template name="tag-end">
+ <xsl:param name="name"/>
+ <xsl:param name="attrs" select="''"/>
+ <xsl:param name="indent" select="''"/>
+ <xsl:if test="$indent != 'none'">
+ <xsl:value-of select="concat($init-indent, $indent)"/>
+ </xsl:if>
+ <xsl:value-of select="concat($TSc, $name)"/>
+ <xsl:if test="$attrs != ''">
+ <xsl:value-of select="concat($SP, $attrs)"/>
+ </xsl:if>
+ <xsl:value-of select="$TE"/>
+</xsl:template>
+
+<xsl:template name="tag-self">
+ <xsl:param name="name"/>
+ <xsl:param name="attrs" select="''"/>
+ <xsl:param name="indent" select="''"/>
+ <xsl:if test="$indent != 'none'">
+ <xsl:value-of select="concat($init-indent, $indent)"/>
+ </xsl:if>
+ <xsl:value-of select="concat($TS, $name)"/>
+ <xsl:if test="$attrs != ''">
+ <xsl:value-of select="concat($SP, $attrs)"/>
+ </xsl:if>
+ <xsl:value-of select="$TEc"/>
+</xsl:template>
+
+
+<!--
+ proceed
+ -->
+
+<xsl:template match="/resource-agent">
+ <xsl:value-of select="$NL"/>
+
+ <!-- (comment denoting the fence agent name) -->
+ <xsl:call-template name="comment">
+ <xsl:with-param name="text" select="@name"/>
+ </xsl:call-template>
+ <xsl:value-of select="$NL"/>
+
+ <!-- group rha:name=... rha:description=... (start) -->
+ <xsl:call-template name="tag-start">
+ <xsl:with-param name="name" select="'group'"/>
+ <xsl:with-param name="attrs" select="concat(
+ 'rha:name=', $Q, @name, $Q, $SP,
+ 'rha:description=', $Q, @shortdesc, $Q)"/>
+ </xsl:call-template>
+ <xsl:value-of select="$NL"/>
+
+ <!-- optional (start) -->
+ <xsl:call-template name="tag-start">
+ <xsl:with-param name="name" select="'optional'"/>
+ <xsl:with-param name="indent" select="$indent"/>
+ </xsl:call-template>
+ <xsl:value-of select="$NL"/>
+
+ <!-- attribute name="option" -->
+ <xsl:call-template name="tag-self">
+ <xsl:with-param name="name" select="'attribute'"/>
+ <xsl:with-param name="attrs" select="concat(
+ 'name=', $Q, 'option', $Q)"/>
+ <xsl:with-param name="indent" select="concat($indent, $indent)"/>
+ </xsl:call-template>
+ <xsl:value-of select="$SP"/>
+ <!-- (comment mentioning that "option" is deprecated) -->
+ <xsl:call-template name="comment">
+ <xsl:with-param name="text">
+ <xsl:text>deprecated; for compatibility. use "action"</xsl:text>
+ </xsl:with-param>
+ <xsl:with-param name="indent" select="'none'"/>
+ </xsl:call-template>
+ <xsl:value-of select="$NL"/>
+
+ <!-- optional (end) -->
+ <xsl:call-template name="tag-end">
+ <xsl:with-param name="name" select="'optional'"/>
+ <xsl:with-param name="indent" select="$indent"/>
+ </xsl:call-template>
+ <xsl:value-of select="$NL"/>
+
+ <xsl:for-each select="parameters/parameter">
+ <xsl:variable name="escapeddesc">
+ <xsl:call-template name="escape_quot">
+ <xsl:with-param name="replace" select="shortdesc"/>
+ </xsl:call-template>
+ </xsl:variable>
+
+ <!-- optional (start) -->
+ <xsl:call-template name="tag-start">
+ <xsl:with-param name="name" select="'optional'"/>
+ <xsl:with-param name="indent" select="$indent"/>
+ </xsl:call-template>
+ <xsl:value-of select="$NL"/>
+
+ <!-- attribute name=... rha:description=... -->
+ <xsl:call-template name="tag-self">
+ <xsl:with-param name="name" select="'attribute'"/>
+ <xsl:with-param name="attrs" select="concat(
+ 'name=', $Q, @name, $Q, $SP,
+ 'rha:description=', $Q, normalize-space($escapeddesc), $Q, $SP)"/>
+ <xsl:with-param name="indent" select="concat($indent, $indent)"/>
+ </xsl:call-template>
+ <xsl:value-of select="$NL"/>
+
+ <!-- optional (end) -->
+ <xsl:call-template name="tag-end">
+ <xsl:with-param name="name" select="'optional'"/>
+ <xsl:with-param name="indent" select="$indent"/>
+ </xsl:call-template>
+ <xsl:value-of select="$NL"/>
+ </xsl:for-each>
+
+ <!-- group rha:name=... rha:description=... (end) -->
+ <xsl:call-template name="tag-end">
+ <xsl:with-param name="name" select="'group'"/>
+ </xsl:call-template>
+ <xsl:value-of select="$NL"/>
+
+ <xsl:value-of select="$NL"/>
+</xsl:template>
+
+<xsl:template name="escape_quot">
+ <xsl:param name="replace"/>
+ <xsl:choose>
+ <xsl:when test="contains($replace,'&quot;')">
+ <xsl:value-of select="substring-before($replace,'&quot;')"/>
+ <!-- escape quot-->
+ <xsl:text>&amp;quot;</xsl:text>
+ <xsl:call-template name="escape_quot">
+ <xsl:with-param name="replace" select="substring-after($replace,'&quot;')"/>
+ </xsl:call-template>
+ </xsl:when>
+ <xsl:otherwise>
+ <xsl:value-of select="$replace"/>
+ </xsl:otherwise>
+ </xsl:choose>
+ </xsl:template>
+
+</xsl:stylesheet>
diff --git a/lib/fence2wiki.xsl b/lib/fence2wiki.xsl
new file mode 100644
index 0000000..8112169
--- /dev/null
+++ b/lib/fence2wiki.xsl
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet version='1.0' xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+<xsl:template match="/resource-agent">
+[=#<xsl:value-of select="@name" />]
+||='''<xsl:value-of select="@shortdesc" />''' =||='''<xsl:value-of select="@name" />''' =||
+|| '''Name Of The Argument For STDIN''' || '''Name Of The Argument For Command-Line''' || '''Default Value''' ||'''Description''' ||
+<xsl:apply-templates select="parameters/parameter" />
+</xsl:template>
+
+<xsl:template match="parameters/parameter">|| <xsl:value-of select="@name" /> || <xsl:value-of select="getopt/@mixed" /> || {{{<xsl:value-of select="content/@default" disable-output-escaping="yes"/>}}} || <xsl:value-of select="shortdesc" /> ||
+</xsl:template>
+
+</xsl:stylesheet> \ No newline at end of file
diff --git a/lib/fencing.py.py b/lib/fencing.py.py
new file mode 100644
index 0000000..c5b5e94
--- /dev/null
+++ b/lib/fencing.py.py
@@ -0,0 +1,1748 @@
+#!@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},
+ "plug_separator" : {
+ "getopt" : ":",
+ "longopt" : "plug-separator",
+ "help" : "--plug-separator=[char] Separator for plug parameter when specifying more than 1 plug",
+ "default" : ",",
+ "required" : "0",
+ "order" : 100},
+ "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",
+ "plug_separator", "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 <plug> 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("<?xml version=\"1.0\" ?>")
+ print("<resource-agent name=\"" + os.path.basename(sys.argv[0]) + \
+ "\" shortdesc=\"" + docs["shortdesc"] + "\" >")
+ for (symlink, desc) in docs.get("symlink", []):
+ print("<symlink name=\"" + symlink + "\" shortdesc=\"" + desc + "\"/>")
+ print("<longdesc>" + docs["longdesc"] + "</longdesc>")
+ print("<vendor-url>" + docs["vendorurl"] + "</vendor-url>")
+ print("<parameters>")
+ 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<parameter name=\"" + key + "\" unique=\"0\" required=\"" + opt["required"] + "\"" + info + ">")
+
+ 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<getopt mixed=\"" + mixed + "\" />")
+ if "choices" in opt:
+ print("\t\t<content type=\"select\" "+default+" >")
+ for choice in opt["choices"]:
+ print("\t\t\t<option value=\"%s\" />" % (choice))
+ print("\t\t</content>")
+ elif opt["getopt"].count(":") > 0:
+ t = opt.get("type", "string")
+ print("\t\t<content type=\"%s\" " % (t) +default+" />")
+ else:
+ print("\t\t<content type=\"boolean\" "+default+" />")
+ print("\t\t<shortdesc lang=\"en\">" + shortdesc + "</shortdesc>")
+ print("\t</parameter>")
+ print("</parameters>")
+ print("<actions>")
+
+ (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<action name=\"on\"%s automatic=\"%d\"/>" % (on_target, avail_opt.count("fabric_fencing")))
+
+ for action in available_actions:
+ print("\t<action name=\"%s\" />" % (action))
+ print("</actions>")
+ print("</resource-agent>")
+
+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(options["--plug-separator"])
+
+ ## 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_commands(options, commands, timeout=None, env=None, log_command=None):
+ # inspired by psutils.wait_procs (BSD License)
+ def check_gone(proc, timeout):
+ try:
+ returncode = proc.wait(timeout=timeout)
+ except subprocess.TimeoutExpired:
+ pass
+ else:
+ if returncode is not None or not proc.is_running():
+ proc.returncode = returncode
+ gone.add(proc)
+
+ if timeout is None and "--power-timeout" in options:
+ timeout = options["--power-timeout"]
+ if timeout == 0:
+ timeout = None
+ if timeout is not None:
+ timeout = float(timeout)
+
+ time_start = time.time()
+ procs = []
+ status = None
+ pipe_stdout = ""
+ pipe_stderr = ""
+
+ for command in commands:
+ 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)
+
+ procs.append(process)
+
+ gone = set()
+ alive = set(procs)
+
+ while True:
+ if alive:
+ max_timeout = 2.0 / len(alive)
+ for proc in alive:
+ if timeout is not None:
+ if time.time()-time_start >= timeout:
+ # quickly go over the rest
+ max_timeout = 0
+ check_gone(proc, max_timeout)
+ alive = alive - gone
+
+ if not alive:
+ break
+
+ if time.time()-time_start < 5.0:
+ # give it at least 5s to get a complete answer
+ # afterwards we're OK with a quorate answer
+ continue
+
+ if len(gone) > len(alive):
+ good_cnt = 0
+ for proc in gone:
+ if proc.returncode == 0:
+ good_cnt += 1
+ # a positive result from more than half is fine
+ if good_cnt > len(procs)/2:
+ break
+
+ if timeout is not None:
+ if time.time() - time_start >= timeout:
+ logging.debug("Stop waiting after %s\n", str(timeout))
+ break
+
+ logging.debug("Done: %d gone, %d alive\n", len(gone), len(alive))
+
+ for proc in gone:
+ if (status != 0):
+ status = proc.returncode
+ # hand over the best status we have
+ # but still collect as much stdout/stderr feedback
+ # avoid communicate as we know already process
+ # is gone and it seems to block when there
+ # are D state children we don't get rid off
+ os.set_blocking(proc.stdout.fileno(), False)
+ os.set_blocking(proc.stderr.fileno(), False)
+ try:
+ pipe_stdout += proc.stdout.read()
+ except:
+ pass
+ try:
+ pipe_stderr += proc.stderr.read()
+ except:
+ pass
+ proc.stdout.close()
+ proc.stderr.close()
+
+ for proc in alive:
+ proc.kill()
+
+ if status is None:
+ fail(EC_TIMED_OUT, stop=(int(options.get("retry", 0)) < 1))
+ status = EC_TIMED_OUT
+ pipe_stdout = ""
+ pipe_stderr = "timed out"
+
+ logging.debug("%s %s %s\n", str(status), str(pipe_stdout), str(pipe_stderr))
+
+ return (status, pipe_stdout, pipe_stderr)
+
+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') if not re.match("^\s*$", line))
+
+# 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("&", "&amp;").replace('"', "&quot;").replace('<', "&lt;"). \
+ replace('>', "&gt;").replace("'", "&apos;")
+
+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/lib/fencing_snmp.py.py b/lib/fencing_snmp.py.py
new file mode 100644
index 0000000..f9e5768
--- /dev/null
+++ b/lib/fencing_snmp.py.py
@@ -0,0 +1,128 @@
+#!@PYTHON@ -tt
+
+# For example of use please see fence_cisco_mds
+
+import re, pexpect
+import logging
+from fencing import *
+from fencing import fail, fail_usage, EC_TIMED_OUT, run_delay, frun
+
+__all__ = ['FencingSnmp']
+
+## do not add code here.
+class FencingSnmp:
+ def __init__(self, options):
+ self.options = options
+ run_delay(options)
+
+ def quote_for_run(self, string):
+ return string.replace(r"'", "'\\''")
+
+ def complete_missed_params(self):
+ mapping = [[
+ ['snmp-priv-passwd', 'password', '!snmp-sec-level'],
+ 'self.options["--snmp-sec-level"]="authPriv"'
+ ], [
+ ['!snmp-version', 'community', '!username', '!snmp-priv-passwd', '!password'],
+ 'self.options["--snmp-version"]="2c"'
+ ]]
+
+ for val in mapping:
+ e = val[0]
+
+ res = True
+
+ for item in e:
+ if item[0] == '!' and "--" + item[1:] in self.options:
+ res = False
+ break
+
+ if item[0] != '!' and "--" + item[0:] not in self.options:
+ res = False
+ break
+
+ if res:
+ exec(val[1])
+
+ def prepare_cmd(self, command):
+ cmd = "%s -m '' -Oeqn "% (command)
+
+ self.complete_missed_params()
+
+ #mapping from our option to snmpcmd option
+ mapping = (('snmp-version', 'v'), ('community', 'c'))
+
+ for item in mapping:
+ if "--" + item[0] in self.options:
+ cmd += " -%s '%s'"% (item[1], self.quote_for_run(self.options["--" + item[0]]))
+
+ # Some options make sense only for v3 (and for v1/2c can cause "problems")
+ if ("--snmp-version" in self.options) and (self.options["--snmp-version"] == "3"):
+ # Mapping from our options to snmpcmd options for v3
+ mapping_v3 = (('snmp-auth-prot', 'a'), ('snmp-sec-level', 'l'), ('snmp-priv-prot', 'x'), \
+ ('snmp-priv-passwd', 'X'), ('password', 'A'), ('username', 'u'))
+ for item in mapping_v3:
+ if "--"+item[0] in self.options:
+ cmd += " -%s '%s'"% (item[1], self.quote_for_run(self.options["--" + item[0]]))
+
+ force_ipvx = ""
+
+ if "--inet6-only" in self.options:
+ force_ipvx = "udp6:"
+
+ if "--inet4-only" in self.options:
+ force_ipvx = "udp:"
+
+ cmd += " '%s%s%s'"% (force_ipvx, self.quote_for_run(self.options["--ip"]),
+ "--ipport" in self.options and self.quote_for_run(":" + str(self.options["--ipport"])) or "")
+ return cmd
+
+ def run_command(self, command, additional_timeout=0):
+ try:
+ logging.debug("%s\n", command)
+
+ (res_output, res_code) = frun(command,
+ int(self.options["--shell-timeout"]) +
+ int(self.options["--login-timeout"]) +
+ additional_timeout, True)
+
+ if res_code == None:
+ fail(EC_TIMED_OUT)
+
+ logging.debug("%s\n", res_output)
+
+ if (res_code != 0) or (re.search("^Error ", res_output, re.MULTILINE) != None):
+ fail_usage("Returned %d: %s"% (res_code, res_output))
+ except pexpect.ExceptionPexpect:
+ fail_usage("Cannot run command %s"%(command))
+
+ return res_output
+
+ def get(self, oid, additional_timeout=0):
+ cmd = "%s '%s'"% (self.prepare_cmd(self.options["--snmpget-path"]), self.quote_for_run(oid))
+
+ output = self.run_command(cmd, additional_timeout).splitlines()
+
+ return output[len(output)-1].split(None, 1)
+
+ def set(self, oid, value, additional_timeout=0):
+ mapping = ((int, 'i'), (str, 's'))
+
+ type_of_value = ''
+
+ for item in mapping:
+ if isinstance(value, item[0]):
+ type_of_value = item[1]
+ break
+
+ cmd = "%s '%s' %s '%s'" % (self.prepare_cmd(self.options["--snmpset-path"]),
+ self.quote_for_run(oid), type_of_value, self.quote_for_run(str(value)))
+
+ self.run_command(cmd, additional_timeout)
+
+ def walk(self, oid, additional_timeout=0):
+ cmd = "%s '%s'"% (self.prepare_cmd(self.options["--snmpwalk-path"]), self.quote_for_run(oid))
+
+ output = self.run_command(cmd, additional_timeout).splitlines()
+
+ return [x.split(None, 1) for x in output if x.startswith(".")]
diff --git a/lib/metadata.rng b/lib/metadata.rng
new file mode 100644
index 0000000..e0cd441
--- /dev/null
+++ b/lib/metadata.rng
@@ -0,0 +1,80 @@
+<grammar xmlns="http://relaxng.org/ns/structure/1.0">
+
+<start><element name="resource-agent">
+ <attribute name="name" />
+ <attribute name="shortdesc" />
+
+ <zeroOrMore>
+ <element name="symlink">
+ <attribute name="name" />
+ <attribute name="shortdesc" />
+ </element>
+ </zeroOrMore>
+
+ <element name="longdesc"> <text /> </element>
+ <element name="vendor-url"> <text /> </element>
+
+ <element name="parameters"> <oneOrMore>
+ <element name="parameter">
+ <attribute name="name" />
+ <attribute name="unique"> <ref name="boolean-values" /> </attribute>
+ <attribute name="required"> <ref name="boolean-values" /> </attribute>
+ <optional><attribute name="deprecated"> <ref name="boolean-values" /></attribute></optional>
+ <optional><attribute name="obsoletes" /> </optional>
+ <element name="getopt">
+ <attribute name="mixed" />
+ </element>
+ <element name="content">
+ <choice>
+ <attribute name="type">
+ <choice>
+ <value>boolean</value>
+ <value>string</value>
+ <value>second</value>
+ <value>integer</value>
+ </choice>
+ </attribute>
+ <group>
+ <attribute name="type">
+ <value>select</value>
+ </attribute>
+ <zeroOrMore>
+ <element name="option">
+ <attribute name="value" />
+ </element>
+ </zeroOrMore>
+ </group>
+ </choice>
+ <optional>
+ <attribute name="default"> <text /> </attribute>
+ </optional>
+ </element>
+
+ <oneOrMore> <element name="shortdesc">
+ <attribute name="lang" />
+ <text />
+ </element> </oneOrMore>
+ </element>
+ </oneOrMore> </element>
+
+ <element name="actions"> <oneOrMore>
+ <element name="action">
+ <attribute name="name" />
+ <optional>
+ <attribute name="on_target"> <ref name="boolean-values" /> </attribute>
+ </optional>
+ <optional>
+ <attribute name="automatic"> <ref name="boolean-values" /> </attribute>
+ </optional>
+ </element>
+ </oneOrMore> </element>
+</element></start>
+
+<define name="boolean-values">
+ <choice>
+ <value>0</value>
+ <value>1</value>
+ </choice>
+</define>
+
+</grammar>
diff --git a/lib/tests/test_fencing.py b/lib/tests/test_fencing.py
new file mode 100644
index 0000000..6ee9385
--- /dev/null
+++ b/lib/tests/test_fencing.py
@@ -0,0 +1,123 @@
+#!/usr/bin/python
+
+import unittest
+import sys
+sys.path.append("..")
+import fencing
+import copy
+
+class Test_join2(unittest.TestCase):
+ def test_single(self):
+ words = ["Mike"]
+ self.assertEqual(fencing._join2(words), "Mike")
+ self.assertEqual(fencing._join2(words, last_separator=" xor "), "Mike")
+ self.assertEqual(fencing._join2(words, normal_separator=" xor "), "Mike")
+
+ def test_double(self):
+ words = ["Mike", "John"]
+ self.assertEqual(fencing._join2(words), "Mike and John")
+ self.assertEqual(fencing._join2(words, last_separator=" xor "), "Mike xor John")
+ self.assertEqual(fencing._join2(words, normal_separator=" xor "), "Mike and John")
+
+ def test_triple(self):
+ words = ["Mike", "John", "Adam"]
+ self.assertEqual(fencing._join2(words), "Mike, John and Adam")
+ self.assertEqual(fencing._join2(words, last_separator=" xor "), "Mike, John xor Adam")
+ self.assertEqual(fencing._join2(words, normal_separator=" xor "), "Mike xor John and Adam")
+
+ def test_quadruple(self):
+ words = ["Eve", "Mike", "John", "Adam"]
+ self.assertEqual(fencing._join2(words), "Eve, Mike, John and Adam")
+ self.assertEqual(fencing._join2(words, last_separator=" xor "), "Eve, Mike, John xor Adam")
+ self.assertEqual(fencing._join2(words, normal_separator=" xor "), "Eve xor Mike xor John and Adam")
+
+class Test_add_dependency_options(unittest.TestCase):
+ basic_set = fencing.DEPENDENCY_OPT["default"]
+
+ def test_add_nothing(self):
+ self.assertEqual(set(fencing._add_dependency_options([])), set(self.basic_set))
+ self.assertEqual(set(fencing._add_dependency_options(["not-exist"])), set(self.basic_set))
+
+ def test_add_single(self):
+ self.assertEqual(set(fencing._add_dependency_options(["passwd"])), set(self.basic_set + ["passwd_script"]))
+
+ def test_add_tuple(self):
+ self.assertEqual(set(fencing._add_dependency_options(["ssl", "passwd"])), \
+ set(self.basic_set + ["passwd_script", "ssl_secure", "ssl_insecure", "gnutlscli_path"]))
+
+class Test_set_default_values(unittest.TestCase):
+ original_all_opt = None
+
+ def setUp(self):
+ # all_opt[*]["default"] can be changed during tests
+ self.original_all_opt = copy.deepcopy(fencing.all_opt)
+
+ def tearDown(self):
+ fencing.all_opt = copy.deepcopy(self.original_all_opt)
+
+ def _prepare_options(self, device_opts, args = {}):
+ device_opts = fencing._add_dependency_options(device_opts) + device_opts
+
+ arg_opts = args
+ options = dict(arg_opts)
+ options["device_opt"] = device_opts
+ fencing._update_metadata(options)
+ return fencing._set_default_values(options)
+
+ def test_status_io(self):
+ options = self._prepare_options([])
+
+ self.assertEqual(options["--action"], "reboot")
+ self.assertIsNone(options.get("--not-exist", None))
+
+ def test_status_fabric(self):
+ options = self._prepare_options(["fabric_fencing"])
+ self.assertEqual(options["--action"], "off")
+
+ def test_ipport_nothing(self):
+ # should fail because connection method (telnet/ssh/...) is not set at all
+ self.assertRaises(IndexError, self._prepare_options, ["ipaddr"])
+
+ def test_ipport_set(self):
+ options = self._prepare_options(["ipaddr", "telnet"], {"--ipport" : "999"})
+ self.assertEqual(options["--ipport"], "999")
+
+ def test_ipport_telnet(self):
+ options = self._prepare_options(["ipaddr", "telnet"])
+ self.assertEqual(options["--ipport"], "23")
+
+ def test_ipport_ssh(self):
+ options = self._prepare_options(["ipaddr", "secure"], {"--ssh" : "1"})
+ self.assertEqual(options["--ipport"], "22")
+
+ def test_ipport_sshtelnet_use_telnet(self):
+ options = self._prepare_options(["ipaddr", "secure", "telnet"])
+ self.assertEqual(options["--ipport"], "23")
+
+ def test_ipport_sshtelnet_use_ssh(self):
+ options = self._prepare_options(["ipaddr", "secure", "telnet"], {"--ssh" : "1"})
+ self.assertEqual(options["--ipport"], "22")
+
+ def test_ipport_ssl(self):
+ options = self._prepare_options(["ipaddr", "ssl"], {"--ssl-secure" : "1"})
+ self.assertEqual(options["--ipport"], "443")
+
+ def test_ipport_ssl_insecure_as_default(self):
+ fencing.all_opt["ssl_insecure"]["default"] = "1"
+ options = self._prepare_options(["ipaddr", "ssl"])
+ self.assertEqual(options["--ipport"], "443")
+
+ def test_ipport_snmp(self):
+ options = self._prepare_options(["ipaddr", "community"])
+ self.assertEqual(options["--ipport"], "161")
+
+ def test_ipport_web(self):
+ options = self._prepare_options(["ipaddr", "web", "ssl"])
+ self.assertEqual(options["--ipport"], "80")
+
+ def test_path_telnet(self):
+ options = self._prepare_options(["ipaddr", "telnet"])
+ self.assertTrue("--telnet-path" in options)
+
+if __name__ == '__main__':
+ unittest.main()