Merge ~lucaskanashiro/ubuntu/+source/fence-agents:backport_ibmz_agent_bionic into ubuntu/+source/fence-agents:ubuntu/bionic-devel

Proposed by Lucas Kanashiro
Status: Merged
Merged at revision: 2a5fdcf7272b12ef2c86344390c85f6f9e4e5e76
Proposed branch: ~lucaskanashiro/ubuntu/+source/fence-agents:backport_ibmz_agent_bionic
Merge into: ubuntu/+source/fence-agents:ubuntu/bionic-devel
Diff against target: 677 lines (+615/-10)
5 files modified
debian/changelog (+11/-0)
debian/copyright (+15/-9)
debian/patches/0013-Add-fence-agent-for-IBM-z-LPARs.patch (+587/-0)
debian/patches/series (+1/-0)
debian/rules (+1/-1)
Reviewer Review Type Date Requested Status
Lucas Kanashiro (community) Approve
Christian Ehrhardt  (community) Needs Fixing
Review via email: mp+398276@code.launchpad.net

Description of the change

Backport IBM LPAR fence agent (LP: #1889070). The intent of this MP is reviewing the work done so far before sharing a PPA containing the proposed package with IBM for testing. Due to that, the SRU paperwork is not done yet, when we get an approval from IBM I will do that.

PPA:

https://launchpad.net/~lucaskanashiro/+archive/ubuntu/ha-stack-ibm

autopkgtest result:

autopkgtest [12:09:31]: @@@@@@@@@@@@@@@@@@@@ summary
fence-dummy PASS

To post a comment you must log in.
Revision history for this message
Christian Ehrhardt  (paelzer) wrote :

If we disable the test (which is ok) why do we backport and then carry tests/data/metadata/fence_ibmz.xml ?
Maybe not adding this test xml allows you to even not need to skip it?

If we update doc/COPYRIGHT don't we also need to update d/cpyright?
I'm not good on those licensing things, but it comes to mind seeing to touch one but not the other.

Maybe add on the patch header notes for the backport:
- dropped .spec because ...
- dropped test because ...

The rest looks like a good SRU, while it adds new features that should be ok as it is for HW exploitation. And in addition it just adds one other agent not touching (=regressing) the others.

review: Needs Fixing
Revision history for this message
Lucas Kanashiro (lucaskanashiro) wrote :

Thanks for the review Christian.

I tried to backport the very same commit from upstream without changes but I believe you are right regarding the test, we can drop it and add a note. About the copyright change, in my opinion, we should keep it and as you mentioned advertise it in d/copyright as well. Does that sound good to you?

Revision history for this message
Christian Ehrhardt  (paelzer) wrote :

> I tried to backport the very same commit from upstream without changes but I believe you are right regarding the test, we can drop it and add a note. About the copyright change, in my opinion, we should keep it and as you mentioned advertise it in d/copyright as well. Does that sound good to you?

yes, WFM

Revision history for this message
Lucas Kanashiro (lucaskanashiro) wrote :

The package was tested by IBM and it was uploaded:

$ git push pkg upload/4.0.25-2ubuntu1.3
Enumerating objects: 26, done.
Counting objects: 100% (26/26), done.
Delta compression using up to 32 threads
Compressing objects: 100% (19/19), done.
Writing objects: 100% (19/19), 8.28 KiB | 2.07 MiB/s, done.
Total 19 (delta 13), reused 0 (delta 0)
To ssh://git.launchpad.net/ubuntu/+source/fence-agents
 * [new tag] upload/4.0.25-2ubuntu1.3 -> upload/4.0.25-2ubuntu1.3
 dput ubuntu ../fence-agents_4.0.25-2ubuntu1.3_source.changes
Checking signature on .changes
gpg: ../fence-agents_4.0.25-2ubuntu1.3_source.changes: Valid signature from F823A2729883C97C
Checking signature on .dsc
gpg: ../fence-agents_4.0.25-2ubuntu1.3.dsc: Valid signature from F823A2729883C97C
Uploading to ubuntu (via ftp to upload.ubuntu.com):
  Uploading fence-agents_4.0.25-2ubuntu1.3.dsc: done.
  Uploading fence-agents_4.0.25-2ubuntu1.3.debian.tar.xz: done.
  Uploading fence-agents_4.0.25-2ubuntu1.3_source.changes: done.
Successfully uploaded packages.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/debian/changelog b/debian/changelog
index 7412a79..568d5d5 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,14 @@
1fence-agents (4.0.25-2ubuntu1.3) bionic; urgency=medium
2
3 * Backport upstream patch to add agent for IBM z LPARs (LP: #1889070).
4 - d/p/0013-Add-fence-agent-for-IBM-z-LPARs.patch
5 - d/copyright: add the IBM z LPARs agent.
6 - d/rules: skip fence_ibmz in dh_auto_test. The test metadata file was
7 removed from the upstream patch and it makes "make check" fail for
8 fence_ibmz.
9
10 -- Lucas Kanashiro <kanashiro@ubuntu.com> Wed, 10 Feb 2021 16:39:54 -0300
11
1fence-agents (4.0.25-2ubuntu1.2) bionic; urgency=medium12fence-agents (4.0.25-2ubuntu1.2) bionic; urgency=medium
213
3 * fence_aws backport from Focal (LP: #1894323):14 * fence_aws backport from Focal (LP: #1894323):
diff --git a/debian/copyright b/debian/copyright
index a70b5d8..ba34532 100644
--- a/debian/copyright
+++ b/debian/copyright
@@ -19,6 +19,17 @@ License: GPL-2+
19 On Debian systems, the complete text of the GNU General19 On Debian systems, the complete text of the GNU General
20 Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".20 Public License version 2 can be found in "/usr/share/common-licenses/GPL-2".
21 21
22License: LGPL-2.1+
23 This library is free software; you can redistribute it and/or
24 modify it under the terms of the GNU Lesser General Public
25 License as published by the Free Software Foundation; either
26 version 2.1 of the License, or (at your option) any later version.
27 .
28 This library is distributed in the hope that it will be useful,
29 but WITHOUT ANY WARRANTY; without even the implied warranty of
30 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
31 Lesser General Public License for more details.
32
22Files: debian/*33Files: debian/*
23Copyright: 2011 Andres Rodriguez <andreserl@ubuntu.com>34Copyright: 2011 Andres Rodriguez <andreserl@ubuntu.com>
24License: GPL-2+35License: GPL-2+
@@ -53,16 +64,11 @@ License: GPL-2+
53Files: fence/agents/zvm/fence_zvm.*64Files: fence/agents/zvm/fence_zvm.*
54Copyright: (C) 2012 Sine Nomine Associates65Copyright: (C) 2012 Sine Nomine Associates
55License: LGPL-2.1+66License: LGPL-2.1+
56 This library is free software; you can redistribute it and/or
57 modify it under the terms of the GNU Lesser General Public
58 License as published by the Free Software Foundation; either
59 version 2.1 of the License, or (at your option) any later version.
60 .
61 This library is distributed in the hope that it will be useful,
62 but WITHOUT ANY WARRANTY; without even the implied warranty of
63 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
64 Lesser General Public License for more details.
6567
66Files: fence/man/fence_ifmib.868Files: fence/man/fence_ifmib.8
67Copyright: 2008 Ross Vandegrift69Copyright: 2008 Ross Vandegrift
68License: GPL-2+70License: GPL-2+
71
72Files: fence/agents/ibmz/fence_ibmz.py
73Copyright: 2020 IBM Corp.
74License: LGPL-2.1+
diff --git a/debian/patches/0013-Add-fence-agent-for-IBM-z-LPARs.patch b/debian/patches/0013-Add-fence-agent-for-IBM-z-LPARs.patch
69new file mode 10064475new file mode 100644
index 0000000..113fa1f
--- /dev/null
+++ b/debian/patches/0013-Add-fence-agent-for-IBM-z-LPARs.patch
@@ -0,0 +1,587 @@
1From: Paulo de Rezende Pinatti <ppinatti@linux.ibm.com>
2Date: Thu, 16 Jul 2020 21:37:12 +0200
3Subject: Add fence agent for IBM z LPARs
4
5This agent manages IBM z LPARs via the HMC Web Services
6REST API.
7
8Signed-off-by: Paulo de Rezende Pinatti <ppinatti@linux.ibm.com>
9
10Origin: backport, https://github.com/ClusterLabs/fence-agents/commit/74d415bf
11Bug-Ubuntu: https://bugs.launchpad.net/ubuntu-z-systems/+bug/1889070
12Reviewed-By: Lucas Kanashiro <kanashiro@ubuntu.com>
13
14The addition of test/data/metadata/fence_ibmz.xml was dropped because it does
15not work during build time, it tries to connect to non-localhost. The changes
16made to the fence-agensts.spec.in were also removed since here we do not care
17about RPM packages.
18
19---
20 doc/COPYRIGHT | 4 +
21 fence/agents/ibmz/fence_ibmz.py | 542 +++++++++++++++++++++++++++++++++++++
22 2 files changed, 546 insertions(+)
23 create mode 100644 fence/agents/ibmz/fence_ibmz.py
24
25diff --git a/doc/COPYRIGHT b/doc/COPYRIGHT
26index 8124c53..f1e8353 100644
27--- a/doc/COPYRIGHT
28+++ b/doc/COPYRIGHT
29@@ -5,6 +5,10 @@ Copyright (C) 2004-2011 Red Hat, Inc. All rights reserved.
30
31 Exceptions:
32
33+fence/agents/ibmz/*:
34+ Copyright (c) 2020 IBM Corp.
35+ Contributed by Paulo de Rezende Pinatti <ppinatti at linux.ibm.com>
36+
37 fence/agents/hds_cb/*:
38 Copyright (C) 2012 Matthew Clark.
39 Author: Matthew Clark <mattjclark0407 at hotmail.com>
40diff --git a/fence/agents/ibmz/fence_ibmz.py b/fence/agents/ibmz/fence_ibmz.py
41new file mode 100644
42index 0000000..d3ac550
43--- /dev/null
44+++ b/fence/agents/ibmz/fence_ibmz.py
45@@ -0,0 +1,542 @@
46+#!@PYTHON@ -tt
47+
48+# Copyright (c) 2020 IBM Corp.
49+#
50+# This library is free software; you can redistribute it and/or
51+# modify it under the terms of the GNU Lesser General Public
52+# License as published by the Free Software Foundation; either
53+# version 2.1 of the License, or (at your option) any later version.
54+#
55+# This library is distributed in the hope that it will be useful,
56+# but WITHOUT ANY WARRANTY; without even the implied warranty of
57+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
58+# Lesser General Public License for more details.
59+#
60+# You should have received a copy of the GNU Lesser General Public
61+# License along with this library. If not, see
62+# <http://www.gnu.org/licenses/>.
63+
64+import atexit
65+import logging
66+import time
67+import sys
68+
69+import requests
70+from requests.packages import urllib3
71+
72+sys.path.append("@FENCEAGENTSLIBDIR@")
73+from fencing import *
74+from fencing import fail_usage, run_delay, EC_GENERIC_ERROR
75+
76+DEFAULT_POWER_TIMEOUT = '300'
77+ERROR_NOT_FOUND = ("{obj_type} {obj_name} not found in this HMC. "
78+ "Attention: names are case-sensitive.")
79+
80+class ApiClientError(Exception):
81+ """
82+ Base exception for all API Client related errors.
83+ """
84+
85+class ApiClientRequestError(ApiClientError):
86+ """
87+ Raised when an API request ends in error
88+ """
89+
90+ def __init__(self, req_method, req_uri, status, reason, message):
91+ self.req_method = req_method
92+ self.req_uri = req_uri
93+ self.status = status
94+ self.reason = reason
95+ self.message = message
96+ super(ApiClientRequestError, self).__init__()
97+
98+ def __str__(self):
99+ return (
100+ "API request failed, details:\n"
101+ "HTTP Request : {req_method} {req_uri}\n"
102+ "HTTP Response status: {status}\n"
103+ "Error reason: {reason}\n"
104+ "Error message: {message}\n".format(
105+ req_method=self.req_method, req_uri=self.req_uri,
106+ status=self.status, reason=self.reason, message=self.message)
107+ )
108+
109+class APIClient(object):
110+ DEFAULT_CONFIG = {
111+ # how many connection-related errors to retry on
112+ 'connect_retries': 3,
113+ # how many times to retry on read errors (after request was sent to the
114+ # server)
115+ 'read_retries': 3,
116+ # http methods that should be retried
117+ 'method_whitelist': ['HEAD', 'GET', 'OPTIONS'],
118+ # limit of redirects to perform to avoid loops
119+ 'redirect': 5,
120+ # how long to wait while establishing a connection
121+ 'connect_timeout': 30,
122+ # how long to wait for asynchronous operations (jobs) to complete
123+ 'operation_timeout': 900,
124+ # how long to wait between bytes sent by the remote side
125+ 'read_timeout': 300,
126+ # default API port
127+ 'port': 6794,
128+ # validate ssl certificates
129+ 'ssl_verify': False
130+ }
131+ LABEL_BY_OP_MODE = {
132+ 'classic': {
133+ 'nodes': 'logical-partitions',
134+ 'state-on': 'operating',
135+ 'start': 'load',
136+ 'stop': 'deactivate'
137+ },
138+ 'dpm': {
139+ 'nodes': 'partitions',
140+ 'state-on': 'active',
141+ 'start': 'start',
142+ 'stop': 'stop'
143+ }
144+ }
145+ def __init__(self, host, user, passwd, config=None):
146+ self.host = host
147+ if not passwd:
148+ raise ValueError('Password cannot be empty')
149+ self.passwd = passwd
150+ if not user:
151+ raise ValueError('Username cannot be empty')
152+ self.user = user
153+ self._cpc_cache = {}
154+ self._session = None
155+ self._config = self.DEFAULT_CONFIG.copy()
156+ # apply user defined values
157+ if config:
158+ self._config.update(config)
159+
160+ def _create_session(self):
161+ """
162+ Create a new requests session and apply config values
163+ """
164+ session = requests.Session()
165+ retry_obj = urllib3.Retry(
166+ # setting a total is necessary to cover SSL related errors
167+ total=max(self._config['connect_retries'],
168+ self._config['read_retries']),
169+ connect=self._config['connect_retries'],
170+ read=self._config['read_retries'],
171+ method_whitelist=self._config['method_whitelist'],
172+ redirect=self._config['redirect']
173+ )
174+ session.mount('http://', requests.adapters.HTTPAdapter(
175+ max_retries=retry_obj))
176+ session.mount('https://', requests.adapters.HTTPAdapter(
177+ max_retries=retry_obj))
178+ return session
179+
180+ def _get_mode_labels(self, cpc):
181+ """
182+ Return the map of labels that corresponds to the cpc operation mode
183+ """
184+ if self.is_dpm_enabled(cpc):
185+ return self.LABEL_BY_OP_MODE['dpm']
186+ return self.LABEL_BY_OP_MODE['classic']
187+
188+ def _partition_switch_power(self, cpc, partition, action):
189+ """
190+ Perform the API request to start (power on) or stop (power off) the
191+ target partition and wait for the job to finish.
192+ """
193+ # retrieve partition's uri
194+ label_map = self._get_mode_labels(cpc)
195+ resp = self._request('get', '{}/{}?name={}'.format(
196+ self._cpc_cache[cpc]['object-uri'], label_map['nodes'], partition))
197+
198+ if not resp[label_map['nodes']]:
199+ raise ValueError(ERROR_NOT_FOUND.format(
200+ obj_type='LPAR/Partition', obj_name=partition))
201+
202+ part_uri = resp[label_map['nodes']][0]['object-uri']
203+
204+ # in dpm mode the request must have empty body
205+ if self.is_dpm_enabled(cpc):
206+ body = None
207+ # in classic mode we make sure the operation is executed
208+ # even if the partition is already on
209+ else:
210+ body = {'force': True}
211+ # when powering on the partition must be activated first
212+ if action == 'start':
213+ op_uri = '{}/operations/activate'.format(part_uri)
214+ job_resp = self._request(
215+ 'post', op_uri, body=body, valid_codes=[202])
216+ # always wait for activate otherwise the load (start)
217+ # operation will fail
218+ if self._config['operation_timeout'] == 0:
219+ timeout = self.DEFAULT_CONFIG['operation_timeout']
220+ else:
221+ timeout = self._config['operation_timeout']
222+ logging.debug(
223+ 'waiting for activate (timeout %s secs)', timeout)
224+ self._wait_for_job('post', op_uri, job_resp['job-uri'],
225+ timeout=timeout)
226+
227+ # trigger the start job
228+ op_uri = '{}/operations/{}'.format(part_uri, label_map[action])
229+ job_resp = self._request('post', op_uri, body=body, valid_codes=[202])
230+ if self._config['operation_timeout'] == 0:
231+ return
232+ logging.debug('waiting for %s (timeout %s secs)',
233+ label_map[action], self._config['operation_timeout'])
234+ self._wait_for_job('post', op_uri, job_resp['job-uri'],
235+ timeout=self._config['operation_timeout'])
236+
237+ def _request(self, method, uri, body=None, headers=None, valid_codes=None):
238+ """
239+ Perform a request to the HMC API
240+ """
241+ assert method in ('delete', 'head', 'get', 'post', 'put')
242+
243+ url = 'https://{host}:{port}{uri}'.format(
244+ host=self.host, port=self._config['port'], uri=uri)
245+ if not headers:
246+ headers = {}
247+
248+ if self._session is None:
249+ raise ValueError('You need to log on first')
250+ method = getattr(self._session, method)
251+ timeout = (
252+ self._config['connect_timeout'], self._config['read_timeout'])
253+ response = method(url, json=body, headers=headers,
254+ verify=self._config['ssl_verify'], timeout=timeout)
255+
256+ if valid_codes and response.status_code not in valid_codes:
257+ reason = '(no reason)'
258+ message = '(no message)'
259+ if response.headers.get('content-type') == 'application/json':
260+ try:
261+ json_resp = response.json()
262+ except ValueError:
263+ pass
264+ else:
265+ reason = json_resp.get('reason', reason)
266+ message = json_resp.get('message', message)
267+ else:
268+ message = '{}...'.format(response.text[:500])
269+ raise ApiClientRequestError(
270+ response.request.method, response.request.url,
271+ response.status_code, reason, message)
272+
273+ if response.status_code == 204:
274+ return dict()
275+ try:
276+ json_resp = response.json()
277+ except ValueError:
278+ raise ApiClientRequestError(
279+ response.request.method, response.request.url,
280+ response.status_code, '(no reason)',
281+ 'Invalid JSON content in response')
282+
283+ return json_resp
284+
285+ def _update_cpc_cache(self, cpc_props):
286+ self._cpc_cache[cpc_props['name']] = {
287+ 'object-uri': cpc_props['object-uri'],
288+ 'dpm-enabled': cpc_props.get('dpm-enabled', False)
289+ }
290+
291+ def _wait_for_job(self, req_method, req_uri, job_uri, timeout):
292+ """
293+ Perform API requests to check for job status until it has completed
294+ or the specified timeout is reached
295+ """
296+ op_timeout = time.time() + timeout
297+ while time.time() < op_timeout:
298+ job_resp = self._request("get", job_uri)
299+ if job_resp['status'] == 'complete':
300+ if job_resp['job-status-code'] in (200, 201, 204):
301+ return
302+ raise ApiClientRequestError(
303+ req_method, req_uri,
304+ job_resp.get('job-status-code', '(no status)'),
305+ job_resp.get('job-reason-code', '(no reason)'),
306+ job_resp.get('job-results', {}).get(
307+ 'message', '(no message)')
308+ )
309+ time.sleep(1)
310+ raise ApiClientError('Timed out while waiting for job completion')
311+
312+ def cpc_list(self):
313+ """
314+ Return a list of CPCs in the format {'name': 'cpc-name', 'status':
315+ 'operating'}
316+ """
317+ list_resp = self._request("get", "/api/cpcs", valid_codes=[200])
318+ ret = []
319+ for cpc_props in list_resp['cpcs']:
320+ self._update_cpc_cache(cpc_props)
321+ ret.append({
322+ 'name': cpc_props['name'], 'status': cpc_props['status']})
323+ return ret
324+
325+ def is_dpm_enabled(self, cpc):
326+ """
327+ Return True if CPC is in DPM mode, False for classic mode
328+ """
329+ if cpc in self._cpc_cache:
330+ return self._cpc_cache[cpc]['dpm-enabled']
331+ list_resp = self._request("get", "/api/cpcs?name={}".format(cpc),
332+ valid_codes=[200])
333+ if not list_resp['cpcs']:
334+ raise ValueError(ERROR_NOT_FOUND.format(
335+ obj_type='CPC', obj_name=cpc))
336+ self._update_cpc_cache(list_resp['cpcs'][0])
337+ return self._cpc_cache[cpc]['dpm-enabled']
338+
339+ def logon(self):
340+ """
341+ Open a session with the HMC API and store its ID
342+ """
343+ self._session = self._create_session()
344+ logon_body = {"userid": self.user, "password": self.passwd}
345+ logon_resp = self._request("post", "/api/sessions", body=logon_body,
346+ valid_codes=[200, 201])
347+ self._session.headers["X-API-Session"] = logon_resp['api-session']
348+
349+ def logoff(self):
350+ """
351+ Close/delete the HMC API session
352+ """
353+ if self._session is None:
354+ return
355+ self._request("delete", "/api/sessions/this-session",
356+ valid_codes=[204])
357+ self._cpc_cache = {}
358+ self._session = None
359+
360+ def partition_list(self, cpc):
361+ """
362+ Return a list of partitions in the format {'name': 'part-name',
363+ 'status': 'on'}
364+ """
365+ label_map = self._get_mode_labels(cpc)
366+ list_resp = self._request('get', '{}/{}'.format(
367+ self._cpc_cache[cpc]['object-uri'], label_map['nodes']))
368+ status_map = {label_map['state-on']: 'on'}
369+ return [{'name': part['name'],
370+ 'status': status_map.get(part['status'].lower(), 'off')}
371+ for part in list_resp[label_map['nodes']]]
372+
373+ def partition_start(self, cpc, partition):
374+ """
375+ Power on a partition
376+ """
377+ self._partition_switch_power(cpc, partition, 'start')
378+
379+ def partition_status(self, cpc, partition):
380+ """
381+ Return the current status of a partition (on or off)
382+ """
383+ label_map = self._get_mode_labels(cpc)
384+
385+ resp = self._request('get', '{}/{}?name={}'.format(
386+ self._cpc_cache[cpc]['object-uri'], label_map['nodes'], partition))
387+ if not resp[label_map['nodes']]:
388+ raise ValueError(ERROR_NOT_FOUND.format(
389+ obj_type='LPAR/Partition', obj_name=partition))
390+ part_props = resp[label_map['nodes']][0]
391+ if part_props['status'].lower() == label_map['state-on']:
392+ return 'on'
393+ return 'off'
394+
395+ def partition_stop(self, cpc, partition):
396+ """
397+ Power off a partition
398+ """
399+ self._partition_switch_power(cpc, partition, 'stop')
400+
401+def parse_plug(options):
402+ """
403+ Extract cpc and partition from specified plug value
404+ """
405+ try:
406+ cpc, partition = options['--plug'].strip().split('/', 1)
407+ except ValueError:
408+ fail_usage('Please specify nodename in format cpc/partition')
409+ cpc = cpc.strip()
410+ if not cpc or not partition:
411+ fail_usage('Please specify nodename in format cpc/partition')
412+ return cpc, partition
413+
414+def get_power_status(conn, options):
415+ logging.debug('executing get_power_status')
416+ status = conn.partition_status(*parse_plug(options))
417+ return status
418+
419+def set_power_status(conn, options):
420+ logging.debug('executing set_power_status')
421+ if options['--action'] == 'on':
422+ conn.partition_start(*parse_plug(options))
423+ elif options['--action'] == 'off':
424+ conn.partition_stop(*parse_plug(options))
425+ else:
426+ fail_usage('Invalid action {}'.format(options['--action']))
427+
428+def get_outlet_list(conn, options):
429+ logging.debug('executing get_outlet_list')
430+ result = {}
431+ for cpc in conn.cpc_list():
432+ for part in conn.partition_list(cpc['name']):
433+ result['{}/{}'.format(cpc['name'], part['name'])] = (
434+ part['name'], part['status'])
435+ return result
436+
437+def disconnect(conn):
438+ """
439+ Close the API session
440+ """
441+ try:
442+ conn.logoff()
443+ except Exception as exc:
444+ logging.exception('Logoff failed: ')
445+ sys.exit(str(exc))
446+
447+def set_opts():
448+ """
449+ Define the options supported by this agent
450+ """
451+ device_opt = [
452+ "ipaddr",
453+ "ipport",
454+ "login",
455+ "passwd",
456+ "port",
457+ "connect_retries",
458+ "connect_timeout",
459+ "operation_timeout",
460+ "read_retries",
461+ "read_timeout",
462+ "ssl_secure",
463+ ]
464+
465+ all_opt["ipport"]["default"] = APIClient.DEFAULT_CONFIG['port']
466+ all_opt["power_timeout"]["default"] = DEFAULT_POWER_TIMEOUT
467+ port_desc = ("Physical plug id in the format cpc-name/partition-name "
468+ "(case-sensitive)")
469+ all_opt["port"]["shortdesc"] = port_desc
470+ all_opt["port"]["help"] = (
471+ "-n, --plug=[id] {}".format(port_desc))
472+ all_opt["connect_retries"] = {
473+ "getopt" : ":",
474+ "longopt" : "connect-retries",
475+ "help" : "--connect-retries=[number] How many times to "
476+ "retry on connection errors",
477+ "default" : APIClient.DEFAULT_CONFIG['connect_retries'],
478+ "type" : "integer",
479+ "required" : "0",
480+ "shortdesc" : "How many times to retry on connection errors",
481+ "order" : 2
482+ }
483+ all_opt["read_retries"] = {
484+ "getopt" : ":",
485+ "longopt" : "read-retries",
486+ "help" : "--read-retries=[number] How many times to "
487+ "retry on errors related to reading from server",
488+ "default" : APIClient.DEFAULT_CONFIG['read_retries'],
489+ "type" : "integer",
490+ "required" : "0",
491+ "shortdesc" : "How many times to retry on read errors",
492+ "order" : 2
493+ }
494+ all_opt["connect_timeout"] = {
495+ "getopt" : ":",
496+ "longopt" : "connect-timeout",
497+ "help" : "--connect-timeout=[seconds] How long to wait to "
498+ "establish a connection",
499+ "default" : APIClient.DEFAULT_CONFIG['connect_timeout'],
500+ "type" : "second",
501+ "required" : "0",
502+ "shortdesc" : "How long to wait to establish a connection",
503+ "order" : 2
504+ }
505+ all_opt["operation_timeout"] = {
506+ "getopt" : ":",
507+ "longopt" : "operation-timeout",
508+ "help" : "--operation-timeout=[seconds] How long to wait for "
509+ "power operation to complete (0 = do not wait)",
510+ "default" : APIClient.DEFAULT_CONFIG['operation_timeout'],
511+ "type" : "second",
512+ "required" : "0",
513+ "shortdesc" : "How long to wait for power operation to complete",
514+ "order" : 2
515+ }
516+ all_opt["read_timeout"] = {
517+ "getopt" : ":",
518+ "longopt" : "read-timeout",
519+ "help" : "--read-timeout=[seconds] How long to wait "
520+ "to read data from server",
521+ "default" : APIClient.DEFAULT_CONFIG['read_timeout'],
522+ "type" : "second",
523+ "required" : "0",
524+ "shortdesc" : "How long to wait for server data",
525+ "order" : 2
526+ }
527+ return device_opt
528+
529+def main():
530+ """
531+ Agent entry point
532+ """
533+ # register exit handler used by pacemaker
534+ atexit.register(atexit_handler)
535+
536+ # prepare accepted options
537+ device_opt = set_opts()
538+
539+ # parse options provided on input
540+ options = check_input(device_opt, process_input(device_opt))
541+
542+ docs = {
543+ "shortdesc": "Fence agent for IBM z LPARs",
544+ "longdesc": (
545+ "fence_ibmz is a power fencing agent which uses the HMC Web "
546+ "Services API to fence IBM z LPARs."),
547+ "vendorurl": "http://www.ibm.com"
548+ }
549+ show_docs(options, docs)
550+
551+ run_delay(options)
552+
553+ # set underlying library's logging and ssl config according to specified
554+ # options
555+ requests_log = logging.getLogger("urllib3")
556+ requests_log.propagate = True
557+ if "--verbose" in options:
558+ requests_log.setLevel(logging.DEBUG)
559+ if "--ssl-secure" not in options:
560+ urllib3.disable_warnings(
561+ category=urllib3.exceptions.InsecureRequestWarning)
562+
563+ hmc_address = options["--ip"]
564+ hmc_userid = options["--username"]
565+ hmc_password = options["--password"]
566+ config = {
567+ 'connect_retries': int(options['--connect-retries']),
568+ 'read_retries': int(options['--read-retries']),
569+ 'operation_timeout': int(options['--operation-timeout']),
570+ 'connect_timeout': int(options['--connect-timeout']),
571+ 'read_timeout': int(options['--read-timeout']),
572+ 'port': int(options['--ipport']),
573+ 'ssl_verify': bool('--ssl-secure' in options),
574+ }
575+ try:
576+ conn = APIClient(hmc_address, hmc_userid, hmc_password, config)
577+ conn.logon()
578+ atexit.register(disconnect, conn)
579+ result = fence_action(conn, options, set_power_status,
580+ get_power_status, get_outlet_list)
581+ except Exception:
582+ logging.exception('Exception occurred: ')
583+ result = EC_GENERIC_ERROR
584+ sys.exit(result)
585+
586+if __name__ == "__main__":
587+ main()
diff --git a/debian/patches/series b/debian/patches/series
index df80fa4..075cb3b 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -10,3 +10,4 @@ lp1865523-05-fence_scsi-fix-python3-encoding.patch
10lp1865523-06-fence_scsi-fixes-around-node-id.patch10lp1865523-06-fence_scsi-fixes-around-node-id.patch
11lp1865523-07-fence-metadata-update.xml11lp1865523-07-fence-metadata-update.xml
12lp1894323-01-fence_aws-new-agent.patch12lp1894323-01-fence_aws-new-agent.patch
130013-Add-fence-agent-for-IBM-z-LPARs.patch
diff --git a/debian/rules b/debian/rules
index 551e252..3d92f95 100755
--- a/debian/rules
+++ b/debian/rules
@@ -47,7 +47,7 @@ override_dh_missing:
4747
48override_dh_auto_test:48override_dh_auto_test:
49 # disable testing for ovh as it tries to access SOAP service on www.ovh.com49 # disable testing for ovh as it tries to access SOAP service on www.ovh.com
50 dh_auto_test -- TEST_TARGET_SKIP=ovh/fence_ovh50 dh_auto_test -- TEST_TARGET_SKIP="ovh/fence_ovh ibmz/fence_ibmz"
5151
52override_dh_python2:52override_dh_python2:
53 dh_python253 dh_python2

Subscribers

People subscribed via source and target branches