mirror of
https://github.com/xcat2/confluent.git
synced 2026-09-29 08:41:00 +00:00
Compare commits
307 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| da400b11d0 | |||
| d9042e5d4c | |||
| e443aa6530 | |||
| 8993f80b13 | |||
| 8e00f3a795 | |||
| 1f6fa7636b | |||
| d86bacf4d8 | |||
| 7230ec1306 | |||
| 00d5416737 | |||
| b22073e106 | |||
| 03369c5d5e | |||
| 0b30b92bc9 | |||
| dd90af1414 | |||
| 9e13724037 | |||
| bbe839ea5d | |||
| ff7bdcc16e | |||
| 6cd961786d | |||
| 18fa8f0b41 | |||
| 6e5375918d | |||
| 046c1e8ec4 | |||
| eb48f2c1f7 | |||
| d25ac000f4 | |||
| a47bdf4181 | |||
| caf41345a1 | |||
| 3d6a1881c8 | |||
| 5083977664 | |||
| 53821d98e6 | |||
| fdb8ef6919 | |||
| b08bf6ae97 | |||
| 3318b3cda7 | |||
| e97f768170 | |||
| 9eacb4e3f0 | |||
| 77bce756cd | |||
| 2decaac8ea | |||
| e0a62a2533 | |||
| f8d2fd8f46 | |||
| 7f18f5fde0 | |||
| f07371c756 | |||
| 179fcd9dcf | |||
| e79a5ecd12 | |||
| 2faf187d2d | |||
| 703d8f273f | |||
| db0bc41441 | |||
| 97effaae01 | |||
| 0a5424fbb2 | |||
| 3571947332 | |||
| 72289f89cb | |||
| bf90431101 | |||
| 784758fa25 | |||
| 6c5536c840 | |||
| 64ebc9b16f | |||
| 213b027f62 | |||
| e9ddbb8785 | |||
| 28eb1180c7 | |||
| 892d01d054 | |||
| 949609ce64 | |||
| 21b99728d7 | |||
| 79b4482298 | |||
| 40f700c46d | |||
| 652d9c0a55 | |||
| 20d7eb62f4 | |||
| 34623d5bbd | |||
| 0e7072a256 | |||
| 298b9bcfff | |||
| ddfdae6bf0 | |||
| b77a97db43 | |||
| 1e1d8dc000 | |||
| 4adb2adc12 | |||
| 0e9e5da102 | |||
| 777c419cb8 | |||
| 96916b57ae | |||
| 40ccf9d325 | |||
| 6bd36580ba | |||
| 37f12296b8 | |||
| cd9eefa100 | |||
| 2c72931641 | |||
| 0731bfe29a | |||
| 667a1030b4 | |||
| 4c6e3fcc4f | |||
| baa4d8bd1a | |||
| 816445e4b6 | |||
| 8cce35dbcb | |||
| dabb93fc04 | |||
| 120b70f7ab | |||
| fb626f7713 | |||
| 41c22d9e66 | |||
| 6b6b7f06b7 | |||
| 4595c417ee | |||
| f5fea79ee4 | |||
| 58ab997105 | |||
| 819318b763 | |||
| d47dfd8a73 | |||
| bcfc6966c3 | |||
| e1cb97e836 | |||
| 7f2df84167 | |||
| ce1a5b464f | |||
| 4fb41a991e | |||
| bd735e8902 | |||
| 7dcbd75561 | |||
| 4ec06f8940 | |||
| 60b91d5202 | |||
| 4ce382ced2 | |||
| d85fddd555 | |||
| c150202585 | |||
| 8cde65a9e6 | |||
| d1abace7da | |||
| 412166922a | |||
| 5e34ebe525 | |||
| 8274167298 | |||
| f5e6461fd2 | |||
| c9ffe77582 | |||
| 60ec9c7c51 | |||
| 1d04405099 | |||
| 16db3f1b6a | |||
| a1b0ab15c5 | |||
| 2a1794ddad | |||
| 7704523410 | |||
| 4cc57acea9 | |||
| 70aa1fd114 | |||
| ba27207b83 | |||
| be12f163a5 | |||
| 20e00b266a | |||
| ae03ca9fd8 | |||
| 1812e16559 | |||
| be971fd5e7 | |||
| 8675929a16 | |||
| a2d440d83b | |||
| e0d6d6b18c | |||
| e47038b2dd | |||
| a5e84e2eb0 | |||
| 6f633e0bfb | |||
| 2112fd71f5 | |||
| d83fb76254 | |||
| 064514d6ec | |||
| 3e8271afa8 | |||
| 3980be4e3a | |||
| 67d61fc803 | |||
| 5f3f073e9c | |||
| 439f4c7709 | |||
| 3b8d0ef40a | |||
| 26a8ba53bd | |||
| c9192ab170 | |||
| 00598578a6 | |||
| 74740e01b1 | |||
| 0034309fe1 | |||
| 0b7f2a45e9 | |||
| 261783f971 | |||
| 9f8feb971d | |||
| 2959fa7e33 | |||
| 0b40b876c0 | |||
| 6952f7cab9 | |||
| c2c81b0167 | |||
| ac0626a51c | |||
| 9effdee15a | |||
| 65580b1d60 | |||
| 9c41031df3 | |||
| 740cccd82e | |||
| 344bb1229c | |||
| 935acc2a10 | |||
| cd841fb43e | |||
| 447a3b3ffe | |||
| 108c9d3e9d | |||
| cc98290d68 | |||
| 1fce78ed39 | |||
| 5bc4d20eef | |||
| 5dbd345a49 | |||
| 62839b76b7 | |||
| 848745bb4a | |||
| f68a8c80bb | |||
| 7f185db64f | |||
| 2cd2e170e0 | |||
| 0df75d3102 | |||
| 5b8aebaa3e | |||
| 0577c9a99c | |||
| 12ffdf9504 | |||
| c2a4913c78 | |||
| f34770529e | |||
| 0336a07f17 | |||
| b57dbcd89b | |||
| 7948cd1bb2 | |||
| 049a728be1 | |||
| 42c775a7a4 | |||
| 9cf074e1a2 | |||
| 631509a32a | |||
| 4e4fbeb907 | |||
| b92fddc647 | |||
| 79474ea9fb | |||
| 639a7352ad | |||
| 4708630bac | |||
| a0be58c9cb | |||
| 71e89c005b | |||
| 1dc8fc973d | |||
| a517436f71 | |||
| ed34b30992 | |||
| 8ca1553c33 | |||
| 02266a4be6 | |||
| 70e5d1dc52 | |||
| 191726776c | |||
| c99d446a63 | |||
| e3318ec5ef | |||
| cbded58fc2 | |||
| 005877d3eb | |||
| 8497c28ebe | |||
| 2d76c0aefc | |||
| 2e5ec9264f | |||
| 9734aa65bf | |||
| 46ed0eb38a | |||
| 85d2a0e6aa | |||
| 5f88067134 | |||
| eb63a6fcda | |||
| 8be3c2a80e | |||
| 2ce26cbb59 | |||
| 44808fd343 | |||
| 467fea7d8e | |||
| 39463ddcb7 | |||
| cdb3801993 | |||
| af478816e9 | |||
| 79e57a442d | |||
| 38d6354725 | |||
| b2ba28f0fb | |||
| 9bf8d65df1 | |||
| 7c048c9c54 | |||
| 9fbf500b6c | |||
| 147be0c92c | |||
| 54583c604f | |||
| de41a665b2 | |||
| 98981f00a9 | |||
| 074a8debf4 | |||
| a4d66cdbc9 | |||
| 2270e64148 | |||
| 0e99d99051 | |||
| b0ddc66e06 | |||
| 364a17f31a | |||
| 04d18c4b22 | |||
| 44330f2351 | |||
| 117cf71065 | |||
| 44b58d58ae | |||
| b5a924bdb6 | |||
| d1fd17200f | |||
| b035bd6c1e | |||
| 02e2cb1673 | |||
| 53954426a1 | |||
| d02c4e51f9 | |||
| 26e9db263d | |||
| ae56085f95 | |||
| 125160e0f5 | |||
| 2f251dd05a | |||
| 88b2b49d3b | |||
| 1fb5c0c177 | |||
| 9fdccf8371 | |||
| f5d7fdcb58 | |||
| b639720190 | |||
| 347dbd8cd1 | |||
| 204b47d174 | |||
| d738a4c61f | |||
| 6ebbd91592 | |||
| 17f587252e | |||
| 5f1a2ed69c | |||
| 0668b90ba9 | |||
| fa4a759bc1 | |||
| 890e23f2d9 | |||
| e08a79b067 | |||
| 0dc42567c2 | |||
| 94a3e83b04 | |||
| 7f71d01c2d | |||
| bd31358649 | |||
| be054689f8 | |||
| 4fada9b931 | |||
| 795e4c1273 | |||
| 9174b46f66 | |||
| 1f3c93d7ee | |||
| c85d02d889 | |||
| 8c7c2026d9 | |||
| e847a5b871 | |||
| c022466bf8 | |||
| e03e9e0736 | |||
| 0caa7b5519 | |||
| c5f1e60414 | |||
| 1f5c7208b8 | |||
| 3edba0f8bb | |||
| 786bf00521 | |||
| b944357071 | |||
| 59a7b190d5 | |||
| bd364ded70 | |||
| da74830353 | |||
| fff0488f89 | |||
| 63431deffb | |||
| a1757571b4 | |||
| 3ecb7aaf83 | |||
| 1ee166ea4e | |||
| 8e15dee11d | |||
| 3610b16a6c | |||
| 4d50007377 | |||
| d3e2af0215 | |||
| cff71ad95b | |||
| edeef69ae9 | |||
| 4505e582b2 | |||
| cf4fda2f7d | |||
| 770559040e | |||
| 711061347c | |||
| 25169c2838 | |||
| 9685870c14 | |||
| 8221760fbd | |||
| 673785f576 | |||
| 51ae476077 | |||
| 8f7199bce6 | |||
| d282db0c02 |
+1
-7
@@ -1,7 +1 @@
|
||||
*.pyc
|
||||
.*.
|
||||
confluent_client/man/man*
|
||||
.vscode
|
||||
.*.sw*
|
||||
.sw*
|
||||
.idea/*
|
||||
.idea
|
||||
|
||||
@@ -175,3 +175,28 @@
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
Name: confluent-client
|
||||
Project: https://hpc.lenovo.com/users/
|
||||
Source: https://github.com/lenovo/confluent
|
||||
|
||||
Files: *
|
||||
Copyright: 2014-2019 Lenovo
|
||||
License: Apache-2.0
|
||||
|
||||
File: client.py:
|
||||
Copyright: 2014 IBM Corporation
|
||||
2015-2019 Lenovo
|
||||
License: Apache-2.0
|
||||
|
||||
File: sortutil.py
|
||||
Copyright: 2014 IBM Corporation
|
||||
2015-2016 Lenovo
|
||||
License: Apache-2.0
|
||||
|
||||
File: tlv.py
|
||||
Copyright: 2014 IBM Corporation
|
||||
License: Apache-2.0
|
||||
@@ -1,24 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
import os
|
||||
import sys
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
try:
|
||||
sys.path.remove(path)
|
||||
except Exception:
|
||||
pass
|
||||
path = os.path.realpath(os.path.join(path, '..', 'confluent_server'))
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.config.attributes as attr
|
||||
import shutil
|
||||
|
||||
shutil.copyfile('doc/man/nodeattrib.ronn.tmpl', 'doc/man/nodeattrib.ronn')
|
||||
shutil.copyfile('doc/man/nodegroupattrib.ronn.tmpl', 'doc/man/nodegroupattrib.ronn')
|
||||
with open('doc/man/nodeattrib.ronn', 'a') as outf:
|
||||
for field in sorted(attr.node):
|
||||
outf.write('\n* `{0}`:\n {1}\n'.format(field, attr.node[field]['description']))
|
||||
with open('doc/man/nodegroupattrib.ronn', 'a') as outf:
|
||||
for field in sorted(attr.node):
|
||||
outf.write('\n* `{0}`:\n {1}\n'.format(field, attr.node[field]['description']))
|
||||
|
||||
|
||||
@@ -1,122 +0,0 @@
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# This is a utility to bring xcoll (plus enhancements) to confluent
|
||||
# the core engine is textgroup.py, this simply provides a CLI to use
|
||||
# generically
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import select
|
||||
import sys
|
||||
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.textgroup as tg
|
||||
import confluent.client
|
||||
|
||||
argparser = optparse.OptionParser(usage="Usage: <other command> | %prog [options]")
|
||||
argparser.add_option('-a', '--abbreviate', action='store_true',
|
||||
help='Attempt to use confluent server to shorten noderanges')
|
||||
argparser.add_option('-b', '--base',
|
||||
help='Use given node as reference for comparison when '
|
||||
'using -d, instead of using the most common result')
|
||||
argparser.add_option('-d', '--diff', action='store_true',
|
||||
help='Show what differs between most common '
|
||||
'output group and others')
|
||||
argparser.add_option('-w', '--watch', action='store_true',
|
||||
help='Show intermediate results while running')
|
||||
argparser.add_option('-g', '--groupcount', action='store_true',
|
||||
help='Show count of output groups rather than the actual output')
|
||||
argparser.add_option('-s', '--skipcommon', action='store_true',
|
||||
help='Do not print most common result, only non modal '
|
||||
'groups, useful when combined with -d')
|
||||
argparser.add_option('-c', '--count', action='store_true',
|
||||
help='Also display count of nodes in a given group')
|
||||
argparser.add_option('-r', '--reverse', action='store_true',
|
||||
help='Reverse sort order to show biggest output group '
|
||||
'last')
|
||||
argparser.add_option('-l', '--log', action='store', type='string', dest='log',
|
||||
help='Log each output to file, using {node} as a placeholder for node.')
|
||||
(options, args) = argparser.parse_args()
|
||||
if sys.stdin.isatty():
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
grouped = tg.GroupedData()
|
||||
|
||||
if options.abbreviate:
|
||||
grouped = tg.GroupedData(confluent.client.Command())
|
||||
else:
|
||||
grouped = tg.GroupedData()
|
||||
|
||||
def print_current():
|
||||
if options.diff:
|
||||
grouped.print_deviants(skipmodal=options.skipcommon, count=options.count,
|
||||
reverse=options.reverse, basenode=options.base)
|
||||
elif options.groupcount:
|
||||
grouped.generate_byoutput()
|
||||
print(len(grouped.byoutput))
|
||||
else:
|
||||
grouped.print_all(skipmodal=options.skipcommon,
|
||||
count=options.count,
|
||||
reverse=options.reverse)
|
||||
sys.stdout.flush()
|
||||
|
||||
fullline = sys.stdin.readline()
|
||||
printpending = True
|
||||
clearpending = False
|
||||
holdoff = 0
|
||||
while fullline:
|
||||
for line in fullline.split('\n'):
|
||||
if not line:
|
||||
continue
|
||||
if ':' not in line:
|
||||
line = 'UNKNOWN: ' + line
|
||||
if options.log:
|
||||
node, output = line.split(':', 1)
|
||||
output = output.lstrip()
|
||||
currlog = options.log.format(node=node, nodename=node)
|
||||
with open(currlog, mode='a') as log:
|
||||
log.write(output + '\n')
|
||||
continue
|
||||
node, output = line.split(':', 1)
|
||||
output = output.lstrip()
|
||||
grouped.add_line(node, output)
|
||||
if options.watch:
|
||||
if not holdoff:
|
||||
holdoff = os.times()[4] + 0.250
|
||||
if (holdoff < os.times()[4] or
|
||||
not select.select((sys.stdin,), (), (), 0.250)[0]):
|
||||
# print now, nothing pending
|
||||
holdoff = 0
|
||||
sys.stdout.write('\x1b[2J\x1b[;H') # clear screen
|
||||
print_current()
|
||||
printpending = False
|
||||
clearpending = True
|
||||
else:
|
||||
printpending = True
|
||||
fullline = sys.stdin.readline()
|
||||
|
||||
if printpending:
|
||||
if clearpending:
|
||||
sys.stdout.write('\x1b[2J\x1b[;H') # clear screen
|
||||
print_current()
|
||||
|
||||
+180
-311
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/python2
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2014 IBM Corporation
|
||||
# Copyright 2015-2018 Lenovo
|
||||
# Copyright 2015-2016 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -49,7 +49,6 @@ import select
|
||||
import shlex
|
||||
import signal
|
||||
import socket
|
||||
import struct
|
||||
import sys
|
||||
import time
|
||||
try:
|
||||
@@ -76,13 +75,7 @@ import confluent.termhandler as termhandler
|
||||
import confluent.tlvdata as tlvdata
|
||||
import confluent.client as client
|
||||
|
||||
try:
|
||||
unicode
|
||||
except NameError:
|
||||
unicode = str
|
||||
|
||||
conserversequence = '\x05c' # ctrl-e, c
|
||||
clearpowermessage = False
|
||||
|
||||
oldtcattr = None
|
||||
fd = sys.stdin
|
||||
@@ -94,15 +87,6 @@ except NameError:
|
||||
netserver = None
|
||||
laststate = {}
|
||||
|
||||
try:
|
||||
input = raw_input
|
||||
except NameError:
|
||||
pass
|
||||
|
||||
class BailOut(Exception):
|
||||
def __init__(self, errorcode=0):
|
||||
self.errorcode = errorcode
|
||||
|
||||
|
||||
def print_help():
|
||||
print("confetty provides a filesystem like interface to confluent. "
|
||||
@@ -131,7 +115,6 @@ def print_help():
|
||||
|
||||
|
||||
def updatestatus(stateinfo={}):
|
||||
global powerstate, powertime, clearpowermessage
|
||||
status = consolename
|
||||
info = []
|
||||
for statekey in stateinfo:
|
||||
@@ -146,15 +129,6 @@ def updatestatus(stateinfo={}):
|
||||
# error will be repeated if relevant
|
||||
# avoid keeping it around as stale
|
||||
del laststate['error']
|
||||
if 'state' in stateinfo: # currently only read power means anything
|
||||
newpowerstate = stateinfo['state']['value']
|
||||
if newpowerstate != powerstate and newpowerstate == 'off':
|
||||
sys.stdout.write("\x1b[2J\x1b[;H[powered off]\r\n")
|
||||
clearpowermessage = True
|
||||
if newpowerstate == 'on' and clearpowermessage:
|
||||
sys.stdout.write("\x1b[2J\x1b[;H")
|
||||
clearpowermessage = False
|
||||
powerstate = newpowerstate
|
||||
if 'clientcount' in laststate and laststate['clientcount'] != 1:
|
||||
info.append('clients: %d' % laststate['clientcount'])
|
||||
if 'bufferage' in stateinfo and stateinfo['bufferage'] is not None:
|
||||
@@ -169,7 +143,7 @@ def updatestatus(stateinfo={}):
|
||||
info.append(time.strftime('%H:%M', time.localtime(showtime)))
|
||||
if info:
|
||||
status += ' [' + ','.join(info) + ']'
|
||||
if os.environ.get('TERM', '') not in ('linux'):
|
||||
if os.environ['TERM'] not in ('linux'):
|
||||
sys.stdout.write('\x1b]0;console: %s\x07' % status)
|
||||
sys.stdout.flush()
|
||||
|
||||
@@ -201,12 +175,12 @@ def recurse_format(datum, levels=0):
|
||||
|
||||
|
||||
def prompt():
|
||||
if os.environ.get('TERM', '') not in ('linux'):
|
||||
if os.environ['TERM'] not in ('linux'):
|
||||
sys.stdout.write('\x1b]0;confetty: %s\x07' % target)
|
||||
try:
|
||||
return input(target + ' -> ')
|
||||
return raw_input(target + ' -> ')
|
||||
except KeyboardInterrupt:
|
||||
print("")
|
||||
print ""
|
||||
return ""
|
||||
except EOFError: # ctrl-d
|
||||
print("exit")
|
||||
@@ -292,8 +266,6 @@ def rcompleter(text, state):
|
||||
|
||||
|
||||
def parse_command(command):
|
||||
if isinstance(command, list):
|
||||
return command
|
||||
try:
|
||||
args = shlex.split(command, posix=True)
|
||||
except ValueError as ve:
|
||||
@@ -306,26 +278,20 @@ currchildren = None
|
||||
|
||||
|
||||
def print_result(res):
|
||||
global exitcode
|
||||
if 'errorcode' in res or 'error' in res:
|
||||
print(res['error'])
|
||||
if 'errorcode' in res:
|
||||
exitcode |= res['errorcode']
|
||||
print res['error']
|
||||
return
|
||||
if 'databynode' in res:
|
||||
print_result(res['databynode'])
|
||||
return
|
||||
for key in sorted(res):
|
||||
for key in res.iterkeys():
|
||||
notes = []
|
||||
if res[key] is None:
|
||||
attrstr = '%s=""' % key
|
||||
elif type(res[key]) == list:
|
||||
attrstr = '%s=%s' % (key, recurse_format(res[key]))
|
||||
elif not isinstance(res[key], dict):
|
||||
try:
|
||||
print('{0}: {1}'.format(key, res[key]))
|
||||
except UnicodeEncodeError:
|
||||
print('{0}: {1}'.format(key, repr(res[key])))
|
||||
print '{0}: {1}'.format(key, res[key])
|
||||
continue
|
||||
elif 'value' in res[key] and res[key]['value'] is not None:
|
||||
attrstr = '%s="%s"' % (key, res[key]['value'])
|
||||
@@ -338,7 +304,7 @@ def print_result(res):
|
||||
else:
|
||||
sys.stdout.write('{0}: '.format(key))
|
||||
if isinstance(res[key], str) or isinstance(res[key], unicode):
|
||||
print(res[key])
|
||||
print res[key]
|
||||
else:
|
||||
print_result(res[key])
|
||||
continue
|
||||
@@ -371,9 +337,9 @@ def do_command(command, server):
|
||||
return
|
||||
argv[0] = argv[0].lower()
|
||||
if argv[0] == 'exit':
|
||||
if os.environ.get('TERM', '') not in ('linux'):
|
||||
if os.environ['TERM'] not in ('linux'):
|
||||
sys.stdout.write('\x1b]0;\x07')
|
||||
raise BailOut()
|
||||
sys.exit(0)
|
||||
elif argv[0] in ('help', '?'):
|
||||
return print_help()
|
||||
elif argv[0] == 'cd':
|
||||
@@ -437,10 +403,10 @@ def do_command(command, server):
|
||||
for res in session.read(targpath):
|
||||
if 'item' in res: # a link relation
|
||||
if type(res['item']) == dict:
|
||||
print(res['item']["href"])
|
||||
print res['item']["href"]
|
||||
else:
|
||||
for item in res['item']:
|
||||
print(item["href"])
|
||||
print item["href"]
|
||||
else: # generic attributes to list
|
||||
if 'error' in res:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
@@ -454,10 +420,6 @@ def do_command(command, server):
|
||||
currconsole = targpath
|
||||
startrequest = {'operation': 'start', 'path': targpath,
|
||||
'parameters': {}}
|
||||
height, width = struct.unpack(
|
||||
'hh', fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ, b'....'))[:2]
|
||||
startrequest['parameters']['width'] = width
|
||||
startrequest['parameters']['height'] = height
|
||||
for param in argv[2:]:
|
||||
(parmkey, parmval) = param.split("=")
|
||||
startrequest['parameters'][parmkey] = parmval
|
||||
@@ -498,12 +460,8 @@ def createresource(args):
|
||||
if keydata is None:
|
||||
return
|
||||
targpath = fullpath_target(resname)
|
||||
if targpath.startswith('/noderange//'):
|
||||
collection = targpath
|
||||
else:
|
||||
collection, _, resname = targpath.rpartition('/')
|
||||
if 'name' not in keydata:
|
||||
keydata['name'] = resname
|
||||
collection, _, resname = targpath.rpartition('/')
|
||||
keydata['name'] = resname
|
||||
makecall(session.create, (collection, keydata))
|
||||
|
||||
|
||||
@@ -518,13 +476,6 @@ def makecall(callout, args):
|
||||
if 'errorcode' in response:
|
||||
exitcode = response['errorcode']
|
||||
sys.stderr.write('Error: ' + response['error'] + '\n')
|
||||
if 'databynode' in response:
|
||||
lresponse = response['databynode']
|
||||
for node in lresponse:
|
||||
if 'errorcode' in lresponse[node]:
|
||||
exitcode = lresponse[node]['errorcode']
|
||||
if 'error' in lresponse[node]:
|
||||
sys.stderr.write('{0}: Error - {1}\n'.format(node, lresponse[node]['error']))
|
||||
|
||||
|
||||
def clearvalues(resource, attribs):
|
||||
@@ -607,20 +558,11 @@ def fullpath_target(currpath, forcepath=False):
|
||||
ntarget += '/'
|
||||
return ntarget
|
||||
|
||||
def do_resize(a, b):
|
||||
if not inconsole:
|
||||
return
|
||||
height, width = struct.unpack(
|
||||
'hh', fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ, b'....'))[:2]
|
||||
tlvdata.send(session.connection, {'operation': 'resize', 'width': width,
|
||||
'height': height})
|
||||
|
||||
|
||||
def startconsole(nodename):
|
||||
global inconsole
|
||||
global consolename
|
||||
global didconsole
|
||||
signal.signal(signal.SIGWINCH, do_resize)
|
||||
didconsole = True
|
||||
consolename = nodename
|
||||
tty.setraw(sys.stdin.fileno())
|
||||
@@ -635,16 +577,15 @@ def quitconfetty(code=0, fullexit=False, fixterm=True):
|
||||
global didconsole
|
||||
if fixterm or didconsole:
|
||||
currfl = fcntl.fcntl(sys.stdin.fileno(), fcntl.F_GETFL)
|
||||
fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, currfl & ~os.O_NONBLOCK)
|
||||
fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, currfl ^ os.O_NONBLOCK)
|
||||
if oldtcattr is not None:
|
||||
termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, oldtcattr)
|
||||
# Request default color scheme, to undo potential weirdness of terminal
|
||||
if sys.stdout.isatty():
|
||||
sys.stdout.write('\x1b[m')
|
||||
sys.stdout.write('\x1b[m')
|
||||
if fullexit:
|
||||
if sys.stdout.isatty() and os.environ.get('TERM', '') not in ('linux'):
|
||||
if os.environ['TERM'] not in ('linux'):
|
||||
sys.stdout.write('\x1b]0;\x07')
|
||||
raise BailOut(code)
|
||||
sys.exit(code)
|
||||
else:
|
||||
tlvdata.send(session.connection, {'operation': 'stop',
|
||||
'path': currconsole})
|
||||
@@ -663,28 +604,6 @@ def get_session_node(shellargs):
|
||||
return None
|
||||
|
||||
|
||||
def run_inline_command(path, arg, completion, **kwargs):
|
||||
tlvdata.send(session.connection, {'operation': 'pause',
|
||||
'path': currconsole})
|
||||
buffdata = ''
|
||||
while select.select((session.connection,), (), (), 0)[0]:
|
||||
buffdata += consume_termdata(session.connection, bufferonly=True)
|
||||
rc = session.simple_noderange_command(consolename, path, arg, **kwargs)
|
||||
tlvdata.send(session.connection, {'operation': 'resume',
|
||||
'path': currconsole})
|
||||
sys.stdout.write(completion)
|
||||
sys.stdout.flush()
|
||||
if buffdata:
|
||||
try:
|
||||
sys.stdout.write(buffdata)
|
||||
except UnicodeEncodeError:
|
||||
sys.stdout.buffer.write(buffdata.encode('utf8'))
|
||||
except IOError: # Some times circumstances are bad
|
||||
# resort to byte at a time...
|
||||
for d in buffdata:
|
||||
sys.stdout.write(d)
|
||||
return rc
|
||||
|
||||
def conserver_command(filehandle, localcommand):
|
||||
# x - conserver has that as 'show baud', I am inclined to replace that with
|
||||
# 'request exclusive'
|
||||
@@ -720,13 +639,13 @@ def conserver_command(filehandle, localcommand):
|
||||
localcommand = get_command_bytes(filehandle, localcommand, cmdlen)
|
||||
|
||||
if localcommand[1] == 'o': # off
|
||||
sys.stdout.write("powering off...")
|
||||
sys.stdout.flush()
|
||||
run_inline_command('/power/state', 'off', 'complete]')
|
||||
print("powering off...")
|
||||
session.simple_noderange_command(consolename, '/power/state', 'off')
|
||||
print("complete]\r")
|
||||
elif localcommand[1] == 's': # shutdown
|
||||
sys.stdout.write("shutting down...")
|
||||
sys.stdout.flush()
|
||||
run_inline_command('/power/state', 'shutdown', 'complete]')
|
||||
print("shutting down...")
|
||||
session.simple_noderange_command(consolename, '/power/state', 'shutdown')
|
||||
print("complete]\r")
|
||||
elif localcommand[1] == 'b': # boot
|
||||
cmdlen += 1
|
||||
localcommand = get_command_bytes(filehandle, localcommand, cmdlen)
|
||||
@@ -737,63 +656,61 @@ def conserver_command(filehandle, localcommand):
|
||||
bootmode = 'uefi'
|
||||
bootdev = 'setup'
|
||||
|
||||
rc = run_inline_command('/boot/nextdevice', bootdev, '', bootmode=bootmode)
|
||||
rc = session.simple_noderange_command(consolename, '/boot/nextdevice', bootdev, bootmode=bootmode)
|
||||
|
||||
if rc:
|
||||
print("Error]\r")
|
||||
else:
|
||||
rc = run_inline_command('/power/state', 'boot', 'complete]')
|
||||
if rc:
|
||||
print("Error]")
|
||||
|
||||
elif localcommand[2] == 'n': # boot to network
|
||||
sys.stdout.write("booting to network...")
|
||||
sys.stdout.flush()
|
||||
bootmode = 'uefi'
|
||||
bootdev = 'network'
|
||||
rc = run_inline_command('/boot/nextdevice', bootdev, '', bootmode=bootmode)
|
||||
if rc:
|
||||
print("Error]\r")
|
||||
else:
|
||||
rc = run_inline_command('/power/state', 'boot', 'complete]')
|
||||
rc = session.simple_noderange_command(consolename, '/power/state', 'boot')
|
||||
if rc:
|
||||
print("Error]\r")
|
||||
else:
|
||||
print("complete]\r")
|
||||
|
||||
elif localcommand[2] == 'n': # boot to network
|
||||
print("booting to network...")
|
||||
|
||||
bootmode = 'uefi'
|
||||
bootdev = 'network'
|
||||
|
||||
rc = session.simple_noderange_command(consolename, '/boot/nextdevice', bootdev, bootmode=bootmode)
|
||||
|
||||
if rc:
|
||||
print("Error]\r")
|
||||
else:
|
||||
rc = session.simple_noderange_command(consolename, '/power/state', 'boot')
|
||||
|
||||
if rc:
|
||||
print("Error]\r")
|
||||
else:
|
||||
print("complete]\r")
|
||||
|
||||
elif localcommand[2] == '\x0d': # boot to default
|
||||
print("booting to default...")
|
||||
|
||||
bootmode = 'uefi'
|
||||
bootdev = 'default'
|
||||
|
||||
rc = run_inline_command('/boot/nextdevice', bootdev, '', bootmode=bootmode)
|
||||
rc = session.simple_noderange_command(consolename, '/boot/nextdevice', bootdev, bootmode=bootmode)
|
||||
|
||||
if rc:
|
||||
print("Error]\r")
|
||||
else:
|
||||
rc = run_inline_command('/power/state', 'boot', 'complete]')
|
||||
rc = session.simple_noderange_command(consolename, '/power/state', 'boot')
|
||||
|
||||
if rc:
|
||||
print("Error]\r")
|
||||
else:
|
||||
print("complete]\r")
|
||||
|
||||
else:
|
||||
print("Unknown boot state.]\r")
|
||||
|
||||
else:
|
||||
print("Unknown power state.]\r")
|
||||
check_power_state()
|
||||
elif localcommand[0] == 'r':
|
||||
sys.stdout.write('\x1b7\x1b[999;999H\x1b[6n')
|
||||
sys.stdout.flush()
|
||||
reply = ''
|
||||
while 'R' not in reply:
|
||||
try:
|
||||
reply += sys.stdin.read(1)
|
||||
except IOError:
|
||||
pass
|
||||
reply = reply.replace('\x1b[', '')
|
||||
reply = reply.replace('R', '')
|
||||
height, width = reply.split(';')
|
||||
sys.stdout.write('\x1b8')
|
||||
sys.stdout.flush()
|
||||
print('sending stty commands]')
|
||||
return 'stty columns {0}\rstty rows {1}\r'.format(width, height)
|
||||
|
||||
#check_power_state()
|
||||
|
||||
elif localcommand[0] == '?':
|
||||
print("help]\r")
|
||||
print(". exit console\r")
|
||||
@@ -804,7 +721,6 @@ def conserver_command(filehandle, localcommand):
|
||||
print("pbs boot to setup\r")
|
||||
print("pbn boot to network\r")
|
||||
print("pb<ent> boot to default\r")
|
||||
print("r send stty command to resize terminal\r")
|
||||
print("<cr> abort command\r")
|
||||
elif localcommand[0] == '\x0d':
|
||||
print("ignored]\r")
|
||||
@@ -814,10 +730,7 @@ def conserver_command(filehandle, localcommand):
|
||||
|
||||
def get_command_bytes(filehandle, localcommand, cmdlen):
|
||||
while len(localcommand) < cmdlen:
|
||||
try:
|
||||
ready, _, _ = select.select((filehandle,), (), (), 1)
|
||||
except select.error:
|
||||
ready = ()
|
||||
ready, _, _ = select.select((filehandle,), (), (), 1)
|
||||
if ready:
|
||||
localcommand += filehandle.read()
|
||||
return localcommand
|
||||
@@ -830,10 +743,7 @@ def check_escape_seq(currinput, filehandle):
|
||||
sys.stdout.flush()
|
||||
return conserver_command(
|
||||
filehandle, currinput[len(conserversequence):])
|
||||
try:
|
||||
ready, _, _ = select.select((filehandle,), (), (), 3)
|
||||
except select.error:
|
||||
ready = ()
|
||||
ready, _, _ = select.select((filehandle,), (), (), 3)
|
||||
if not ready: # 3 seconds of no typing
|
||||
break
|
||||
currinput += filehandle.read()
|
||||
@@ -846,10 +756,6 @@ parser.add_option("-s", "--server", dest="netserver",
|
||||
parser.add_option("-c", "--control", dest="controlpath",
|
||||
help="Path to offer terminal control",
|
||||
metavar="PATH")
|
||||
parser.add_option(
|
||||
'-m', '--mintime', default=0,
|
||||
help='Minimum time to run or else pause for input (used to keep a '
|
||||
'terminal from closing quickly on error)')
|
||||
opts, shellargs = parser.parse_args()
|
||||
|
||||
username = None
|
||||
@@ -874,176 +780,139 @@ def server_connect():
|
||||
passphrase = os.environ['CONFLUENT_PASSPHRASE']
|
||||
session.authenticate(username, passphrase)
|
||||
while not session.authenticated:
|
||||
username = input("Name: ")
|
||||
username = raw_input("Name: ")
|
||||
passphrase = getpass.getpass("Passphrase: ")
|
||||
session.authenticate(username, passphrase)
|
||||
|
||||
def check_power_state():
|
||||
tlvdata.send(
|
||||
session.connection,
|
||||
{'operation': 'retrieve',
|
||||
'path': '/nodes/' + consolename + '/power/state'})
|
||||
return
|
||||
|
||||
try:
|
||||
server_connect()
|
||||
except EOFError, KeyboardInterrupt:
|
||||
sys.exit(0)
|
||||
except socket.gaierror:
|
||||
sys.stderr.write('Could not connect to confluent\n')
|
||||
sys.exit(1)
|
||||
# clear on start can help with readable of TUI, but it
|
||||
# can be annoying, so for now don't do it.
|
||||
# sys.stdout.write('\x1b[H\x1b[J')
|
||||
# sys.stdout.flush()
|
||||
|
||||
if sys.stdout.isatty():
|
||||
import readline
|
||||
|
||||
readline.parse_and_bind("tab: complete")
|
||||
readline.parse_and_bind("set bell-style none")
|
||||
dl = readline.get_completer_delims().replace('-', '')
|
||||
readline.set_completer_delims(dl)
|
||||
readline.set_completer(completer)
|
||||
|
||||
def main():
|
||||
global inconsole
|
||||
try:
|
||||
server_connect()
|
||||
except (EOFError, KeyboardInterrupt) as _:
|
||||
raise BailOut(0)
|
||||
except socket.gaierror:
|
||||
sys.stderr.write('Could not connect to confluent\n')
|
||||
raise BailOut(1)
|
||||
# clear on start can help with readable of TUI, but it
|
||||
# can be annoying, so for now don't do it.
|
||||
# sys.stdout.write('\x1b[H\x1b[J')
|
||||
# sys.stdout.flush()
|
||||
global powerstate, powertime, clearpowermessage
|
||||
doexit = False
|
||||
inconsole = False
|
||||
pendingcommand = ""
|
||||
session_node = get_session_node(shellargs)
|
||||
if session_node is not None:
|
||||
consoleonly = True
|
||||
do_command("start /nodes/%s/console/session" % session_node, netserver)
|
||||
doexit = True
|
||||
elif shellargs:
|
||||
command = " ".join(shellargs)
|
||||
do_command(command, netserver)
|
||||
quitconfetty(fullexit=True, fixterm=False)
|
||||
|
||||
powerstate = None
|
||||
powertime = None
|
||||
|
||||
|
||||
if sys.stdout.isatty():
|
||||
|
||||
readline.parse_and_bind("tab: complete")
|
||||
readline.parse_and_bind("set bell-style none")
|
||||
dl = readline.get_completer_delims().replace('-', '')
|
||||
readline.set_completer_delims(dl)
|
||||
readline.set_completer(completer)
|
||||
|
||||
doexit = False
|
||||
inconsole = False
|
||||
pendingcommand = ""
|
||||
session_node = get_session_node(shellargs)
|
||||
if session_node is not None:
|
||||
consoleonly = True
|
||||
do_command("start /nodes/%s/console/session" % session_node, netserver)
|
||||
doexit = True
|
||||
elif shellargs:
|
||||
do_command(shellargs, netserver)
|
||||
quitconfetty(fullexit=True, fixterm=False)
|
||||
|
||||
powerstate = None
|
||||
powertime = None
|
||||
|
||||
while inconsole or not doexit:
|
||||
if inconsole:
|
||||
try:
|
||||
rdylist, _, _ = select.select(
|
||||
(sys.stdin, session.connection), (), (), 10)
|
||||
except select.error:
|
||||
rdylist = ()
|
||||
for fh in rdylist:
|
||||
if fh == session.connection:
|
||||
consume_termdata(fh)
|
||||
else:
|
||||
try:
|
||||
myinput = fh.read()
|
||||
myinput = check_escape_seq(myinput, fh)
|
||||
if myinput:
|
||||
tlvdata.send(session.connection, myinput)
|
||||
except IOError:
|
||||
pass
|
||||
if powerstate is None or powertime < time.time() - 10: # Check powerstate every 10 seconds
|
||||
powertime = time.time()
|
||||
powerstate = True
|
||||
check_power_state()
|
||||
def check_power_state():
|
||||
global powerstate, powertime
|
||||
for rsp in session.read('/nodes/' + consolename + '/power/state'):
|
||||
if type(rsp) == dict and 'state' in rsp:
|
||||
newpowerstate = rsp['state']['value']
|
||||
powertime = time.time()
|
||||
if newpowerstate != powerstate and newpowerstate == 'off':
|
||||
sys.stdout.write("\x1b[2J\x1b[;H[powered off]\r\n")
|
||||
powerstate = newpowerstate
|
||||
elif type(rsp) == dict and '_requestdone' in rsp:
|
||||
break
|
||||
elif type(rsp) == dict:
|
||||
updatestatus(rsp)
|
||||
else:
|
||||
currcommand = prompt()
|
||||
try:
|
||||
do_command(currcommand, netserver)
|
||||
except socket.error:
|
||||
try:
|
||||
server_connect()
|
||||
do_command(currcommand, netserver)
|
||||
except socket.error:
|
||||
doexit = True
|
||||
sys.stdout.write('Lost connection to server')
|
||||
quitconfetty(fullexit=True)
|
||||
|
||||
def consume_termdata(fh, bufferonly=False):
|
||||
global clearpowermessage
|
||||
try:
|
||||
data = tlvdata.recv(fh)
|
||||
except Exception:
|
||||
data = None
|
||||
if type(data) == dict:
|
||||
updatestatus(data)
|
||||
return ''
|
||||
if data is not None:
|
||||
data = client.stringify(data)
|
||||
if clearpowermessage:
|
||||
sys.stdout.write("\x1b[2J\x1b[;H")
|
||||
clearpowermessage = False
|
||||
if bufferonly:
|
||||
return data
|
||||
try:
|
||||
sys.stdout.write(data)
|
||||
except UnicodeEncodeError:
|
||||
sys.stdout.buffer.write(data.encode('utf8'))
|
||||
except IOError: # Some times circumstances are bad
|
||||
# resort to byte at a time...
|
||||
for d in data:
|
||||
sys.stdout.write(d)
|
||||
now = time.time()
|
||||
if ('showtime' not in laststate or
|
||||
(now // 60) != laststate['showtime'] // 60):
|
||||
# don't bother churning if minute does not change
|
||||
laststate['showtime'] = now
|
||||
updatestatus()
|
||||
try:
|
||||
sys.stdout.write(rsp)
|
||||
sys.stdout.flush()
|
||||
except Exception:
|
||||
# EWOULDBLOCK causes this to raise, ignore
|
||||
# this scenario comfortable that it
|
||||
# will come out soon enough
|
||||
pass
|
||||
|
||||
|
||||
while inconsole or not doexit:
|
||||
if inconsole:
|
||||
rdylist, _, _ = select.select(
|
||||
(sys.stdin, session.connection), (), (), 60)
|
||||
for fh in rdylist:
|
||||
if fh == session.connection:
|
||||
# this only should get called in the
|
||||
# case of a console session
|
||||
# each command should slurp up all relevant
|
||||
# recv potential
|
||||
#fh.read()
|
||||
try:
|
||||
data = tlvdata.recv(fh)
|
||||
except Exception:
|
||||
data = None
|
||||
if type(data) == dict:
|
||||
updatestatus(data)
|
||||
continue
|
||||
if data is not None:
|
||||
try:
|
||||
sys.stdout.write(data)
|
||||
except IOError: # Some times circumstances are bad
|
||||
# resort to byte at a time...
|
||||
for d in data:
|
||||
sys.stdout.write(d)
|
||||
now = time.time()
|
||||
if ('showtime' not in laststate or
|
||||
(now // 60) != laststate['showtime'] // 60):
|
||||
# don't bother churning if minute does not change
|
||||
laststate['showtime'] = now
|
||||
updatestatus()
|
||||
sys.stdout.flush()
|
||||
else:
|
||||
deadline = 5
|
||||
connected = False
|
||||
while not connected and deadline > 0:
|
||||
try:
|
||||
server_connect()
|
||||
connected = True
|
||||
except (socket.gaierror, socket.error):
|
||||
pass
|
||||
if not connected:
|
||||
time.sleep(1)
|
||||
deadline -=1
|
||||
if connected:
|
||||
do_command(
|
||||
"start /nodes/%s/console/session skipreplay=True" % consolename,
|
||||
netserver)
|
||||
else:
|
||||
doexit = True
|
||||
inconsole = False
|
||||
sys.stdout.write("\r\n[remote disconnected]\r\n")
|
||||
break
|
||||
else:
|
||||
try:
|
||||
myinput = fh.read()
|
||||
myinput = check_escape_seq(myinput, fh)
|
||||
if myinput:
|
||||
tlvdata.send(session.connection, myinput)
|
||||
except IOError:
|
||||
pass
|
||||
#if powerstate is None or powertime < time.time() - 60: # Check powerstate every 60 seconds
|
||||
# check_power_state()
|
||||
else:
|
||||
deadline = 5
|
||||
connected = False
|
||||
while not connected and deadline > 0:
|
||||
currcommand = prompt()
|
||||
try:
|
||||
do_command(currcommand, netserver)
|
||||
except socket.error:
|
||||
try:
|
||||
server_connect()
|
||||
connected = True
|
||||
except Exception:
|
||||
pass
|
||||
if not connected:
|
||||
time.sleep(1)
|
||||
deadline -=1
|
||||
if connected:
|
||||
do_command(
|
||||
"start /nodes/%s/console/session skipreplay=True" % consolename,
|
||||
netserver)
|
||||
else:
|
||||
doexit = True
|
||||
inconsole = False
|
||||
sys.stdout.write("\r\n[remote disconnected]\r\n")
|
||||
return ''
|
||||
|
||||
if __name__ == '__main__':
|
||||
errcode = 0
|
||||
deadline = 0
|
||||
if opts.mintime:
|
||||
deadline = os.times()[4] + float(opts.mintime)
|
||||
try:
|
||||
main()
|
||||
except BailOut as e:
|
||||
errcode = e.errorcode
|
||||
except Exception as e:
|
||||
import traceback
|
||||
excinfo = traceback.print_exc()
|
||||
try:
|
||||
quitconfetty()
|
||||
except Exception:
|
||||
pass
|
||||
print(excinfo)
|
||||
finally:
|
||||
if deadline and os.times()[4] < deadline:
|
||||
sys.stderr.write('[Exited early, hit enter to continue]')
|
||||
sys.stdin.readline()
|
||||
if errcode == 0:
|
||||
errcode = exitcode
|
||||
sys.exit(errcode)
|
||||
do_command(currcommand, netserver)
|
||||
except socket.error:
|
||||
doexit = True
|
||||
sys.stdout.write('Lost connection to server')
|
||||
quitconfetty(fullexit=True)
|
||||
|
||||
@@ -1,71 +0,0 @@
|
||||
#!/usr/bin/python2
|
||||
import optparse
|
||||
import signal
|
||||
import sys
|
||||
import os
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
import confluent.sortutil as sortutil
|
||||
|
||||
|
||||
def lookupdata(data, key):
|
||||
ret = data.get(key, {}).get('value', '')
|
||||
if ret is None:
|
||||
ret = ''
|
||||
return ret
|
||||
|
||||
def main():
|
||||
argparser = optparse.OptionParser(
|
||||
usage='''\n %prog noderange -o ansible.hosts
|
||||
\n ''')
|
||||
argparser.add_option('-o', '--output',
|
||||
help='Ansible hosts file')
|
||||
(options, args) = argparser.parse_args()
|
||||
try:
|
||||
noderange = args[0]
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
if not options.output:
|
||||
sys.stderr.write('Output file must be specified by -o\n')
|
||||
sys.exit(1)
|
||||
sess = client.Command()
|
||||
databynode = {}
|
||||
for res in sess.read('/noderange/{0}/attributes/all'.format(noderange)):
|
||||
for node in res.get('databynode', {}):
|
||||
if node not in databynode:
|
||||
databynode[node] = {}
|
||||
databynode[node].update(res['databynode'][node])
|
||||
nodesbygroup = {}
|
||||
with open(options.output, 'w') as importfile:
|
||||
needempty = False
|
||||
for node in sortutil.natural_sort(databynode):
|
||||
data = databynode[node]
|
||||
if not data.get('groups', []):
|
||||
importfile.write(node + '\n')
|
||||
needempty = True
|
||||
for g in data.get('groups', []):
|
||||
if g not in nodesbygroup:
|
||||
nodesbygroup[g] = set([node])
|
||||
else:
|
||||
nodesbygroup[g].add(node)
|
||||
if needempty:
|
||||
importfile.write('\n')
|
||||
for group in sortutil.natural_sort(nodesbygroup):
|
||||
importfile.write('[{0}]\n'.format(group))
|
||||
for node in sortutil.natural_sort(nodesbygroup[group]):
|
||||
importfile.write('{0}\n'.format(node))
|
||||
importfile.write('\n')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,212 +0,0 @@
|
||||
#!/usr/bin/python2
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import signal
|
||||
import sys
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
import confluent.client as client
|
||||
import confluent.sortutil as sortutil
|
||||
|
||||
def partitionhostsline(line):
|
||||
comment = ''
|
||||
if '#' in line:
|
||||
cmdidx = line.index('#')
|
||||
comment = line[cmdidx:]
|
||||
line = line[:cmdidx].strip()
|
||||
if not line.strip():
|
||||
return '', [], comment
|
||||
ipaddr, names = line.split(None, 1)
|
||||
names = names.split()
|
||||
return ipaddr, names, comment
|
||||
|
||||
class HostMerger(object):
|
||||
def __init__(self):
|
||||
self.byip = {}
|
||||
self.byname = {}
|
||||
self.byname6 = {}
|
||||
self.sourcelines = []
|
||||
self.targlines = []
|
||||
|
||||
def read_source(self, sourcefile):
|
||||
with open(sourcefile, 'r') as hfile:
|
||||
self.sourcelines = hfile.read().split('\n')
|
||||
while not self.sourcelines[-1]:
|
||||
self.sourcelines = self.sourcelines[:-1]
|
||||
for x in range(len(self.sourcelines)):
|
||||
line = self.sourcelines[x]
|
||||
currip, names, comment = partitionhostsline(line)
|
||||
if currip:
|
||||
self.byip[currip] = x
|
||||
byname = self.byname
|
||||
if ':' in currip:
|
||||
byname = self.byname6
|
||||
for name in names:
|
||||
byname[name] = x
|
||||
|
||||
def add_entry(self, ip, names):
|
||||
targ = self.byname
|
||||
if ':' in ip:
|
||||
targ = self.byname6
|
||||
line = '{:<39} {}'.format(ip, names)
|
||||
x = len(self.sourcelines)
|
||||
self.sourcelines.append(line)
|
||||
for name in names.split():
|
||||
if not name:
|
||||
continue
|
||||
targ[name] = x
|
||||
self.byip[ip] = x
|
||||
|
||||
|
||||
def read_target(self, targetfile):
|
||||
if not os.path.exists(targetfile):
|
||||
return
|
||||
with open(targetfile, 'r') as hfile:
|
||||
lines = hfile.read().split('\n')
|
||||
while lines and not lines[-1]:
|
||||
lines = lines[:-1]
|
||||
for y in range(len(lines)):
|
||||
line = lines[y]
|
||||
currip, names, comment = partitionhostsline(line)
|
||||
byname = self.byname
|
||||
if ':' in currip:
|
||||
byname = self.byname6
|
||||
if currip in self.byip:
|
||||
x = self.byip[currip]
|
||||
if self.sourcelines[x] is None:
|
||||
# have already consumed this entry
|
||||
continue
|
||||
self.targlines.append(self.sourcelines[x])
|
||||
self.sourcelines[x] = None
|
||||
continue
|
||||
for name in names:
|
||||
if name in byname:
|
||||
x = byname[name]
|
||||
if self.sourcelines[x] is None:
|
||||
break
|
||||
self.targlines.append(self.sourcelines[x])
|
||||
self.sourcelines[x] = None
|
||||
break
|
||||
else:
|
||||
self.targlines.append(line)
|
||||
|
||||
def write_out(self, targetfile):
|
||||
while self.targlines and not self.targlines[-1]:
|
||||
self.targlines = self.targlines[:-1]
|
||||
while self.sourcelines and not self.sourcelines[-1]:
|
||||
self.sourcelines = self.sourcelines[:-1]
|
||||
if not self.sourcelines:
|
||||
break
|
||||
with open(targetfile, 'w') as hosts:
|
||||
for line in self.targlines:
|
||||
hosts.write(line + '\n')
|
||||
for line in self.sourcelines:
|
||||
if line is not None:
|
||||
hosts.write(line + '\n')
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description="Create/amend /etc/hosts file for given noderange")
|
||||
ap.add_argument('noderange', help='Noderange to generate/update /etc/hosts for')
|
||||
ap.add_argument('-a', '--attrib', help='Pull ip addresses and hostnames from attribute database', action='store_true')
|
||||
ap.add_argument('-i', '--ip', help='Expression to generate addresses (e.g. 172.16.1.{n1} or fd2b:246f:8a50::{n1:x})')
|
||||
ap.add_argument('-n', '--name', help='Expression for name to add ({node}-compute, etc). If unspecified, "{node} {node}.{dns.domain}" will be used', action='append')
|
||||
args = ap.parse_args()
|
||||
c = client.Command()
|
||||
if args.name:
|
||||
names = ' '.join(args.name)
|
||||
else:
|
||||
names = '{node} {node}.{dns.domain}'
|
||||
merger = HostMerger()
|
||||
if not args.ip and not args.attrib:
|
||||
sys.stderr.write('-a or -i is currently required\n')
|
||||
sys.exit(1)
|
||||
if args.attrib:
|
||||
ip4bynode = {}
|
||||
ip6bynode = {}
|
||||
namesbynode = {}
|
||||
domainsbynode = {}
|
||||
for ent in c.read('/noderange/{0}/attributes/current'.format(args.noderange)):
|
||||
ent = ent.get('databynode', {})
|
||||
for node in ent:
|
||||
for attrib in ent[node]:
|
||||
val = ent[node][attrib]
|
||||
if not isinstance(val, dict):
|
||||
continue
|
||||
val = val.get('value', None)
|
||||
if attrib == 'dns.domain':
|
||||
domainsbynode[node] = val
|
||||
if attrib.startswith('net.'):
|
||||
nameparts = attrib.split('.')
|
||||
if len(nameparts) == 2:
|
||||
currnet = None
|
||||
else:
|
||||
currnet = '.'.join(nameparts[1:-1])
|
||||
for currdict in (ip4bynode, ip6bynode, namesbynode):
|
||||
if node not in currdict:
|
||||
currdict[node] = {}
|
||||
if attrib.endswith('.ipv4_address') and val:
|
||||
ip4bynode[node][currnet] = val.split('/', 1)[0]
|
||||
elif attrib.endswith('.ipv6_address') and val:
|
||||
ip6bynode[node][currnet] = val.split('/', 1)[0]
|
||||
elif attrib.endswith('.hostname'):
|
||||
namesbynode[node][currnet] = re.split('\s+|,', val)
|
||||
for node in ip4bynode:
|
||||
mydomain = domainsbynode.get(node, None)
|
||||
for ipdb in (ip4bynode, ip6bynode):
|
||||
for currnet in ipdb[node]:
|
||||
if args.name:
|
||||
sys.stderr.write('Custom name and attribute addresses not currently supported together\n')
|
||||
sys.exit(1)
|
||||
else:
|
||||
names = list(namesbynode.get(node, {}).get(currnet, [node]))
|
||||
if mydomain:
|
||||
for name in names:
|
||||
if mydomain in names:
|
||||
break
|
||||
else:
|
||||
for name in list(names):
|
||||
names.append('{0}.{1}'.format(name, mydomain))
|
||||
names = ' '.join(names)
|
||||
merger.add_entry(ipdb[node][currnet], names)
|
||||
merger.write_out('/etc/whatnowhosts')
|
||||
else:
|
||||
namesbynode = {}
|
||||
ipbynode = {}
|
||||
expurl = '/noderange/{0}/attributes/expression'.format(args.noderange)
|
||||
expression = names
|
||||
exitcode = 0
|
||||
exitcode |= expand_expression(c, namesbynode, expurl, names)
|
||||
exitcode |= expand_expression(c, ipbynode, expurl, args.ip)
|
||||
if exitcode:
|
||||
sys.exit(exitcode)
|
||||
for node in ipbynode:
|
||||
merger.add_entry(ipbynode[node], namesbynode[node])
|
||||
if os.path.exists('/etc/hosts'):
|
||||
merger.read_target('/etc/hosts')
|
||||
os.rename('/etc/hosts', '/etc/hosts.confluentbkup')
|
||||
merger.write_out('/etc/hosts')
|
||||
|
||||
|
||||
|
||||
def expand_expression(c, namesbynode, expurl, expression):
|
||||
exitcode = 0
|
||||
for exp in c.create(expurl, {'expression': expression}):
|
||||
if 'error' in exp:
|
||||
sys.stderr.write(exp['error'] + '\n')
|
||||
exitcode |= exp.get('errorcode', 1)
|
||||
ex = exp.get('databynode', ())
|
||||
for node in ex:
|
||||
namesbynode[node] = ex[node]['value']
|
||||
return exitcode
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,76 +0,0 @@
|
||||
#!/usr/bin/python2
|
||||
import csv
|
||||
import optparse
|
||||
import signal
|
||||
import sys
|
||||
import os
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
import confluent.sortutil as sortutil
|
||||
|
||||
headers = '# Type,Serial Number,Current IP,Current username,Current password,New password,Recovery password,Switch enable password,New IPv4,IPv4 Subnet mask,IPv4 Default gateway,IPv4 DNS1,IPv4 DNS2,New IPv6,IPv6 Prefix,IPv6 Default gateway,IPv6 DNS1,IPv6 DNS2,Domain,Host name,Display name,Rack,Lowest Rack Unit,Height,Force,Stored credentials ID,Managed authentication,Group Name'.split(',')
|
||||
|
||||
def lookupdata(data, key):
|
||||
ret = data.get(key, {}).get('value', '')
|
||||
if ret is None:
|
||||
ret = ''
|
||||
return ret
|
||||
|
||||
def main():
|
||||
argparser = optparse.OptionParser(
|
||||
usage='''\n %prog noderange -o bulkimport.csv
|
||||
\n ''')
|
||||
argparser.add_option('-o', '--output',
|
||||
help='File to write for bulk import into xClarity Administrator')
|
||||
(options, args) = argparser.parse_args()
|
||||
try:
|
||||
noderange = args[0]
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
if not options.output:
|
||||
sys.stderr.write('Output file must be specified by -o\n')
|
||||
sys.exit(1)
|
||||
if 'XCCUSER' not in os.environ or 'XCCPASS' not in os.environ:
|
||||
sys.stderr.write('Must specify XCCUSER and XCCPASS in environment variables\n')
|
||||
sys.exit(1)
|
||||
xccuser = os.environ['XCCUSER']
|
||||
xccpass = os.environ['XCCPASS']
|
||||
sess = client.Command()
|
||||
databynode = {}
|
||||
for res in sess.read('/noderange/{0}/attributes/all'.format(noderange)):
|
||||
for node in res.get('databynode', {}):
|
||||
if node not in databynode:
|
||||
databynode[node] = {}
|
||||
databynode[node].update(res['databynode'][node])
|
||||
for res in sess.read(
|
||||
'/noderange/{0}/configuration/management_controller/net_interfaces/management'.format(
|
||||
noderange)):
|
||||
for node in res.get('databynode', {}):
|
||||
currip = res['databynode'][node].get('ipv4_address', {}).get(
|
||||
'value', '')
|
||||
if currip:
|
||||
databynode[node]['hardwaremanagement.manager'] = {
|
||||
'value': currip.split('/', 1)[0]}
|
||||
with open(options.output, 'w') as importfile:
|
||||
bulkimport = csv.writer(importfile)
|
||||
bulkimport.writerow(headers)
|
||||
for node in sortutil.natural_sort(databynode):
|
||||
data = databynode[node]
|
||||
row = ['server', lookupdata(data, 'id.serial'), lookupdata(data, 'hardwaremanagement.manager'), xccuser, xccpass, '', '', '', '', '', '', '', '', '', '', '', '', '', '', '', node, lookupdata(data, 'location.rack'), lookupdata(data, 'location.u'), '', '', '','', '']
|
||||
bulkimport.writerow(row)
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,105 +0,0 @@
|
||||
#!/usr/bin/python2
|
||||
import csv
|
||||
import optparse
|
||||
import signal
|
||||
import sys
|
||||
import os
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
import confluent.sortutil as sortutil
|
||||
|
||||
|
||||
def lookupdata(data, key):
|
||||
ret = data.get(key, {}).get('value', '')
|
||||
if ret is None:
|
||||
ret = ''
|
||||
return ret
|
||||
|
||||
def main():
|
||||
argparser = optparse.OptionParser(
|
||||
usage='''\n %prog noderange -o xcatnodes.def
|
||||
\n ''')
|
||||
argparser.add_option('-o', '--output',
|
||||
help='xCAT stanza file')
|
||||
argparser.add_option('-m', '--macs',
|
||||
help='xCAT macs.csv to write')
|
||||
(options, args) = argparser.parse_args()
|
||||
try:
|
||||
noderange = args[0]
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
if not (options.output or options.macs):
|
||||
sys.stderr.write('Output file must be specified by -o or -m\n')
|
||||
sys.exit(1)
|
||||
sess = client.Command()
|
||||
databynode = {}
|
||||
for res in sess.read('/noderange/{0}/attributes/all'.format(noderange)):
|
||||
for node in res.get('databynode', {}):
|
||||
if node not in databynode:
|
||||
databynode[node] = {}
|
||||
databynode[node].update(res['databynode'][node])
|
||||
if options.output:
|
||||
with open(options.output, 'w') as importfile:
|
||||
for node in sortutil.natural_sort(databynode):
|
||||
xcatattrs = collect_attribute_data(databynode, node)
|
||||
importfile.write('{0}:\n'.format(node))
|
||||
importfile.write(' objtype=node\n')
|
||||
importfile.write(' arch=x86_64\n')
|
||||
importfile.write(' netboot=xnba\n')
|
||||
importfile.write(' mgt=ipmi\n')
|
||||
for attr in xcatattrs:
|
||||
if xcatattrs[attr] is None:
|
||||
continue
|
||||
importfile.write(' {0}={1}\n'.format(attr,
|
||||
xcatattrs[attr]))
|
||||
importfile.write('\n')
|
||||
if options.macs:
|
||||
with open(options.macs, 'w') as importfile:
|
||||
macsv = csv.writer(importfile)
|
||||
macsv.writerow(['#node', 'mac'])
|
||||
for node in sortutil.natural_sort(databynode):
|
||||
xcatattrs = collect_attribute_data(databynode, node)
|
||||
macsv.writerow([node, xcatattrs['mac']])
|
||||
|
||||
|
||||
|
||||
def collect_attribute_data(databynode, node):
|
||||
data = databynode[node]
|
||||
xcatattrs = {}
|
||||
xcatattrs['groups'] = ','.join(data.get('groups', []))
|
||||
xcatattrs['bmc'] = data.get('hardwaremanagement.manager', {}).get(
|
||||
'value', None)
|
||||
if xcatattrs['bmc']:
|
||||
xcatattrs['bmc'] = xcatattrs['bmc'].split('/', 1)[0]
|
||||
xcatattrs['mpa'] = data.get('enclosure.manager', {}).get(
|
||||
'value', None)
|
||||
xcatattrs['slotid'] = data.get('enclosure.bay', {}).get(
|
||||
'value', None)
|
||||
gotmac = False
|
||||
for key in data:
|
||||
if key.startswith('net.') and 'hwaddr' in key:
|
||||
currmac = data[key].get('value', None)
|
||||
if currmac:
|
||||
if gotmac:
|
||||
sys.stderr.write(
|
||||
'Ignoring {0} and using only {1} for mac, '
|
||||
'multiple macs not supported by '
|
||||
'confluent2xcat\n'.format(key, gotmac))
|
||||
continue
|
||||
gotmac = key
|
||||
xcatattrs['mac'] = currmac
|
||||
return xcatattrs
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,61 +0,0 @@
|
||||
#!/usr/bin/python
|
||||
# This will take a given directory and make a 'big floppy image'
|
||||
# out of it, suitable for nodemedia upload.
|
||||
|
||||
import fcntl
|
||||
import glob
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
def create_image(directory, image, label=None):
|
||||
ents = 0
|
||||
datasz = 512
|
||||
for dir in os.walk(sys.argv[1]):
|
||||
ents += 1
|
||||
for filen in dir[2]:
|
||||
ents += 1
|
||||
filename = os.path.join(dir[0], filen)
|
||||
currsz = os.path.getsize(filename)
|
||||
# assuming up to 65k cluster
|
||||
currsz = (currsz // 512 +1) * 512
|
||||
datasz += currsz
|
||||
datasz += ents * 32768
|
||||
datasz = datasz // 16384 + 1
|
||||
with open(image, 'wb') as imgfile:
|
||||
imgfile.seek(datasz * 16384 - 1)
|
||||
imgfile.write(b'\x00')
|
||||
if label:
|
||||
subprocess.check_call(['mformat', '-i', image, '-v', label,
|
||||
'-r', '16', '-d', '1', '-t', str(datasz),
|
||||
'-s', '16','-h', '2', '::'])
|
||||
else:
|
||||
subprocess.check_call(['mformat', '-i', image, '-r', '16', '-d', '1', '-t',
|
||||
str(datasz), '-s', '16','-h', '2', '::'])
|
||||
# Some clustered filesystems will have the lock from mformat
|
||||
# linger after close (mformat doesn't unlock)
|
||||
# do a blocking wait for shared lock and then explicitly
|
||||
# unlock between calls to mtools
|
||||
with open(image, 'rb') as imgfile:
|
||||
fcntl.flock(imgfile.fileno(), fcntl.LOCK_SH)
|
||||
fcntl.flock(imgfile.fileno(), fcntl.LOCK_UN)
|
||||
cpycmd = ['mcopy', '-i', image, '-s']
|
||||
cpycmd.extend(glob.glob('{0}/*'.format(directory)))
|
||||
cpycmd.append('::')
|
||||
subprocess.check_call(cpycmd)
|
||||
# though not necessary for us, make sure dir2img doesn't have a lingering
|
||||
# flock from mcopy for any subsequent commands
|
||||
with open(image, 'rb') as imgfile:
|
||||
fcntl.flock(imgfile.fileno(), fcntl.LOCK_SH)
|
||||
fcntl.flock(imgfile.fileno(), fcntl.LOCK_UN)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) < 3:
|
||||
sys.stderr.write("Usage: {0} <directory> <imagefile>".format(
|
||||
sys.argv[0]))
|
||||
sys.exit(1)
|
||||
label = None
|
||||
if len(sys.argv) > 3:
|
||||
label = sys.argv[3]
|
||||
create_image(sys.argv[1], sys.argv[2], label)
|
||||
@@ -1,166 +0,0 @@
|
||||
#!/usr/bin/python3
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2016-2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from collections import deque
|
||||
import optparse
|
||||
import os
|
||||
import select
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
pass
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
import confluent.sortutil as sortutil
|
||||
devnull = None
|
||||
|
||||
def run():
|
||||
global devnull
|
||||
devnull = open(os.devnull, 'rb')
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog [options] noderange commandexpression",
|
||||
epilog="Expressions are the same as in attributes, e.g. "
|
||||
"'ipmitool -H {hardwaremanagement.manager}' will be expanded.")
|
||||
argparser.add_option('-f', '-c', '--count', type='int', default=168,
|
||||
help='Number of commands to run at a time')
|
||||
argparser.add_option('-k', '--security', action='store_true',
|
||||
help='Update SSH setup')
|
||||
argparser.add_option('-F', '--sync', action='store_true',
|
||||
help='Run the syncfiles associated with the currently completed OS profile on the noderange')
|
||||
argparser.add_option('-P', '--scripts',
|
||||
help='Re-run specified scripts, with full path under scripts, e.g. post.d/first,firstboot.d/second')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to run remote ssh command to, '
|
||||
'prompting if over the threshold')
|
||||
# among other things, FD_SETSIZE limits. Besides, spawning too many
|
||||
# processes can be unkind for the unaware on memory pressure and such...
|
||||
#argparser.disable_interspersed_args()
|
||||
(options, args) = argparser.parse_args()
|
||||
if len(args) < 1:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(args[0])
|
||||
concurrentprocs = options.count
|
||||
c = client.Command()
|
||||
|
||||
currprocs = 0
|
||||
all = set([])
|
||||
pipedesc = {}
|
||||
pendingexecs = deque()
|
||||
exitcode = 0
|
||||
|
||||
c.stop_if_noderange_over(args[0], options.maxnodes)
|
||||
nodemap = {}
|
||||
cmdparms = []
|
||||
nodes = []
|
||||
for res in c.read('/noderange/{0}/nodes/'.format(args[0])):
|
||||
if 'error' in res:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
exitcode |= res.get('errorcode', 1)
|
||||
break
|
||||
node = res['item']['href'][:-1]
|
||||
nodes.append(node)
|
||||
|
||||
cmdstorun = []
|
||||
if options.security:
|
||||
cmdstorun.append(['run_remote', 'setupssh'])
|
||||
if options.sync:
|
||||
cmdstorun.append(['run_remote_python', 'syncfileclient'])
|
||||
if options.scripts:
|
||||
for script in options.scripts.split(','):
|
||||
cmdstorun.append(['run_remote', script])
|
||||
if not cmdstorun:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
idxbynode = {}
|
||||
cmdvbase = ['bash', '/etc/confluent/functions']
|
||||
for sshnode in nodes:
|
||||
idxbynode[sshnode] = 1
|
||||
cmdv = ['ssh', sshnode] + cmdvbase + cmdstorun[0]
|
||||
if currprocs < concurrentprocs:
|
||||
currprocs += 1
|
||||
run_cmdv(node, cmdv, all, pipedesc)
|
||||
else:
|
||||
pendingexecs.append((node, cmdv))
|
||||
if not all or exitcode:
|
||||
sys.exit(exitcode)
|
||||
rdy, _, _ = select.select(all, [], [], 10)
|
||||
while all:
|
||||
pernodeout = {}
|
||||
for r in rdy:
|
||||
desc = pipedesc[r]
|
||||
node = desc['node']
|
||||
data = True
|
||||
while data and select.select([r], [], [], 0)[0]:
|
||||
data = r.readline()
|
||||
if data:
|
||||
if desc['type'] == 'stdout':
|
||||
if node not in pernodeout:
|
||||
pernodeout[node] = []
|
||||
pernodeout[node].append(data)
|
||||
else:
|
||||
data = client.stringify(data)
|
||||
sys.stderr.write('{0}: {1}'.format(node, data))
|
||||
sys.stderr.flush()
|
||||
else:
|
||||
pop = desc['popen']
|
||||
ret = pop.poll()
|
||||
if ret is not None:
|
||||
exitcode = exitcode | ret
|
||||
all.discard(r)
|
||||
r.close()
|
||||
if desc['type'] == 'stdout':
|
||||
if idxbynode[node] < len(cmdstorun):
|
||||
cmdv = ['ssh', sshnode] + cmdvbase + cmdstorun[idxbynode[node]]
|
||||
idxbynode[node] += 1
|
||||
run_cmdv(node, cmdv, all, pipedesc)
|
||||
elif pendingexecs:
|
||||
node, cmdv = pendingexecs.popleft()
|
||||
run_cmdv(node, cmdv, all, pipedesc)
|
||||
for node in sortutil.natural_sort(pernodeout):
|
||||
for line in pernodeout[node]:
|
||||
line = client.stringify(line)
|
||||
line = line.lstrip('\x08')
|
||||
sys.stdout.write('{0}: {1}'.format(node, line))
|
||||
sys.stdout.flush()
|
||||
if all:
|
||||
rdy, _, _ = select.select(all, [], [], 10)
|
||||
sys.exit(exitcode)
|
||||
|
||||
|
||||
def run_cmdv(node, cmdv, all, pipedesc):
|
||||
nopen = subprocess.Popen(
|
||||
cmdv, stdin=devnull, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
pipedesc[nopen.stdout] = {'node': node, 'popen': nopen,
|
||||
'type': 'stdout'}
|
||||
pipedesc[nopen.stderr] = {'node': node, 'popen': nopen,
|
||||
'type': 'stderr'}
|
||||
all.add(nopen.stdout)
|
||||
all.add(nopen.stderr)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python2
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
@@ -17,12 +17,10 @@
|
||||
|
||||
__author__ = 'alin37'
|
||||
|
||||
from getpass import getpass
|
||||
import optparse
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
import shlex
|
||||
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
@@ -37,25 +35,14 @@ if path.startswith('/opt'):
|
||||
import confluent.client as client
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage='''\n %prog [-b] noderange [list of attributes or 'all'] \
|
||||
usage='''\n %prog [-b] noderange [list of attributes] \
|
||||
\n %prog -c noderange <list of attributes> \
|
||||
\n %prog -e noderange <attribute names to set> \
|
||||
\n %prog noderange attribute1=value1 attribute2=value,...
|
||||
\n ''')
|
||||
argparser.add_option('-b', '--blame', action='store_true',
|
||||
help='Show information about how attributes inherited')
|
||||
argparser.add_option('-e', '--environment', action='store_true',
|
||||
help='Set attributes, but from environment variable of '
|
||||
'same name')
|
||||
argparser.add_option('-c', '--clear', action='store_true',
|
||||
help='Clear attributes')
|
||||
argparser.add_option('-p', '--prompt', action='store_true',
|
||||
help='Prompt for attribute values interactively')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Prompt if trying to set attributes on more '
|
||||
'than specified number of nodes')
|
||||
argparser.add_option('-s', '--set', dest='set', metavar='settings.batch',
|
||||
default=False, help='set attributes using a batch file')
|
||||
(options, args) = argparser.parse_args()
|
||||
|
||||
|
||||
@@ -68,7 +55,6 @@ try:
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
|
||||
@@ -76,25 +62,8 @@ exitcode = 0
|
||||
nodetype="noderange"
|
||||
|
||||
if len(args) > 1:
|
||||
if "=" in args[1] or options.clear or options.environment or options.prompt:
|
||||
if "=" in args[1] and options.clear:
|
||||
print("Can not clear and set at the same time!")
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
argassign = None
|
||||
if options.prompt:
|
||||
argassign = {}
|
||||
for arg in args[1:]:
|
||||
oneval = 1
|
||||
twoval = 2
|
||||
while oneval != twoval:
|
||||
oneval = getpass('Enter value for {0}: '.format(arg))
|
||||
twoval = getpass('Confirm value for {0}: '.format(arg))
|
||||
if oneval != twoval:
|
||||
print('Values did not match.')
|
||||
argassign[arg] = twoval
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
exitcode=client.updateattrib(session,args,nodetype, noderange, options, argassign)
|
||||
if "=" in args[1] or options.clear:
|
||||
exitcode=client.updateattrib(session,args,nodetype, noderange, options)
|
||||
try:
|
||||
# setting user output to what the user inputs
|
||||
if args[1] == 'all':
|
||||
@@ -108,27 +77,7 @@ if len(args) > 1:
|
||||
requestargs=args[1:]
|
||||
except:
|
||||
pass
|
||||
elif options.clear or options.environment or options.prompt:
|
||||
sys.stderr.write('Attribute names required with specified options\n')
|
||||
argparser.print_help()
|
||||
exitcode = 400
|
||||
|
||||
elif options.set:
|
||||
arglist = [noderange]
|
||||
showtype='current'
|
||||
argfile = open(options.set, 'r')
|
||||
argset = argfile.readline()
|
||||
while argset:
|
||||
try:
|
||||
argset = argset[:argset.index('#')]
|
||||
except ValueError:
|
||||
pass
|
||||
argset = argset.strip()
|
||||
if argset:
|
||||
arglist += shlex.split(argset)
|
||||
argset = argfile.readline()
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
exitcode=client.updateattrib(session,arglist,nodetype, noderange, options, None)
|
||||
if exitcode != 0:
|
||||
sys.exit(exitcode)
|
||||
|
||||
@@ -153,6 +102,6 @@ else:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
exitcode = 1
|
||||
else:
|
||||
print(res['item']['href'].replace('/', ''))
|
||||
print res['item']['href'].replace('/', '')
|
||||
|
||||
sys.exit(exitcode)
|
||||
sys.exit(exitcode)
|
||||
@@ -1,66 +0,0 @@
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015-2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
argparser = optparse.OptionParser(usage="Usage: %prog <noderange>")
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Number of nodes to affect before prompting for confirmation')
|
||||
(options, args) = argparser.parse_args()
|
||||
try:
|
||||
noderange = args[0]
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
|
||||
errorNodes = set([])
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
success = session.simple_noderange_command(noderange, 'configuration/management_controller/reset', 'reset', key='state', errnodes=errorNodes) # = 0 if successful
|
||||
|
||||
# Determine which nodes were successful and print them
|
||||
|
||||
allNodes = set([])
|
||||
|
||||
for node in session.read('/noderange/{0}/nodes/'.format(noderange)):
|
||||
if 'error' in node and success != 0:
|
||||
sys.exit(success)
|
||||
allNodes.add(node['item']['href'].replace("/", ""))
|
||||
|
||||
goodNodes = allNodes - errorNodes
|
||||
|
||||
for node in goodNodes:
|
||||
print(node + ": BMC Reset Successful")
|
||||
|
||||
|
||||
sys.exit(success)
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python2
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015 Lenovo
|
||||
@@ -35,17 +35,10 @@ argparser = optparse.OptionParser()
|
||||
argparser.add_option('-b', '--bios', dest='biosmode',
|
||||
action='store_true', default=False,
|
||||
help='Request BIOS style boot (rather than UEFI)')
|
||||
argparser.add_option('-u', '--uefi', dest='uefimode',
|
||||
action='store_true', default=False,
|
||||
help='Request UEFI style boot (rather than BIOS)')
|
||||
argparser.add_option('-p', '--persist', dest='persist', action='store_true',
|
||||
default=False,
|
||||
help='Request the boot device be persistent rather than '
|
||||
'one time')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to boot, '
|
||||
'prompting if over the threshold')
|
||||
|
||||
(options, args) = argparser.parse_args()
|
||||
|
||||
@@ -56,10 +49,9 @@ except IndexError:
|
||||
'Usage: {0} <noderange> [default|cd|network|setup|hd]\n'.format(
|
||||
sys.argv[0]))
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
bootdev = None
|
||||
if len(args) > 1:
|
||||
bootdev = args[1]
|
||||
if len(sys.argv) > 2:
|
||||
bootdev = sys.argv[2]
|
||||
if bootdev in ('net', 'pxe'):
|
||||
bootdev = 'network'
|
||||
session = client.Command()
|
||||
@@ -69,13 +61,11 @@ if options.biosmode:
|
||||
else:
|
||||
bootmode = 'uefi'
|
||||
|
||||
errnodes = set([])
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
rc = session.simple_noderange_command(noderange, '/boot/nextdevice', bootdev,
|
||||
bootmode=bootmode,
|
||||
persistent=options.persist,
|
||||
errnodes=errnodes)
|
||||
if errnodes:
|
||||
noderange = noderange + ',-(' + ','.join(errnodes) + ')'
|
||||
rc |= session.simple_noderange_command(noderange, '/power/state', 'boot')
|
||||
sys.exit(rc)
|
||||
persistent=options.persist)
|
||||
|
||||
if rc:
|
||||
sys.exit(rc)
|
||||
else:
|
||||
sys.exit(session.simple_noderange_command(noderange, '/power/state', 'boot'))
|
||||
@@ -1,319 +0,0 @@
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
import os
|
||||
import signal
|
||||
import optparse
|
||||
import shlex
|
||||
import sys
|
||||
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
pass
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
class NullOpt(object):
|
||||
blame = None
|
||||
clear = None
|
||||
|
||||
|
||||
def bailout(msg, code=1):
|
||||
sys.stderr.write(msg + '\n')
|
||||
sys.exit(code)
|
||||
|
||||
|
||||
argparser = optparse.OptionParser(usage="Usage: %prog [options] <noderange> [setting|setting=value]")
|
||||
argparser.add_option('-c', '--comparedefault', dest='comparedefault',
|
||||
action='store_true', default=False,
|
||||
help='Compare given settings to default or list settings '
|
||||
'that are non default')
|
||||
argparser.add_option('-b', '--batch', dest='batch', metavar='settings.batch',
|
||||
default=False, help='Provide settings in a batch file')
|
||||
argparser.add_option('-d', '--detail', dest='detail',
|
||||
action='store_true', default=False,
|
||||
help='Provide verbose information as available, such as '
|
||||
'help text and possible valid values')
|
||||
argparser.add_option('-e', '--extra', dest='extra',
|
||||
action='store_true', default=False,
|
||||
help='Access extra configuration. Extra configuration is generally '
|
||||
'reserved for unpopular or redundant options that may be slow to '
|
||||
'read. Notably the IMM category on Lenovo settings is considered '
|
||||
'to be extra configuration')
|
||||
argparser.add_option('-x', '--exclude', dest='exclude',
|
||||
action='store_true', default=False,
|
||||
help='Treat positional arguments as items to not '
|
||||
'examine, compare, or restore default')
|
||||
argparser.add_option('-a', '--advanced', dest='advanced',
|
||||
action='store_true', default=False,
|
||||
help='Include advanced settings, which are normally not '
|
||||
'intended to be used without direction from the '
|
||||
'relevant server vendor.')
|
||||
argparser.add_option('-r', '--restoredefault', default=False,
|
||||
dest='restoredefault', metavar="COMPONENT",
|
||||
help='Restore the configuration of the node '
|
||||
'to factory default for given component. '
|
||||
'Currently only uefi is supported')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to configure, '
|
||||
'prompting if over the threshold')
|
||||
(options, args) = argparser.parse_args()
|
||||
|
||||
cfgpaths = {
|
||||
'bmc.ipv4_address': (
|
||||
'configuration/management_controller/net_interfaces/management',
|
||||
'ipv4_address'),
|
||||
'bmc.ipv4_method': (
|
||||
'configuration/management_controller/net_interfaces/management',
|
||||
'ipv4_configuration'),
|
||||
'bmc.ipv4_gateway': (
|
||||
'configuration/management_controller/net_interfaces/management',
|
||||
'ipv4_gateway'),
|
||||
'bmc.static_ipv6_addresses': (
|
||||
'configuration/management_controller/net_interfaces/management',
|
||||
'static_v6_addresses'),
|
||||
'bmc.static_ipv6_gateway': (
|
||||
'configuration/management_controller/net_interfaces/management',
|
||||
'static_v6_gateway'),
|
||||
'bmc.hostname': (
|
||||
'configuration/management_controller/hostname', 'hostname'),
|
||||
}
|
||||
|
||||
autodeps = {
|
||||
'bmc.ipv4_address': (('bmc.ipv4_method', 'static'),)
|
||||
}
|
||||
|
||||
try:
|
||||
noderange = args[0]
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
setmode = None
|
||||
assignment = {}
|
||||
queryparms = {}
|
||||
printsys = []
|
||||
printbmc = []
|
||||
printextbmc = []
|
||||
printallbmc = False
|
||||
setsys = {}
|
||||
forceset = False
|
||||
needval = None
|
||||
|
||||
if len(args) == 1 or options.exclude:
|
||||
if not options.exclude:
|
||||
printsys = 'all'
|
||||
for candidate in cfgpaths:
|
||||
path, attrib = cfgpaths[candidate]
|
||||
path = '/noderange/{0}/{1}'.format(noderange, path)
|
||||
if path not in queryparms:
|
||||
queryparms[path] = {}
|
||||
queryparms[path][attrib] = candidate
|
||||
|
||||
|
||||
def _assign_value():
|
||||
if key not in cfgpaths:
|
||||
setsys[key] = value
|
||||
for depkey, depval in autodeps.get(key, []):
|
||||
assignment[depkey] = depval
|
||||
assignment[key] = value
|
||||
|
||||
|
||||
def parse_config_line(arguments, single=False):
|
||||
global setmode, printallbmc, forceset, key, value, needval, candidate, path, attrib
|
||||
for pidx in range(0, len(arguments)):
|
||||
param = arguments[pidx]
|
||||
if param == 'show':
|
||||
continue # forgive muscle memory of pasu users
|
||||
if param == 'set':
|
||||
setmode = True
|
||||
forceset = True
|
||||
continue
|
||||
if needval:
|
||||
key = needval
|
||||
needval = None
|
||||
if single:
|
||||
value = ' '.join(arguments[pidx:])
|
||||
_assign_value()
|
||||
break
|
||||
else:
|
||||
value = param
|
||||
_assign_value()
|
||||
continue
|
||||
if '=' in param or param[-1] == ':' or forceset:
|
||||
if setmode is None:
|
||||
setmode = True
|
||||
if setmode != True:
|
||||
bailout('Cannot do set and query in same command: Query detected but "{0}" appears to be set'.format(param))
|
||||
if '=' in param:
|
||||
key, _, value = param.partition('=')
|
||||
_assign_value()
|
||||
elif param[-1] == ':':
|
||||
needval = param[:-1]
|
||||
else:
|
||||
needval = param
|
||||
else:
|
||||
if setmode is None:
|
||||
setmode = False
|
||||
if setmode != False:
|
||||
bailout('Cannot do set and query in same command: Set mode detected but "{0}" appears to be a query'.format(param))
|
||||
if '.' not in param:
|
||||
if param == 'bmc':
|
||||
printallbmc = True
|
||||
matchedparms = False
|
||||
for candidate in cfgpaths:
|
||||
if candidate.startswith('{0}.'.format(param)):
|
||||
matchedparms = True
|
||||
if not options.exclude:
|
||||
path, attrib = cfgpaths[candidate]
|
||||
path = '/noderange/{0}/{1}'.format(noderange, path)
|
||||
if path not in queryparms:
|
||||
queryparms[path] = {}
|
||||
queryparms[path][attrib] = candidate
|
||||
else:
|
||||
try:
|
||||
del queryparms[path]
|
||||
except KeyError:
|
||||
pass
|
||||
if param.lower() == 'imm':
|
||||
printextbmc.append(param)
|
||||
options.extra = True
|
||||
elif not matchedparms:
|
||||
printsys.append(param)
|
||||
elif param not in cfgpaths:
|
||||
if param.startswith('bmc.'):
|
||||
printbmc.append(param.replace('bmc.', ''))
|
||||
elif param.lower().startswith('imm'):
|
||||
options.extra = True
|
||||
printextbmc.append(param)
|
||||
else:
|
||||
printsys.append(param)
|
||||
else:
|
||||
path, attrib = cfgpaths[param]
|
||||
path = '/noderange/{0}/{1}'.format(noderange, path)
|
||||
if path not in queryparms:
|
||||
queryparms[path] = {}
|
||||
queryparms[path][attrib] = param
|
||||
|
||||
if options.batch:
|
||||
printsys = []
|
||||
argfile = open(options.batch, 'r')
|
||||
argset = argfile.readline()
|
||||
while argset:
|
||||
try:
|
||||
argset = argset[:argset.index('#')]
|
||||
except ValueError:
|
||||
pass
|
||||
argset = argset.strip()
|
||||
if argset:
|
||||
parse_config_line(shlex.split(argset), single=True)
|
||||
argset = argfile.readline()
|
||||
else:
|
||||
parse_config_line(args[1:])
|
||||
session = client.Command()
|
||||
rcode = 0
|
||||
if options.restoredefault:
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
if options.restoredefault.lower() in (
|
||||
'sys', 'system', 'uefi', 'bios'):
|
||||
for fr in session.update(
|
||||
'/noderange/{0}/configuration/system/clear'.format(noderange),
|
||||
{'clear': True}):
|
||||
rcode |= client.printerror(fr)
|
||||
sys.exit(rcode)
|
||||
elif options.restoredefault.lower() in (
|
||||
'bmc', 'imm', 'xcc'):
|
||||
for fr in session.update(
|
||||
'/noderange/{0}/configuration/management_controller/clear'.format(noderange),
|
||||
{'clear': True}):
|
||||
rcode |= client.printerror(fr)
|
||||
sys.exit(rcode)
|
||||
else:
|
||||
sys.stderr.write(
|
||||
'Unrecognized component to restore defaults: {0}\n'.format(
|
||||
options.restoredefault))
|
||||
sys.exit(1)
|
||||
if setmode:
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
if options.exclude:
|
||||
sys.stderr.write('Cannot use exclude and assign at the same time\n')
|
||||
sys.exit(1)
|
||||
updatebypath = {}
|
||||
attrnamebypath = {}
|
||||
for key in assignment:
|
||||
if key not in cfgpaths:
|
||||
if key.startswith('bmc.'):
|
||||
path = 'configuration/management_controller/extended/all'
|
||||
attrib = key.replace('bmc.', '')
|
||||
else:
|
||||
path = 'configuration/system/all'
|
||||
attrib = key
|
||||
else:
|
||||
path, attrib = cfgpaths[key]
|
||||
if path not in updatebypath:
|
||||
updatebypath[path] = {}
|
||||
attrnamebypath[path] = {}
|
||||
updatebypath[path][attrib] = assignment[key]
|
||||
attrnamebypath[path][attrib] = key
|
||||
# well, we want to expand things..
|
||||
# check ipv4, if requested change method to static
|
||||
for path in updatebypath:
|
||||
for fr in session.update('/noderange/{0}/{1}'.format(noderange, path),
|
||||
updatebypath[path]):
|
||||
rcode |= client.printerror(fr)
|
||||
for node in fr.get('databynode', []):
|
||||
r = fr['databynode'][node]
|
||||
if 'value' not in r:
|
||||
continue
|
||||
keyval = r['value']
|
||||
key, val = keyval.split('=')
|
||||
if key in attrnamebypath[path]:
|
||||
key = attrnamebypath[path][key]
|
||||
print('{0}: {1}: {2}'.format(node, key, val))
|
||||
else:
|
||||
for path in queryparms:
|
||||
if options.comparedefault:
|
||||
continue
|
||||
rcode |= client.print_attrib_path(path, session, list(queryparms[path]),
|
||||
NullOpt(), queryparms[path])
|
||||
if printsys == 'all' or printextbmc or printbmc or printallbmc:
|
||||
if printbmc or not printextbmc:
|
||||
rcode |= client.print_attrib_path(
|
||||
'/noderange/{0}/configuration/management_controller/extended/all'.format(noderange),
|
||||
session, printbmc, options, attrprefix='bmc.')
|
||||
if options.extra:
|
||||
rcode |= client.print_attrib_path(
|
||||
'/noderange/{0}/configuration/management_controller/extended/extra'.format(noderange),
|
||||
session, printextbmc, options)
|
||||
if printsys or options.exclude:
|
||||
if printsys == 'all':
|
||||
printsys = []
|
||||
if (options.comparedefault or printsys == []) and not options.advanced:
|
||||
path = '/noderange/{0}/configuration/system/all'.format(noderange)
|
||||
else:
|
||||
path = '/noderange/{0}/configuration/system/advanced'.format(
|
||||
noderange)
|
||||
rcode = client.print_attrib_path(path, session, printsys,
|
||||
options)
|
||||
sys.exit(rcode)
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/libexec/platform-python
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015 Lenovo
|
||||
@@ -17,314 +17,18 @@
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
import confluent.sortutil as sortutil
|
||||
import confluent.logreader as logreader
|
||||
import time
|
||||
import socket
|
||||
import re
|
||||
|
||||
confettypath = os.path.join(os.path.dirname(sys.argv[0]), 'confetty')
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog [options] <noderange> [kill][-- [passthroughoptions]]",
|
||||
usage="Usage: %prog [options] node",
|
||||
epilog="Command sequences are available while connected to a console, hit "
|
||||
"ctrl-'e', then release ctrl, then 'c', then '?' for a full list. "
|
||||
"For example, ctrl-'e', then 'c', then '.' will exit the current "
|
||||
"console")
|
||||
argparser.add_option('-t', '--tile', action='store_true', default=False,
|
||||
help='Tile console windows in the terminal')
|
||||
argparser.add_option('-l', '--log', action='store_true', default=False,
|
||||
help='Enter log replay mode instead of showing a live console')
|
||||
|
||||
argparser.add_option('-T', '--Timestamp', action='store_true', default=False,
|
||||
help= 'Dump log in stdout with timestamps')
|
||||
|
||||
argparser.add_option('-w','--windowed', action='store_true', default=False,
|
||||
help='Open terminal windows for each node. The '
|
||||
'environment variable NODECONSOLE_WINDOWED_COMMAND '
|
||||
'should be set, which should be a text string corresponding '
|
||||
'to a command that can be used to open a windowed console,'
|
||||
' omitting the "nodeconsole <noderange>" part of the '
|
||||
'command, for example, to open a set of consoles for a '
|
||||
'range of nodes in separate xterm windows, set '
|
||||
'NODECONSOLE_WINDOWED_COMMAND to "xterm -e". To open a '
|
||||
'set of consoles for a range of nodes in separate '
|
||||
'GNOME Terminal windows with a size of 100 columns and '
|
||||
'31 rows, set NODECONSOLE_WINDOWED_COMMAND '
|
||||
'to "gnome-terminal --geometry 100x31 --" or in a WSL '
|
||||
'environment, to open a set of consoles for a range of '
|
||||
'nodes in separate Windows Terminal windows, with the '
|
||||
'title set for each node, set NODECONSOLE_WINDOWED_COMMAND'
|
||||
' to "wt.exe wsl.exe -d AlmaLinux-8 '
|
||||
'--shell-type login". If the NODECONSOLE_WINDOWED_COMMAND '
|
||||
'environment variable isn\'t set, xterm will be used by'
|
||||
'default.')
|
||||
|
||||
(options, args) = argparser.parse_args()
|
||||
|
||||
pass_through_args = []
|
||||
killcon = False
|
||||
try:
|
||||
noderange = args[0]
|
||||
if len(args) > 1:
|
||||
if args[1] == 'kill':
|
||||
killcon = True
|
||||
pass_through_args = args[1:]
|
||||
args = args[:1]
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
if len(args) != 1:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
if options.log:
|
||||
logname = args[0]
|
||||
if not os.path.exists(logname) and logname[0] != '/':
|
||||
logname = os.path.join('/var/log/confluent/consoles', logname)
|
||||
if not os.path.exists(logname):
|
||||
sys.stderr.write('Unable to locate {0} on local system\n'.format(logname))
|
||||
sys.exit(1)
|
||||
logreader.replay_to_console(logname)
|
||||
sys.exit(0)
|
||||
|
||||
if options.Timestamp:
|
||||
logname = args[0]
|
||||
if not os.path.exists(logname) and logname[0] != '/':
|
||||
logname = os.path.join('/var/log/confluent/consoles', logname)
|
||||
if not os.path.exists(logname):
|
||||
sys.stderr.write('Unable to locate {0} on local system\n'.format(logname))
|
||||
sys.exit(1)
|
||||
logreader.dump_to_console(logname)
|
||||
sys.exit(0)
|
||||
|
||||
def kill(noderange):
|
||||
sess = client.Command()
|
||||
envstring=os.environ.get('NODECONSOLE_WINDOWED_COMMAND')
|
||||
if not envstring:
|
||||
envstring = 'xterm'
|
||||
|
||||
nodes = []
|
||||
for res in sess.read('/noderange/{0}/nodes/'.format(args[0])):
|
||||
node = res.get('item', {}).get('href', '/').replace('/', '')
|
||||
if not node:
|
||||
sys.stderr.write(res.get('error', repr(res)) + '\n')
|
||||
sys.exit(1)
|
||||
nodes.append(node)
|
||||
|
||||
for node in nodes:
|
||||
s=socket.socket(socket.AF_UNIX)
|
||||
winid=None
|
||||
try:
|
||||
win=subprocess.Popen(['xwininfo', '-tree', '-root'], stdout=subprocess.PIPE)
|
||||
wintr=win.communicate()[0]
|
||||
for line in wintr.decode('utf-8').split('\n'):
|
||||
if 'console: {0}'.format(node) in line or 'confetty' in line:
|
||||
win_obj = [ele for ele in line.split(' ') if ele.strip()]
|
||||
winid = win_obj[0]
|
||||
except:
|
||||
print("Error: cannot retrieve window id of node {}".format(node))
|
||||
|
||||
if winid:
|
||||
ps_data=subprocess.Popen(['xkill', '-id', winid ], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
def handle_geometry(envlist, sizegeometry, side_pad=0, top_pad=0, first=False):
|
||||
if '-geometry' in envlist:
|
||||
g_index = envlist.index('-geometry')
|
||||
elif '-g' in envlist:
|
||||
g_index = envlist.index('-g')
|
||||
else:
|
||||
g_index = 0
|
||||
if g_index:
|
||||
if first:
|
||||
envlist[g_index+1] = '{0}+{1}+{2}'.format(envlist[g_index+1],side_pad, top_pad)
|
||||
else:
|
||||
envlist[g_index+1] = '{0}+{1}+{2}'.format(sizegeometry,side_pad, top_pad)
|
||||
else:
|
||||
envlist.insert(1, '-geometry')
|
||||
envlist.insert(2, '{0}+{1}+{2}'.format(sizegeometry,side_pad, top_pad))
|
||||
g_index = 1
|
||||
return envlist
|
||||
|
||||
# add funcltionality to close/kill all open consoles
|
||||
if killcon:
|
||||
kill(noderange)
|
||||
|
||||
#added functionality for wcons
|
||||
if options.windowed:
|
||||
result=subprocess.Popen(['xwininfo', '-root'], stdout=subprocess.PIPE)
|
||||
rootinfo=result.communicate()[0]
|
||||
result.wait()
|
||||
for line in rootinfo.decode('utf-8').split('\n'):
|
||||
if 'Width' in line:
|
||||
screenwidth = int(line.split(':')[1])
|
||||
if 'Height' in line:
|
||||
screenheight = int(line.split(':')[1])
|
||||
|
||||
envstring=os.environ.get('NODECONSOLE_WINDOWED_COMMAND')
|
||||
if not envstring:
|
||||
sizegeometry='100x31'
|
||||
corrected_x, corrected_y = (13,84)
|
||||
envlist = handle_geometry(['xterm'] + pass_through_args + ['-e'],sizegeometry, first=True)
|
||||
#envlist=['xterm', '-bg', 'black', '-fg', 'white', '-geometry', '{sizegeometry}+0+0'.format(sizegeometry=sizegeometry), '-e']
|
||||
else:
|
||||
envlist=os.environ.get('NODECONSOLE_WINDOWED_COMMAND').split(' ')
|
||||
if envlist[0] == 'xterm':
|
||||
if '-geometry' in envlist:
|
||||
g_index = envlist.index('-geometry')
|
||||
elif '-g' in envlist:
|
||||
g_index = envlist.index('-g')
|
||||
else:
|
||||
g_index = 0
|
||||
if g_index:
|
||||
envlist[g_index+1] = envlist[g_index+1] + '+0+0'
|
||||
|
||||
else:
|
||||
envlist.insert(1, '-geometry')
|
||||
envlist.insert(2, '100x31+0+0')
|
||||
g_index = 1
|
||||
|
||||
nodes = []
|
||||
sess = client.Command()
|
||||
for res in sess.read('/noderange/{0}/nodes/'.format(args[0])):
|
||||
node = res.get('item', {}).get('href', '/').replace('/', '')
|
||||
if not node:
|
||||
sys.stderr.write(res.get('error', repr(res)) + '\n')
|
||||
sys.exit(1)
|
||||
nodes.append(node)
|
||||
|
||||
if options.tile and not envlist[0] == 'xterm':
|
||||
sys.stderr.write('[ERROR] UNSUPPORTED OPTIONS. \nWindowed and tiled consoles are only supported when using xterm \n')
|
||||
sys.exit(1)
|
||||
firstnode=nodes[0]
|
||||
nodes.pop(0)
|
||||
with open(os.devnull, 'wb') as devnull:
|
||||
xopen=subprocess.Popen(envlist + [confettypath, '-c', '/tmp/controlpath-{0}'.format(firstnode), '-m', '5', 'start', '/nodes/{0}/console/session'.format(firstnode) ] , stdin=devnull)
|
||||
time.sleep(2)
|
||||
s=socket.socket(socket.AF_UNIX)
|
||||
winid=''
|
||||
try:
|
||||
s.connect('/tmp/controlpath-{firstnode}'.format(firstnode=firstnode))
|
||||
s.recv(64)
|
||||
s.send(b'GETWINID')
|
||||
winid=s.recv(64).decode('utf-8')
|
||||
|
||||
except:
|
||||
time.sleep(2)
|
||||
# try to get id of first panel/xterm window using name
|
||||
win=subprocess.Popen(['xwininfo', '-tree', '-root'], stdout=subprocess.PIPE)
|
||||
wintr=win.communicate()[0]
|
||||
for line in wintr.decode('utf-8').split('\n'):
|
||||
if 'console: {firstnode}'.format(firstnode=firstnode) in line or 'confetty' in line:
|
||||
win_obj = [ele for ele in line.split(' ') if ele.strip()]
|
||||
winid = win_obj[0]
|
||||
if winid:
|
||||
firstnode_window=subprocess.Popen(['xwininfo', '-id', '{winid}'.format(winid=winid)], stdout=subprocess.PIPE)
|
||||
xinfo=firstnode_window.communicate()[0]
|
||||
xinfl = xinfo.decode('utf-8').split('\n')
|
||||
for line in xinfl:
|
||||
if 'Absolute upper-left X:' in line:
|
||||
side_pad = int(line.split(':')[1])
|
||||
elif 'Absolute upper-left Y:' in line:
|
||||
top_pad = int(line.split(':')[1])
|
||||
|
||||
elif 'Width:' in line:
|
||||
window_width = int(line.split(':')[1])
|
||||
elif 'Height' in line:
|
||||
window_height = int(line.split(':')[1])
|
||||
elif '-geometry' in line:
|
||||
l = re.split(' |x|-|\+', line)
|
||||
l_nosp = [ele for ele in l if ele.strip()]
|
||||
wmxo = int(l_nosp[1])
|
||||
wmyo = int(l_nosp[2])
|
||||
sizegeometry = str(wmxo) + 'x' + str(wmyo)
|
||||
else:
|
||||
pass
|
||||
|
||||
window_width += side_pad*2
|
||||
window_height += side_pad+top_pad
|
||||
screenwidth -= wmxo
|
||||
screenheight -= wmyo
|
||||
currx = window_width
|
||||
curry = 0
|
||||
maxcol = int(screenwidth/window_width)
|
||||
|
||||
for node in sortutil.natural_sort(nodes):
|
||||
if options.tile and envlist[0] == 'xterm':
|
||||
corrected_x = currx
|
||||
corrected_y = curry
|
||||
xgeometry = '{0}+{1}+{2}'.format(sizegeometry, corrected_x, corrected_y)
|
||||
currx += window_width
|
||||
if currx >= screenwidth:
|
||||
currx=0
|
||||
curry += window_height
|
||||
if curry > screenheight:
|
||||
curry =top_pad
|
||||
if not envstring:
|
||||
envlist= handle_geometry(envlist, sizegeometry, corrected_x, corrected_y)
|
||||
else:
|
||||
if g_index:
|
||||
envlist[g_index+1] = xgeometry
|
||||
elif envlist[0] == 'xterm':
|
||||
envlist=handle_geometry(envlist, sizegeometry, side_pad, top_pad)
|
||||
side_pad+=(side_pad+1)
|
||||
top_pad+=(top_pad+30)
|
||||
else:
|
||||
pass
|
||||
with open(os.devnull, 'wb') as devnull:
|
||||
xopen=subprocess.Popen(envlist + [confettypath, '-c', '/tmp/controlpath-{0}'.format(node), '-m', '5', 'start', '/nodes/{0}/console/session'.format(node)] , stdin=devnull)
|
||||
sys.exit(0)
|
||||
#end of wcons
|
||||
if options.tile:
|
||||
null = open('/dev/null', 'w')
|
||||
nodes = []
|
||||
sess = client.Command()
|
||||
for res in sess.read('/noderange/{0}/nodes/'.format(args[0])):
|
||||
node = res.get('item', {}).get('href', '/').replace('/', '')
|
||||
if not node:
|
||||
sys.stderr.write(res.get('error', repr(res)) + '\n')
|
||||
sys.exit(1)
|
||||
nodes.append(node)
|
||||
initial = True
|
||||
in_tmux = False
|
||||
pane = 0
|
||||
sessname = 'nodeconsole_{0}'.format(os.getpid())
|
||||
if os.environ.get("TMUX"):
|
||||
initial = False
|
||||
in_tmux = True
|
||||
subprocess.call(['tmux', 'rename-session', sessname])
|
||||
for node in sortutil.natural_sort(nodes):
|
||||
panename = '{0}:{1}'.format(sessname, pane)
|
||||
if initial:
|
||||
initial = False
|
||||
subprocess.call(
|
||||
['tmux', 'new-session', '-d', '-s',
|
||||
sessname, '-x', '800', '-y',
|
||||
'800', '{0} -m 5 start /nodes/{1}/console/session'.format(
|
||||
confettypath, node)])
|
||||
else:
|
||||
subprocess.call(['tmux', 'select-pane', '-t', sessname])
|
||||
subprocess.call(['tmux', 'set-option', '-t', panename, 'pane-border-status', 'top'], stderr=null)
|
||||
subprocess.call(
|
||||
['tmux', 'split', '-h', '-t', sessname,
|
||||
'{0} -m 5 start /nodes/{1}/console/session'.format(
|
||||
confettypath, node)])
|
||||
subprocess.call(['tmux', 'select-layout', '-t', sessname, 'tiled'], stdout=null)
|
||||
pane += 1
|
||||
subprocess.call(['tmux', 'select-pane', '-t', sessname])
|
||||
subprocess.call(['tmux', 'set-option', '-t', panename, 'pane-border-status', 'top'], stderr=null)
|
||||
if not in_tmux:
|
||||
os.execlp('tmux', 'tmux', 'attach', '-t', sessname)
|
||||
else:
|
||||
os.execl(confettypath, confettypath, 'start',
|
||||
'/nodes/{0}/console/session'.format(args[0]))
|
||||
os.execl(confettypath, confettypath, 'start',
|
||||
'/nodes/{0}/console/session'.format(args[0]))
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage='''\n %prog noderange attribute1=value1 attribute2=value,...
|
||||
\n ''')
|
||||
(options, args) = argparser.parse_args()
|
||||
requestargs=None
|
||||
try:
|
||||
noderange = args[0]
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
attribs = {'name': noderange}
|
||||
for arg in args[1:]:
|
||||
key, val = arg.split('=', 1)
|
||||
attribs[key] = val
|
||||
for r in session.create('/noderange/', attribs):
|
||||
if 'error' in r:
|
||||
sys.stderr.write(r['error'] + '\n')
|
||||
exitcode |= 1
|
||||
if 'created' in r:
|
||||
print('{0}: created'.format(r['created']))
|
||||
sys.exit(exitcode)
|
||||
@@ -1,174 +0,0 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
import confluent.sortutil as sortutil
|
||||
|
||||
def cleararm(nr, cli):
|
||||
nodes = set([])
|
||||
for rsp in cli.read('/noderange/{0}/attributes/current'.format(nr)):
|
||||
for node in rsp.get('databynode', {}):
|
||||
nodeinfo = rsp['databynode'][node]
|
||||
for attr in nodeinfo:
|
||||
if attr == 'deployment.apiarmed':
|
||||
curr = nodeinfo[attr].get('value', '')
|
||||
if curr == 'continuous':
|
||||
nodes.add(node)
|
||||
noderange = nr
|
||||
if nodes:
|
||||
noderange += ',-({0})'.format(','.join(nodes))
|
||||
for rsp in cli.update('/noderange/{0}/attributes/current'.format(noderange),
|
||||
{'deployment.apiarmed': ''}):
|
||||
pass
|
||||
|
||||
|
||||
def armonce(nr, cli):
|
||||
nodes = set([])
|
||||
for rsp in cli.read('/noderange/{0}/attributes/current'.format(nr)):
|
||||
for node in rsp.get('databynode', {}):
|
||||
nodeinfo = rsp['databynode'][node]
|
||||
for attr in nodeinfo:
|
||||
if attr == 'deployment.apiarmed':
|
||||
curr = nodeinfo[attr].get('value', '')
|
||||
if curr == 'continuous':
|
||||
nodes.add(node)
|
||||
noderange = nr
|
||||
if nodes:
|
||||
noderange += ',-({0})'.format(','.join(nodes))
|
||||
for rsp in cli.update('/noderange/{0}/attributes/current'.format(noderange),
|
||||
{'deployment.apiarmed': 'once'}):
|
||||
pass
|
||||
|
||||
|
||||
def setpending(nr, profile, cli):
|
||||
args = {'deployment.pendingprofile': profile, 'deployment.state': '', 'deployment.state_detail': ''}
|
||||
if not profile.startswith('genesis-'):
|
||||
args['deployment.stagedprofile'] = ''
|
||||
args['deployment.profile'] = ''
|
||||
for rsp in cli.update('/noderange/{0}/attributes/current'.format(nr),
|
||||
args):
|
||||
pass
|
||||
|
||||
|
||||
def clearpending(nr, cli):
|
||||
for rsp in cli.update('/noderange/{0}/attributes/current'.format(nr),
|
||||
{'deployment.pendingprofile': ''}):
|
||||
pass
|
||||
|
||||
def main(args):
|
||||
ap = argparse.ArgumentParser(description='Deploy OS to nodes')
|
||||
ap.add_argument('-c', '--clear', help='Clear any pending deployment action', action='store_true')
|
||||
ap.add_argument('-n', '--network', help='Initiate deployment over PXE/HTTP', action='store_true')
|
||||
ap.add_argument('-p', '--prepareonly', help='Prepare only, skip any interaction with a BMC associated with this deployment action', action='store_true')
|
||||
ap.add_argument('-m', '--maxnodes', help='Specifiy a maximum nodes to be deployed')
|
||||
ap.add_argument('noderange', help='Set of nodes to deploy')
|
||||
ap.add_argument('profile', nargs='?', help='Profile name to deploy')
|
||||
args, extra = ap.parse_known_args(args)
|
||||
if args.profile is None and len(extra) == 1:
|
||||
args.profile = extra[0]
|
||||
extra = extra[1:]
|
||||
if args.profile and not args.network:
|
||||
sys.stderr.write('-n is a required argument currently to perform an install, optionally with -p\n')
|
||||
return 1
|
||||
if not args.profile and args.network:
|
||||
sys.stderr.write('Both noderange and a profile name are required arguments to request a network deployment\n')
|
||||
return 1
|
||||
if extra:
|
||||
sys.stderr.write('Unrecognized arguments: ' + repr(extra) + '\n')
|
||||
c = client.Command()
|
||||
c.stop_if_noderange_over(args.noderange, args.maxnodes)
|
||||
errnodes = set([])
|
||||
for rsp in c.read('/noderange/{0}/nodes/'.format(args.noderange)):
|
||||
if 'error' in rsp:
|
||||
sys.stderr.write(rsp['error'] + '\n')
|
||||
sys.exit(1)
|
||||
if not args.clear and args.network and not args.prepareonly:
|
||||
rc = c.simple_noderange_command(args.noderange, '/boot/nextdevice', 'network',
|
||||
bootmode='uefi',
|
||||
persistent=False,
|
||||
errnodes=errnodes)
|
||||
if errnodes:
|
||||
sys.stderr.write(
|
||||
'Unable to set boot device for following nodes: {0}\n'.format(
|
||||
','.join(errnodes)))
|
||||
return 1
|
||||
rc |= c.simple_noderange_command(args.noderange, '/power/state', 'boot')
|
||||
if args.clear:
|
||||
cleararm(args.noderange, c)
|
||||
clearpending(args.noderange, c)
|
||||
elif args.profile:
|
||||
profnames = []
|
||||
for prof in c.read('/deployment/profiles/'):
|
||||
profname = prof.get('item', {}).get('href', None)
|
||||
if profname:
|
||||
profname = profname.replace('/', '')
|
||||
profnames.append(profname)
|
||||
if profname == args.profile:
|
||||
break
|
||||
else:
|
||||
sys.stderr.write('The specified profile "{}" is not an available profile\n'.format(args.profile))
|
||||
if profnames:
|
||||
sys.stderr.write('The following profiles are available:\n')
|
||||
for profname in profnames:
|
||||
sys.stderr.write(' ' + profname + '\n')
|
||||
else:
|
||||
sys.stderr.write('No deployment profiles available, try osdeploy fiimport or imgutil capture\n')
|
||||
sys.exit(1)
|
||||
armonce(args.noderange, c)
|
||||
setpending(args.noderange, args.profile, c)
|
||||
else:
|
||||
databynode = {}
|
||||
for r in c.read('/noderange/{0}/attributes/current'.format(args.noderange)):
|
||||
dbn = r.get('databynode', {})
|
||||
for node in dbn:
|
||||
if node not in databynode:
|
||||
databynode[node] = {}
|
||||
for attr in dbn[node]:
|
||||
if attr in ('deployment.pendingprofile', 'deployment.apiarmed', 'deployment.stagedprofile', 'deployment.profile', 'deployment.state', 'deployment.state_detail'):
|
||||
databynode[node][attr] = dbn[node][attr].get('value', '')
|
||||
for node in sortutil.natural_sort(databynode):
|
||||
profile = databynode[node].get('deployment.pendingprofile', '')
|
||||
if profile:
|
||||
profile = 'pending: {}'.format(profile)
|
||||
else:
|
||||
profile = databynode[node].get('deployment.stagedprofile', '')
|
||||
if profile:
|
||||
profile = 'staged: {}'.format(profile)
|
||||
else:
|
||||
profile = databynode[node].get('deployment.profile', '')
|
||||
if profile:
|
||||
profile = 'completed: {}'.format(profile)
|
||||
else:
|
||||
profile= 'No profile pending or applied'
|
||||
armed = databynode[node].get('deployment.apiarmed', '')
|
||||
if armed in ('once', 'continuous'):
|
||||
armed = ' (node authentication armed)'
|
||||
else:
|
||||
armed = ''
|
||||
stateinfo = ''
|
||||
deploymentstate = databynode[node].get('deployment.state', '')
|
||||
if deploymentstate:
|
||||
statedetails = databynode[node].get('deployment.state_detail', '')
|
||||
if statedetails:
|
||||
stateinfo = '{}: {}'.format(deploymentstate, statedetails)
|
||||
else:
|
||||
stateinfo = deploymentstate
|
||||
if stateinfo:
|
||||
print('{0}: {1} ({2})'.format(node, profile, stateinfo))
|
||||
else:
|
||||
print('{0}: {1}{2}'.format(node, profile, armed))
|
||||
sys.exit(0)
|
||||
if args.network and not args.prepareonly:
|
||||
return rc
|
||||
return 0
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main(sys.argv[1:]))
|
||||
@@ -1,424 +0,0 @@
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import csv
|
||||
import optparse
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
import confluent.sortutil as sortutil
|
||||
|
||||
defcolumns = ['Node', 'Model', 'Serial', 'UUID', 'Mac Address', 'Type',
|
||||
'Current IP Addresses']
|
||||
columnmapping = {
|
||||
'Node': 'nodename',
|
||||
'Model': 'modelnumber',
|
||||
'Model Name': 'modelname',
|
||||
'Serial': 'serialnumber',
|
||||
'UUID': 'uuid',
|
||||
'Type': 'types',
|
||||
'Current IP Addresses': 'ipaddrs',
|
||||
'IP': 'ipaddrs',
|
||||
'Bay': 'bay',
|
||||
'Mac Address': 'macs',
|
||||
'Mac': 'macs',
|
||||
'Switch': 'switch',
|
||||
'Port': 'port',
|
||||
'Advertised IP': 'otheripaddrs',
|
||||
'Other IP': 'otheripaddrs',
|
||||
}
|
||||
#TODO: add chassis uuid
|
||||
|
||||
def register_endpoint(options, session, addr):
|
||||
neednewline = False
|
||||
current = 0
|
||||
for rsp in session.update('/discovery/register', {'addresses': addr}):
|
||||
if 'count' in rsp:
|
||||
total = rsp['count']
|
||||
elif total > 1:
|
||||
current += 1
|
||||
sys.stdout.write('\x1b[2K\r{0:.1f}% Scanned'.format(100 * current / total))
|
||||
sys.stdout.flush()
|
||||
neednewline = True
|
||||
if 'created' in rsp:
|
||||
if neednewline:
|
||||
print('')
|
||||
neednewline = False
|
||||
print('Registered: {0}'.format(rsp['created']))
|
||||
if neednewline:
|
||||
print('')
|
||||
|
||||
def subscribe_discovery(options, session, subscribe, targ):
|
||||
keyn = 'subscribe' if subscribe else 'unsubscribe'
|
||||
payload = {keyn: targ}
|
||||
if subscribe:
|
||||
for rsp in session.update('/discovery/subscriptions/{0}'.format(targ), payload):
|
||||
if 'status' in rsp:
|
||||
print(rsp['status'])
|
||||
else:
|
||||
for rsp in session.delete('/discovery/subscriptions/{0}'.format(targ)):
|
||||
if 'status' in rsp:
|
||||
print(rsp['status'])
|
||||
|
||||
def print_disco(options, session, currmac, outhandler, columns):
|
||||
procinfo = {}
|
||||
for tmpinfo in session.read('/discovery/by-mac/{0}'.format(currmac)):
|
||||
|
||||
procinfo.update(tmpinfo)
|
||||
if 'Switch' in columns or 'Port' in columns:
|
||||
if 'switch' in procinfo:
|
||||
procinfo['port'] = procinfo['switchport']
|
||||
else:
|
||||
for tmpinfo in session.read(
|
||||
'/networking/macs/by-mac/{0}'.format(currmac)):
|
||||
if 'ports' in tmpinfo:
|
||||
# The api sorts so that the most specific available value
|
||||
# is last
|
||||
procinfo.update(tmpinfo['ports'][-1])
|
||||
record = []
|
||||
for col in columns:
|
||||
rawcol = columnmapping[col]
|
||||
rawval = procinfo.get(rawcol, '')
|
||||
if isinstance(rawval, list):
|
||||
record.append(','.join(rawval))
|
||||
else:
|
||||
record.append(str(rawval))
|
||||
outhandler.add_row(record)
|
||||
|
||||
|
||||
def process_header(header):
|
||||
# normalize likely header titles
|
||||
fields = []
|
||||
broken = False
|
||||
for datum in header:
|
||||
datum = datum.lower()
|
||||
if datum.startswith('node') or datum.startswith('name'):
|
||||
fields.append('node')
|
||||
elif datum in ('nodegroup', 'nodegroups', 'group', 'groups'):
|
||||
fields.append('groups')
|
||||
elif datum.startswith('mac') or datum.startswith('ether'):
|
||||
fields.append('mac')
|
||||
elif datum.startswith('serial') or datum in ('sn', 's/n'):
|
||||
fields.append('serial')
|
||||
elif datum == 'uuid':
|
||||
fields.append('uuid')
|
||||
elif datum in ('bmc', 'imm', 'xcc'):
|
||||
fields.append('hardwaremanagement.manager')
|
||||
elif datum in ('bmc gateway', 'xcc gateway', 'imm gateway'):
|
||||
fields.append('net.bmc.ipv4_gateway')
|
||||
elif datum in ('bmc_gateway', 'xcc_gateway', 'imm_gateway'):
|
||||
fields.append('net.bmc.ipv4_gateway')
|
||||
elif datum in ('bmcuser', 'username', 'user'):
|
||||
fields.append('secret.hardwaremanagementuser')
|
||||
elif datum in ('bmcpass', 'password', 'pass'):
|
||||
fields.append('secret.hardwaremanagementpassword')
|
||||
else:
|
||||
print("Unrecognized column name {0}".format(datum))
|
||||
broken = True
|
||||
if broken:
|
||||
sys.exit(1)
|
||||
return tuple(fields)
|
||||
|
||||
|
||||
def datum_complete(datum):
|
||||
if 'node' not in datum or not datum['node']:
|
||||
sys.stderr.write('Nodename is a required field')
|
||||
return False
|
||||
provided = set(datum)
|
||||
required = set(['serial', 'uuid', 'mac'])
|
||||
for field in provided & required:
|
||||
if datum[field]:
|
||||
break
|
||||
else:
|
||||
sys.stderr.write('One of the fields "Serial Number", "UUID", or '
|
||||
'"MAC Address" must be provided')
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
searchkeys = set(['mac', 'serial', 'uuid'])
|
||||
|
||||
|
||||
def search_record(datum, options, session):
|
||||
for searchkey in searchkeys:
|
||||
options.__dict__[searchkey] = None
|
||||
for searchkey in searchkeys & set(datum):
|
||||
options.__dict__[searchkey] = datum[searchkey]
|
||||
return list(list_matching_macs(options, session))
|
||||
|
||||
|
||||
def datum_to_attrib(datum):
|
||||
for key in ('serial', 'uuid', 'mac'):
|
||||
try:
|
||||
del datum[key]
|
||||
except KeyError:
|
||||
pass
|
||||
datum['name'] = datum['node']
|
||||
del datum['node']
|
||||
return datum
|
||||
|
||||
unique_fields = frozenset(['serial', 'mac', 'uuid'])
|
||||
|
||||
def import_csv(options, session):
|
||||
nodedata = []
|
||||
unique_data = {}
|
||||
exitcode = 0
|
||||
with open(options.importfile, 'r') as datasrc:
|
||||
records = csv.reader(datasrc)
|
||||
fields = process_header(next(records))
|
||||
for field in fields:
|
||||
if field in unique_fields:
|
||||
unique_data[field] = set([])
|
||||
broken = False
|
||||
for record in records:
|
||||
currfields = list(fields)
|
||||
nodedatum = {}
|
||||
for datum in record:
|
||||
currfield = currfields.pop(0)
|
||||
if currfield in unique_fields:
|
||||
if datum in unique_data[currfield]:
|
||||
sys.stderr.write(
|
||||
"Import contains duplicate values "
|
||||
"({0} with value {1}\n".format(currfield, datum)
|
||||
)
|
||||
sys.exit(1)
|
||||
unique_data[currfield].add(datum)
|
||||
nodedatum[currfield] = datum
|
||||
if not datum_complete(nodedatum):
|
||||
sys.exit(1)
|
||||
if not search_record(nodedatum, options, session) and not broken:
|
||||
blocking_scan(session)
|
||||
if not search_record(nodedatum, options, session):
|
||||
sys.stderr.write(
|
||||
"Could not match the following data: " +
|
||||
repr(nodedatum) + '\n')
|
||||
broken = True
|
||||
nodedata.append(nodedatum)
|
||||
if broken:
|
||||
sys.exit(1)
|
||||
for datum in nodedata:
|
||||
maclist = search_record(datum, options, session)
|
||||
datum = datum_to_attrib(datum)
|
||||
nodename = datum['name']
|
||||
for res in session.create('/nodes/', datum):
|
||||
if 'error' in res:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
exitcode |= res.get('errorcode', 1)
|
||||
continue
|
||||
elif 'created' in res:
|
||||
print('Defined ' + res['created'])
|
||||
else:
|
||||
print(repr(res))
|
||||
for mac in maclist:
|
||||
for res in session.update('/discovery/by-mac/{0}'.format(mac),
|
||||
{'node': nodename}):
|
||||
if 'error' in res:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
exitcode |= res.get('errorcode', 1)
|
||||
continue
|
||||
elif 'assigned' in res:
|
||||
print('Discovered ' + res['assigned'])
|
||||
else:
|
||||
print(repr(res))
|
||||
if exitcode:
|
||||
sys.exit(exitcode)
|
||||
|
||||
|
||||
def list_discovery(options, session):
|
||||
orderby = None
|
||||
if options.fields:
|
||||
columns = []
|
||||
for field in options.fields.split(','):
|
||||
for cdt in columnmapping:
|
||||
if cdt.lower().replace(
|
||||
' ', '') == field.lower().replace(' ', ''):
|
||||
columns.append(cdt)
|
||||
else:
|
||||
columns = defcolumns
|
||||
if options.order:
|
||||
for field in columns:
|
||||
if options.order.lower() == field.lower():
|
||||
orderby = field
|
||||
outhandler = client.Tabulator(columns)
|
||||
for mac in list_matching_macs(options, session):
|
||||
print_disco(options, session, mac, outhandler, columns)
|
||||
if options.csv:
|
||||
outhandler.write_csv(sys.stdout, orderby)
|
||||
else:
|
||||
for row in outhandler.get_table(orderby):
|
||||
print(row)
|
||||
|
||||
def clear_discovery(options, session):
|
||||
for mac in list_matching_macs(options, session):
|
||||
for res in session.delete('/discovery/by-mac/{0}'.format(mac)):
|
||||
if 'deleted' in res:
|
||||
print('Cleared info for {0}'.format(res['deleted']))
|
||||
else:
|
||||
print(repr(res))
|
||||
|
||||
def list_matching_macs(options, session, node=None, checknode=True):
|
||||
path = '/discovery/'
|
||||
if node:
|
||||
path += 'by-node/{0}/'.format(node)
|
||||
elif checknode and options.node:
|
||||
path += 'by-node/{0}/'.format(options.node)
|
||||
if options.model:
|
||||
path += 'by-model/{0}/'.format(options.model)
|
||||
if options.serial:
|
||||
path += 'by-serial/{0}/'.format(options.serial)
|
||||
if options.uuid:
|
||||
path += 'by-uuid/{0}/'.format(options.uuid)
|
||||
if options.type:
|
||||
path += 'by-type/{0}/'.format(options.type)
|
||||
if options.state:
|
||||
if options.state == 'unknown':
|
||||
options.state = 'unidentified'
|
||||
path += 'by-state/{0}/'.format(options.state).lower()
|
||||
if options.mac:
|
||||
path += 'by-mac/{0}'.format(options.mac)
|
||||
result = list(session.read(path))[0]
|
||||
if 'error' in result:
|
||||
return []
|
||||
return [options.mac.replace(':', '-')]
|
||||
else:
|
||||
path += 'by-mac/'
|
||||
ret = []
|
||||
for x in session.read(path):
|
||||
if 'item' in x and 'href' in x['item']:
|
||||
ret.append(x['item']['href'])
|
||||
return ret
|
||||
|
||||
def assign_discovery(options, session, needid=True):
|
||||
abort = False
|
||||
if options.importfile:
|
||||
return import_csv(options, session)
|
||||
if not options.node:
|
||||
sys.stderr.write("Node (-n) must be specified for assignment\n")
|
||||
abort = True
|
||||
if needid and not (options.serial or options.uuid or options.mac):
|
||||
sys.stderr.write(
|
||||
"UUID (-u), serial (-s), or ether address (-e) required for "
|
||||
"assignment\n")
|
||||
abort = True
|
||||
if abort:
|
||||
sys.exit(1)
|
||||
matches = list_matching_macs(options, session, None if needid else options.node, False)
|
||||
if not matches:
|
||||
# Do a rescan to catch missing requested data
|
||||
blocking_scan(session)
|
||||
matches = list_matching_macs(options, session, None if needid else options.node, False)
|
||||
if not matches:
|
||||
sys.stderr.write("No matching discovery candidates found\n")
|
||||
sys.exit(1)
|
||||
exitcode = 0
|
||||
for res in session.update('/discovery/by-mac/{0}'.format(matches[0]),
|
||||
{'node': options.node}):
|
||||
if 'assigned' in res:
|
||||
print('Assigned: {0}'.format(res['assigned']))
|
||||
elif 'error' in res:
|
||||
sys.stderr.write('Error: {0}\n'.format(res['error']))
|
||||
exitcode |= res.get('errorcode', 1)
|
||||
else:
|
||||
print(repr(res))
|
||||
if exitcode:
|
||||
sys.exit(exitcode)
|
||||
|
||||
def blocking_scan(session):
|
||||
list(session.update('/discovery/rescan', {'rescan': 'start'}))
|
||||
while(list(session.read('/discovery/rescan'))[0].get('scanning', False)):
|
||||
time.sleep(0.5)
|
||||
list(session.update('/networking/macs/rescan', {'rescan': 'start'}))
|
||||
|
||||
|
||||
def main():
|
||||
parser = optparse.OptionParser(
|
||||
usage='Usage: %prog [list|assign|rescan|clear|subscribe|unsubscribe|register] [options]')
|
||||
# -a for 'address' maybe?
|
||||
# order by
|
||||
# show state (discovered or..
|
||||
# nodediscover approve?
|
||||
# flush to clear old data out? (e.g. no good way to age pxe data)
|
||||
# also delete discovery datum... more targeted
|
||||
# defect: -t lenovo-imm returns all
|
||||
parser.add_option('-m', '--model', dest='model',
|
||||
help='Operate with nodes matching the specified model '
|
||||
'number', metavar='MODEL')
|
||||
parser.add_option('-d', '--discoverystate', dest='state',
|
||||
help='The discovery state of the entries (discovered, '
|
||||
'identified, and unidentified)')
|
||||
parser.add_option('-s', '--serial', dest='serial',
|
||||
help='Operate against the system matching the specified '
|
||||
'serial number', metavar='SERIAL')
|
||||
parser.add_option('-u', '--uuid', dest='uuid',
|
||||
help='Operate against the system matching the specified '
|
||||
'UUID', metavar='UUID')
|
||||
parser.add_option('-n', '--node', help='Operate with the given nodename')
|
||||
parser.add_option('-e', '--ethaddr', dest='mac',
|
||||
help='Operate against the system with the specified MAC '
|
||||
'address', metavar='MAC')
|
||||
parser.add_option('-t', '--type', dest='type',
|
||||
help='Operate against the system of the specified type',
|
||||
metavar='TYPE')
|
||||
parser.add_option('-c', '--csv', dest='csv',
|
||||
help='Use CSV formatted output', action='store_true')
|
||||
parser.add_option('-i', '--import', dest='importfile',
|
||||
help='Import bulk assignment data from given CSV file',
|
||||
metavar='IMPORT.CSV')
|
||||
parser.add_option('-f', '--fields', dest='fields',
|
||||
help='Select fields for output',
|
||||
metavar='FIELDS')
|
||||
parser.add_option('-o', '--order', dest='order',
|
||||
help='Order output by given field', metavar='ORDER')
|
||||
(options, args) = parser.parse_args()
|
||||
if len(args) == 0 or args[0] not in ('list', 'assign', 'reassign', 'rescan', 'clear', 'subscribe', 'unsubscribe', 'register'):
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
if args[0] == 'register' and len(args) != 2:
|
||||
sys.stderr.write('Register requires target address or range (e.g. 192.168.2.30, 192.168.2.0/24, or 192.168.2.1-192.168.2.30)\n')
|
||||
sys.exit(1)
|
||||
if 'subscribe' in args[0] and len(args) != 2:
|
||||
sys.stderr.write('subscribe/unsubscribe subcommands require switch name as argument\n')
|
||||
sys.exit(1)
|
||||
session = client.Command()
|
||||
if args[0] == 'list':
|
||||
list_discovery(options, session)
|
||||
if args[0] == 'clear':
|
||||
clear_discovery(options, session)
|
||||
if args[0] == 'assign':
|
||||
assign_discovery(options, session)
|
||||
if args[0] == 'reassign':
|
||||
assign_discovery(options, session, False)
|
||||
if args[0] == 'register':
|
||||
register_endpoint(options, session, args[1])
|
||||
if args[0] == 'subscribe':
|
||||
subscribe_discovery(options, session, True, args[1])
|
||||
if args[0] == 'unsubscribe':
|
||||
subscribe_discovery(options, session, False, args[1])
|
||||
if args[0] == 'rescan':
|
||||
blocking_scan(session)
|
||||
print("Rescan complete")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015-2019 Lenovo
|
||||
# Copyright 2015-2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -15,9 +15,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import codecs
|
||||
from datetime import datetime as dt
|
||||
from datetime import timedelta
|
||||
import optparse
|
||||
import os
|
||||
import signal
|
||||
@@ -34,43 +32,19 @@ if path.startswith('/opt'):
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
if sys.version_info[0] < 3:
|
||||
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog [options] <noderange> [clear]")
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to clear if clearing log, '
|
||||
'prompting if over the threshold')
|
||||
argparser.add_option('-l', '--lines', type='int',
|
||||
help='return the last <n> entries '
|
||||
'for each node in the eventlog. '
|
||||
)
|
||||
argparser.add_option('-t', '--timeframe', type='string',
|
||||
help='return entries within a specified timeframe '
|
||||
'for each node in the eventlog. This will return '
|
||||
'entries from the last hours or days. '
|
||||
'1h would be one hour, 4d would be four days. '
|
||||
'format <num>h or <num>d'
|
||||
)
|
||||
usage="Usage: %prog [options] noderange [clear]")
|
||||
(options, args) = argparser.parse_args()
|
||||
try:
|
||||
noderange = args[0]
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
|
||||
deletemode = False
|
||||
if len(args) > 2:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
if len(args) == 2:
|
||||
if args[1] == 'clear':
|
||||
if len(sys.argv) == 3:
|
||||
if sys.argv[2] == 'clear':
|
||||
deletemode = True
|
||||
else:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
@@ -81,8 +55,6 @@ def format_event(evt):
|
||||
display = dt.strptime(evt['timestamp'], '%Y-%m-%dT%H:%M:%S')
|
||||
retparts.append(display.strftime('%m/%d/%Y %H:%M:%S'))
|
||||
dscparts = []
|
||||
if evt.get('log_id', None):
|
||||
retparts.append(evt['log_id'] + ':')
|
||||
if 'component_type' in evt and evt['component_type'] is not None:
|
||||
dscparts.append(evt['component_type'])
|
||||
if 'component' in evt and evt['component'] is not None:
|
||||
@@ -96,39 +68,13 @@ def format_event(evt):
|
||||
pass
|
||||
dscparts.append(evttext)
|
||||
retparts.append(' - '.join(dscparts))
|
||||
msg = evt.get('message')
|
||||
if not msg:
|
||||
msg = ''
|
||||
return ' '.join(retparts) + msg
|
||||
return ' '.join(retparts)
|
||||
|
||||
|
||||
if deletemode:
|
||||
func = session.delete
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
else:
|
||||
func = session.read
|
||||
|
||||
if options.timeframe:
|
||||
try:
|
||||
delta = int(options.timeframe[:-1])
|
||||
except ValueError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
if options.timeframe[-1].lower() == 'd':
|
||||
tdelta = timedelta(days=delta)
|
||||
elif options.timeframe[-1].lower() == 'h':
|
||||
tdelta = timedelta(hours=delta)
|
||||
else:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
timeframe = dt.now() - tdelta
|
||||
|
||||
event_dict = {}
|
||||
nodes = []
|
||||
for res in session.read('/noderange/{0}/nodes/'.format(args[0])):
|
||||
node = res.get('item', {}).get('href', '/').replace('/', '')
|
||||
nodes.append(node)
|
||||
event_dict[node] = []
|
||||
|
||||
for rsp in func('/noderange/{0}/events/hardware/log'.format(noderange)):
|
||||
if 'error' in rsp:
|
||||
sys.stderr.write(rsp['error'] + '\n')
|
||||
@@ -141,31 +87,6 @@ for rsp in func('/noderange/{0}/events/hardware/log'.format(noderange)):
|
||||
sys.stderr.write('{0}: {1}\n'.format(node, thisdata['error']))
|
||||
exitcode |= 1
|
||||
if 'events' in thisdata:
|
||||
evtdata = thisdata['events']
|
||||
if options.lines:
|
||||
event_dict[node].extend(evtdata)
|
||||
else:
|
||||
for evt in evtdata:
|
||||
if options.timeframe:
|
||||
# check if line is in timeframe
|
||||
if 'timestamp' in evt and evt['timestamp'] is not None:
|
||||
display = dt.strptime(evt['timestamp'], '%Y-%m-%dT%H:%M:%S')
|
||||
if display > timeframe:
|
||||
print('{0}: {1}'.format(node, format_event(evt)))
|
||||
else:
|
||||
print('{0}: {1}'.format(node, format_event(evt)))
|
||||
|
||||
if options.lines:
|
||||
for node in nodes:
|
||||
evtdata_list = event_dict[node]
|
||||
if len(evtdata_list) != 0:
|
||||
if len(evtdata_list) > options.lines:
|
||||
evtdata_list = evtdata_list[-abs(options.lines):]
|
||||
for evt in evtdata_list:
|
||||
if options.timeframe:
|
||||
if 'timestamp' in evt and evt['timestamp'] is not None:
|
||||
display = dt.strptime(evt['timestamp'], '%Y-%m-%dT%H:%M:%S')
|
||||
if display > timeframe:
|
||||
print('{0}: {1}'.format(node, format_event(evt)))
|
||||
else:
|
||||
print('{0}: {1}'.format(node, format_event(evt)))
|
||||
evtdata = thisdata['events']
|
||||
for evt in evtdata:
|
||||
print '{0}: {1}'.format(node, format_event(evt))
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2016-2017 Lenovo
|
||||
@@ -19,7 +19,6 @@ import optparse
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
import time
|
||||
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
@@ -31,20 +30,32 @@ if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
import confluent.screensqueeze as sq
|
||||
|
||||
|
||||
exitcode = 0
|
||||
|
||||
def printerror(res, node=None):
|
||||
global exitcode
|
||||
if 'errorcode' in res:
|
||||
exitcode = res['errorcode']
|
||||
if 'error' in res:
|
||||
if node:
|
||||
sys.stderr.write('{0}: {1}\n'.format(node, res['error']))
|
||||
else:
|
||||
sys.stderr.write('{0}\n'.format(res['error']))
|
||||
if 'errorcode' not in res:
|
||||
exitcode = 1
|
||||
|
||||
|
||||
def printfirm(node, prefix, data):
|
||||
if 'model' in data and data['model']:
|
||||
if 'model' in data:
|
||||
prefix += ' ' + data['model']
|
||||
builddesc = []
|
||||
if 'build' in data and data['build']:
|
||||
if 'build' in data:
|
||||
builddesc.append(data['build'])
|
||||
if 'date' in data and data['date']:
|
||||
if 'date' in data:
|
||||
builddesc.append(data['date'])
|
||||
if 'version' in data and data['version']:
|
||||
if 'version' in data:
|
||||
version = data['version']
|
||||
if builddesc:
|
||||
version += ' ({0})'.format(' '.join(builddesc))
|
||||
@@ -52,129 +63,28 @@ def printfirm(node, prefix, data):
|
||||
version = ' '.join(builddesc)
|
||||
print('{0}: {1}: {2}'.format(node, prefix, version))
|
||||
|
||||
components = ['all']
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: "
|
||||
"%prog <noderange> [list][update [--backup <file>]]|[<components>]")
|
||||
argparser.add_option('-b', '--backup', action='store_true',
|
||||
help='Target a backup bank rather than primary')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='When updating, prompt if more than the specified '
|
||||
'number of servers will be affected')
|
||||
|
||||
argparser = optparse.OptionParser(usage="Usage: %prog <noderange>")
|
||||
(options, args) = argparser.parse_args()
|
||||
upfile = None
|
||||
try:
|
||||
noderange = args[0]
|
||||
if len(args) > 1:
|
||||
if args[1] == 'update':
|
||||
upfile = args[2]
|
||||
else:
|
||||
if args[1] == 'list':
|
||||
comps = args[2:]
|
||||
else:
|
||||
comps = args[1:]
|
||||
components = []
|
||||
for arg in comps:
|
||||
components += arg.split(',')
|
||||
if not components:
|
||||
components = ['all']
|
||||
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
|
||||
def get_update_progress(session, url):
|
||||
for res in session.read(url):
|
||||
status = res.get('phase', 'error')
|
||||
percent = res.get('progress', None)
|
||||
detail = res.get('detail', repr(res)),
|
||||
if status == 'error':
|
||||
text = 'error!'
|
||||
else:
|
||||
text = '{0}: {1:3.0f}%'.format(status, percent)
|
||||
return text, status, detail
|
||||
|
||||
def update_firmware(session, filename):
|
||||
global exitcode
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
output = sq.ScreenPrinter(noderange, session)
|
||||
nodeurls = {}
|
||||
filename = os.path.abspath(filename)
|
||||
resource = '/noderange/{0}/inventory/firmware/updates/active'.format(
|
||||
noderange)
|
||||
upargs = {'filename': filename}
|
||||
if options.backup:
|
||||
upargs['bank'] = 'backup'
|
||||
noderrs = {}
|
||||
if session.unixdomain:
|
||||
of = open(filename, 'rb')
|
||||
try:
|
||||
session.add_file(filename, of.fileno(), 'rb')
|
||||
except Exception:
|
||||
pass
|
||||
for res in session.create(resource, upargs):
|
||||
if 'created' not in res:
|
||||
for nodename in res.get('databynode', ()):
|
||||
output.set_output(nodename, 'error!')
|
||||
noderrs[nodename] = res['databynode'][nodename].get(
|
||||
'error', 'Unknown Error')
|
||||
continue
|
||||
watchurl = res['created']
|
||||
currnode = watchurl.split('/')[1]
|
||||
nodeurls[currnode] = '/' + watchurl
|
||||
while nodeurls:
|
||||
for node in list(nodeurls):
|
||||
progress, status, err = get_update_progress(
|
||||
session, nodeurls[node])
|
||||
if status == 'error':
|
||||
exitcode = 1
|
||||
noderrs[node] = err
|
||||
if status in ('error', 'complete', 'pending'):
|
||||
list(session.delete(nodeurls[node]))
|
||||
del nodeurls[node]
|
||||
output.set_output(node, progress)
|
||||
time.sleep(2)
|
||||
allerrnodes = ','.join(noderrs)
|
||||
if noderrs:
|
||||
sys.stderr.write(
|
||||
'Nodes had errors updating ({0})!\n'.format(allerrnodes))
|
||||
for node in noderrs:
|
||||
sys.stderr.write('{0}: {1}\n'.format(node, noderrs[node]))
|
||||
|
||||
def show_firmware(session):
|
||||
global exitcode
|
||||
firmware_shown = False
|
||||
nodes_matched = False
|
||||
for component in components:
|
||||
for res in session.read(
|
||||
'/noderange/{0}/inventory/firmware/all/{1}'.format(
|
||||
noderange, component)):
|
||||
nodes_matched = True
|
||||
exitcode |= client.printerror(res)
|
||||
if 'databynode' not in res:
|
||||
continue
|
||||
for node in res['databynode']:
|
||||
if 'firmware' not in res['databynode'][node]:
|
||||
continue
|
||||
for inv in res['databynode'][node]['firmware']:
|
||||
for prefix in inv:
|
||||
firmware_shown = True
|
||||
printfirm(node, prefix, inv[prefix])
|
||||
if not nodes_matched:
|
||||
sys.stderr.write('No matching nodes for noderange "{0}"\n'.format(noderange))
|
||||
elif not firmware_shown and not exitcode:
|
||||
argparser.print_help()
|
||||
|
||||
|
||||
try:
|
||||
session = client.Command()
|
||||
if upfile is None:
|
||||
show_firmware(session)
|
||||
else:
|
||||
update_firmware(session, upfile)
|
||||
for res in session.read('/noderange/{0}/inventory/firmware/all/all'.format(
|
||||
noderange)):
|
||||
printerror(res)
|
||||
if 'databynode' not in res:
|
||||
continue
|
||||
for node in res['databynode']:
|
||||
printerror(res['databynode'][node], node)
|
||||
if 'firmware' not in res['databynode'][node]:
|
||||
continue
|
||||
for inv in res['databynode'][node]['firmware']:
|
||||
for prefix in inv:
|
||||
printfirm(node, prefix, inv[prefix])
|
||||
except KeyboardInterrupt:
|
||||
print('')
|
||||
sys.exit(exitcode)
|
||||
sys.exit(exitcode)
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python2
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
@@ -17,7 +17,6 @@
|
||||
|
||||
__author__ = 'alin37'
|
||||
|
||||
from getpass import getpass
|
||||
import optparse
|
||||
import os
|
||||
import signal
|
||||
@@ -36,20 +35,14 @@ import confluent.client as client
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage='''\n %prog [options] \
|
||||
\n %prog [options] nodegroup [list of attributes|all] \
|
||||
\n %prog [options] nodegroup nodes=value1,value2 \
|
||||
\n %prog -e nodegroup <attribute names to set> \
|
||||
\n %prog [options] nodegroup nodes=value1,value2
|
||||
\n %prog [options] nodegroup [list of attributes] \
|
||||
\n %prog [options] nodegroup group=value1,value2 \
|
||||
\n %prog [options] nodegroup group=value1,value2
|
||||
\n ''')
|
||||
argparser.add_option('-b', '--blame', action='store_true',
|
||||
help='Show information about how attributes inherited')
|
||||
argparser.add_option('-e', '--environment', action='store_true',
|
||||
help='Set attributes, but from environment variable of '
|
||||
'same name')
|
||||
argparser.add_option('-c', '--clear', action='store_true',
|
||||
help='Clear variables')
|
||||
argparser.add_option('-p', '--prompt', action='store_true',
|
||||
help='Prompt for attribute values interactively')
|
||||
(options, args) = argparser.parse_args()
|
||||
|
||||
|
||||
@@ -60,11 +53,8 @@ nodetype="nodegroups"
|
||||
|
||||
try:
|
||||
nodegroups = args[0]
|
||||
client.check_globbing(nodegroups)
|
||||
nodelist = '/{0}/{1}/'.format(nodetype,nodegroups)
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
nodelist = '/nodegroups/'
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
@@ -73,23 +63,7 @@ exitcode = 0
|
||||
|
||||
if len(args) > 1:
|
||||
showtype = 'all'
|
||||
if "=" in args[1] and options.clear:
|
||||
print("Can not clear and set at the same time!")
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
argassign = None
|
||||
if options.prompt:
|
||||
argassign = {}
|
||||
for arg in args[1:]:
|
||||
oneval = 1
|
||||
twoval = 2
|
||||
while oneval != twoval:
|
||||
oneval = getpass('Enter value for {0}: '.format(arg))
|
||||
twoval = getpass('Confirm value for {0}: '.format(arg))
|
||||
if oneval != twoval:
|
||||
print('Values did not match.')
|
||||
argassign[arg] = twoval
|
||||
exitcode=client.updateattrib(session,args,nodetype, nodegroups, options, argassign)
|
||||
exitcode=client.updateattrib(session,args,nodetype, nodegroups, options)
|
||||
try:
|
||||
# setting user output to what the user inputs
|
||||
if args[1] == 'all':
|
||||
@@ -99,7 +73,7 @@ if len(args) > 1:
|
||||
|
||||
requestargs=args[1:]
|
||||
except Exception as e:
|
||||
print(str(e))
|
||||
print str(e)
|
||||
|
||||
if exitcode != 0:
|
||||
sys.exit(exitcode)
|
||||
@@ -125,6 +99,6 @@ else:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
exitcode = 1
|
||||
else:
|
||||
print(res['item']['href'].replace('/', ''))
|
||||
print res['item']['href'].replace('/', '')
|
||||
|
||||
sys.exit(exitcode)
|
||||
sys.exit(exitcode)
|
||||
@@ -1,59 +0,0 @@
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage='''\n %prog noderange attribute1=value1 attribute2=value,...
|
||||
\n ''')
|
||||
(options, args) = argparser.parse_args()
|
||||
requestargs=None
|
||||
try:
|
||||
noderange = args[0]
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
for group in noderange.split(','):
|
||||
attribs = {'name': group}
|
||||
for arg in args[1:]:
|
||||
key, val = arg.split('=', 1)
|
||||
attribs[key] = val
|
||||
for r in session.create('/nodegroups/', attribs):
|
||||
if 'error' in r:
|
||||
sys.stderr.write(r['error'] + '\n')
|
||||
exitcode |= 1
|
||||
if 'created' in r:
|
||||
print('{0}: created'.format(r['created']))
|
||||
sys.exit(exitcode)
|
||||
@@ -1,62 +0,0 @@
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015-2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
__author__ = 'jjohnson2,alin37,andywray'
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
pass
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
def main():
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog\n")
|
||||
(options, args) = argparser.parse_args()
|
||||
noderange=""
|
||||
nodelist=""
|
||||
nodelist = '/nodegroups/'
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
showtype='all'
|
||||
requestargs=args[1:]
|
||||
nodetype='noderange'
|
||||
if len(args) > 0:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
else:
|
||||
for res in session.read(nodelist):
|
||||
if 'error' in res:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
exitcode = 1
|
||||
else:
|
||||
print(res['item']['href'].replace('/', ''))
|
||||
|
||||
sys.exit(exitcode)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,53 +0,0 @@
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage='''\n %prog noderange
|
||||
\n ''')
|
||||
(options, args) = argparser.parse_args()
|
||||
if len(args) != 1:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
noderange = args[0]
|
||||
client.check_globbing(noderange)
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
for group in noderange.split(','):
|
||||
for r in session.delete('/nodegroups/{0}'.format(group)):
|
||||
if 'error' in r:
|
||||
sys.stderr.write(r['error'] + '\n')
|
||||
exitcode |= 1
|
||||
if 'deleted' in r:
|
||||
print('{0}: deleted'.format(r['deleted']))
|
||||
sys.exit(exitcode)
|
||||
@@ -1,63 +0,0 @@
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2019 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
__author__ = 'jjohnson2,alin37,andywray'
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
pass
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
def main():
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog <group> <new group name>\n")
|
||||
(options, args) = argparser.parse_args()
|
||||
noderange=""
|
||||
nodelist=""
|
||||
nodelist = '/nodegroups/'
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
requestargs=args[1:]
|
||||
nodetype='noderange'
|
||||
if len(args) != 2:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
else:
|
||||
for res in session.update(
|
||||
'/nodegroups/{0}/attributes/rename'.format(args[0]),
|
||||
{'rename': args[1]}):
|
||||
if 'error' in res:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
exitcode = 1
|
||||
else:
|
||||
print('{0}: {1}'.format(res['oldname'], res['newname']))
|
||||
|
||||
sys.exit(exitcode)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python2
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015-2017 Lenovo
|
||||
@@ -32,8 +32,7 @@ if path.startswith('/opt'):
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
if sys.version_info[0] < 3:
|
||||
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
|
||||
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
|
||||
|
||||
argparser = optparse.OptionParser(usage="Usage: %prog <noderange>")
|
||||
(options, args) = argparser.parse_args()
|
||||
@@ -42,17 +41,8 @@ try:
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
|
||||
|
||||
codemappings = {
|
||||
'ok': 0,
|
||||
'warning': 1,
|
||||
'critical': 2,
|
||||
'failed': 2,
|
||||
'unknown': 2,
|
||||
}
|
||||
|
||||
def main():
|
||||
global session, exitcode, healthbynode, healthexplanations, health, node, sensor, explanation
|
||||
session = client.Command()
|
||||
@@ -75,11 +65,9 @@ def main():
|
||||
if 'error' in health[node]:
|
||||
sys.stderr.write('{0}: Error: {1}\n'.format(
|
||||
node, health[node]['error']))
|
||||
exitcode = 3
|
||||
exitcode |= 1
|
||||
if 'health' in health[node]:
|
||||
healthbynode[node] = health[node]['health']['value']
|
||||
if codemappings[healthbynode[node]] > exitcode:
|
||||
exitcode = codemappings[healthbynode[node]]
|
||||
if 'sensors' in health[node]:
|
||||
healthexplanations[node] = []
|
||||
for sensor in health[node]['sensors']:
|
||||
@@ -105,5 +93,5 @@ try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
print('')
|
||||
sys.exit(exitcode)
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python2
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015-2017 Lenovo
|
||||
@@ -31,17 +31,20 @@ if path.startswith('/opt'):
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
argparser = optparse.OptionParser(usage="Usage: %prog <noderange> [on|off|blink]")
|
||||
argparser = optparse.OptionParser(usage="Usage: %prog <noderange> [on|off]")
|
||||
(options, args) = argparser.parse_args()
|
||||
try:
|
||||
noderange = args[0]
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
|
||||
identifystate = None
|
||||
if len(sys.argv) > 2:
|
||||
identifystate = sys.argv[2]
|
||||
else:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
sys.exit(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python2
|
||||
#!/usr/bin/python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2016-2017 Lenovo
|
||||
@@ -15,11 +15,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import codecs
|
||||
import json
|
||||
import optparse
|
||||
import os
|
||||
import re
|
||||
import signal
|
||||
import sys
|
||||
|
||||
@@ -34,11 +31,6 @@ if path.startswith('/opt'):
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
if sys.version_info[0] < 3:
|
||||
sys.stdout = codecs.getwriter('utf8')(sys.stdout)
|
||||
|
||||
filters = []
|
||||
|
||||
|
||||
def pretty(text):
|
||||
if text == 'pcislot':
|
||||
@@ -48,25 +40,15 @@ def pretty(text):
|
||||
return text
|
||||
|
||||
def print_mem_info(node, prefix, meminfo):
|
||||
capacity = meminfo['capacity_mb'] / 1024
|
||||
memdescfmt = '{0}GB PC'
|
||||
if meminfo['memory_type'] == 'DDR3 SDRAM':
|
||||
memdescfmt += '3-{1} '
|
||||
elif 'DDR4' in meminfo['memory_type']:
|
||||
elif meminfo['memory_type'] == 'DDR4 SDRAM':
|
||||
memdescfmt += '4-{1} '
|
||||
elif 'DDR5' in meminfo['memory_type']:
|
||||
memdescfmt += '5-{1} '
|
||||
elif 'DCPMM' in meminfo['memory_type']:
|
||||
memdescfmt = '{0}GB {1} '
|
||||
meminfo['module_type'] = 'DCPMM'
|
||||
else:
|
||||
print('{0}: {1}: Unrecognized Memory'.format(node, prefix))
|
||||
return
|
||||
if meminfo.get('ecc', False):
|
||||
if meminfo['ecc']:
|
||||
memdescfmt += 'ECC '
|
||||
capacity = meminfo['capacity_mb'] / 1024
|
||||
modtype = meminfo.get('module_type', None)
|
||||
if modtype:
|
||||
memdescfmt += modtype
|
||||
memdescfmt += meminfo['module_type']
|
||||
memdesc = memdescfmt.format(capacity, meminfo['speed'])
|
||||
print('{0}: {1} description: {2}'.format(node, prefix, memdesc))
|
||||
print('{0}: {1} manufacturer: {2}'.format(
|
||||
@@ -74,11 +56,10 @@ def print_mem_info(node, prefix, meminfo):
|
||||
print('{0}: {1} model: {2}'.format(node, prefix, meminfo['model']))
|
||||
print('{0}: {1} serial number: {2}'.format(node, prefix,
|
||||
meminfo['serial']))
|
||||
if 'manufacture_date' in meminfo:
|
||||
print('{0}: {1} manufacture date: {2}'.format(node, prefix,
|
||||
meminfo['manufacture_date']))
|
||||
print('{0}: {1} manufacture location: {2}'.format(
|
||||
node, prefix, meminfo['manufacture_location']))
|
||||
print('{0}: {1} manufacture date: {2}'.format(node, prefix,
|
||||
meminfo['manufacture_date']))
|
||||
print('{0}: {1} manufacture location: {2}'.format(
|
||||
node, prefix, meminfo['manufacture_location']))
|
||||
|
||||
exitcode = 0
|
||||
|
||||
@@ -95,83 +76,30 @@ def printerror(res, node=None):
|
||||
exitcode = 1
|
||||
|
||||
|
||||
url = '/noderange/{0}/inventory/hardware/all/all'
|
||||
usedprefixes = set([])
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog <noderange> [serial|model|uuid|mac]")
|
||||
argparser.add_option('-j', '--json', action='store_true', help='Output JSON')
|
||||
argparser.add_option('-s', '--store', action='store_true', help='Store serial, model, and uuid into id.serial, id.model, and id.uuid')
|
||||
argparser = optparse.OptionParser(usage="Usage: %prog <noderange>")
|
||||
(options, args) = argparser.parse_args()
|
||||
try:
|
||||
noderange = args[0]
|
||||
noderange = sys.argv[1]
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
if len(args) > 1:
|
||||
if args[1] == 'firm':
|
||||
os.execlp('nodefirmware', 'nodefirmware', noderange)
|
||||
else:
|
||||
url = '/noderange/{0}/inventory/hardware/all/system'
|
||||
for arg in args:
|
||||
for arg in arg.split(','):
|
||||
if arg == 'serial':
|
||||
filters.append(re.compile('serial number'))
|
||||
elif arg == 'model':
|
||||
filters.append(re.compile('^model'))
|
||||
filters.append(re.compile('product name'))
|
||||
elif arg == 'uuid':
|
||||
filters.append(re.compile('uuid'))
|
||||
elif arg == 'mac':
|
||||
filters.append(re.compile('mac address'))
|
||||
url = '/noderange/{0}/inventory/hardware/all/all'
|
||||
if len(args) > 1 and args[1] == 'firm':
|
||||
os.execlp('nodefirmware', 'nodefirmware', noderange)
|
||||
try:
|
||||
if options.json:
|
||||
databynode = {}
|
||||
if options.store and len(args) <= 1:
|
||||
url = '/noderange/{0}/inventory/hardware/all/system'
|
||||
pushattribs = {}
|
||||
session = client.Command()
|
||||
for res in session.read(url.format(noderange)):
|
||||
for res in session.read('/noderange/{0}/inventory/hardware/all/all'.format(
|
||||
noderange)):
|
||||
printerror(res)
|
||||
if 'databynode' not in res:
|
||||
continue
|
||||
for node in res['databynode']:
|
||||
if options.store and node not in pushattribs:
|
||||
pushattribs[node] = {}
|
||||
printerror(res['databynode'][node], node)
|
||||
if 'inventory' not in res['databynode'][node]:
|
||||
continue
|
||||
for inv in res['databynode'][node]['inventory']:
|
||||
prefix = inv['name']
|
||||
if options.store and prefix == 'System':
|
||||
currinfo = inv.get('information', {})
|
||||
curruuid = currinfo.get('UUID', '')
|
||||
if curruuid:
|
||||
curruuid = curruuid.lower()
|
||||
pushattribs[node]['id.uuid'] = curruuid
|
||||
currserial = currinfo.get('Serial Number', '')
|
||||
if currserial:
|
||||
currserial = currserial.strip()
|
||||
pushattribs[node]['id.serial'] = currserial
|
||||
currmodelnum = currinfo.get('Model', '')
|
||||
if currmodelnum:
|
||||
currmodelnum = currmodelnum.strip()
|
||||
pushattribs[node]['id.model'] = currmodelnum
|
||||
idx = 2
|
||||
while (node, prefix) in usedprefixes:
|
||||
prefix = '{0} {1}'.format(inv['name'], idx)
|
||||
idx += 1
|
||||
usedprefixes.add((node, prefix))
|
||||
if not inv['present']:
|
||||
if not filters:
|
||||
if options.json:
|
||||
if node not in databynode:
|
||||
databynode[node] = {}
|
||||
databynode[node][prefix] = inv
|
||||
elif not options.store:
|
||||
print('{0}: {1}: Not Present'.format(node, prefix))
|
||||
print '{0}: {1}: Not Present'.format(node, prefix)
|
||||
continue
|
||||
info = inv['information']
|
||||
info.pop('board_extra', None)
|
||||
@@ -179,40 +107,14 @@ try:
|
||||
info.pop('chassis_extra', None)
|
||||
info.pop('product_extra', None)
|
||||
if 'memory_type' in info:
|
||||
if not filters:
|
||||
if options.json:
|
||||
if node not in databynode:
|
||||
databynode[node] = {}
|
||||
databynode[node][prefix] = inv
|
||||
continue
|
||||
print_mem_info(node, prefix, info)
|
||||
print_mem_info(node, prefix, info)
|
||||
continue
|
||||
for datum in info:
|
||||
if filters:
|
||||
for filter in filters:
|
||||
if filter.match(datum.lower()):
|
||||
break
|
||||
else:
|
||||
continue
|
||||
if info[datum] is None:
|
||||
continue
|
||||
if options.json:
|
||||
if node not in databynode:
|
||||
databynode[node] = {}
|
||||
databynode[node][prefix] = inv
|
||||
break
|
||||
elif not options.store:
|
||||
print(u'{0}: {1} {2}: {3}'.format(node, prefix,
|
||||
pretty(datum),
|
||||
info[datum]))
|
||||
if options.json:
|
||||
print(json.dumps(databynode, sort_keys=True, indent=4,
|
||||
separators=(',', ': ')))
|
||||
if pushattribs:
|
||||
for node in pushattribs:
|
||||
for rsp in session.update('/nodes/{0}/attributes/current'.format(node), pushattribs[node]):
|
||||
if 'error' in rsp:
|
||||
sys.stderr.write(rsp['error'] + '\n')
|
||||
print(u'{0}: {1} {2}: {3}'.format(node, prefix,
|
||||
pretty(datum),
|
||||
info[datum]))
|
||||
except KeyboardInterrupt:
|
||||
print('')
|
||||
sys.exit(exitcode)
|
||||
sys.exit(exitcode)
|
||||
@@ -1,151 +0,0 @@
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2019 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
import time
|
||||
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
pass
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
exitcode = 0
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: "
|
||||
"%prog <noderange> [list][install <license_filename>|save <directory>|delete <license_feature_name>]")
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to delete licenses from, '
|
||||
'prompting if over the threshold')
|
||||
(options, args) = argparser.parse_args()
|
||||
upfile = None
|
||||
downdir = None
|
||||
delete = False
|
||||
try:
|
||||
noderange = args[0]
|
||||
if len(args) > 1:
|
||||
if args[1] == 'install':
|
||||
upfile = args[2]
|
||||
elif args[1] == 'save':
|
||||
downdir = args[2]
|
||||
elif args[1] == 'delete':
|
||||
delete = args[2]
|
||||
elif args[1] != 'list':
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
|
||||
|
||||
def install_license(session, filename):
|
||||
global exitcode
|
||||
resource = '/noderange/{0}/configuration/' \
|
||||
'management_controller/licenses/'.format(noderange)
|
||||
filename = os.path.abspath(filename)
|
||||
instargs = {'filename': filename}
|
||||
if session.unixdomain:
|
||||
of = open(filename, 'rb')
|
||||
try:
|
||||
session.add_file(filename, of.fileno(), 'rb')
|
||||
except Exception:
|
||||
pass
|
||||
for res in session.create(resource, instargs):
|
||||
for node in res.get('databynode', []):
|
||||
if 'error' in res['databynode'][node]:
|
||||
sys.stderr.write('{0}: {1}\n'.format(
|
||||
node, res['databynode'][node]['error']))
|
||||
sys.exit(res['databynode'][node].get('errorcode', 1))
|
||||
show_licenses(session)
|
||||
|
||||
|
||||
def save_licenses(session, dirname):
|
||||
global exitcode
|
||||
resource = '/noderange/{0}/configuration/' \
|
||||
'management_controller/save_licenses'.format(noderange)
|
||||
filename = os.path.abspath(dirname)
|
||||
instargs = {'dirname': filename}
|
||||
for res in session.create(resource, instargs):
|
||||
for node in res.get('databynode', {}):
|
||||
fname = res['databynode'][node].get('filename', None)
|
||||
if fname:
|
||||
print('{0}: Saved license to {1}'.format(node, fname))
|
||||
elif 'error' in res['databynode'][node]:
|
||||
sys.stderr.write('{0}: {1}\n'.format(node, res['databynode'][node]['error']))
|
||||
else:
|
||||
sys.stderr.write('{0}: {1}\n'.format(node, repr(res['databynode'][node])))
|
||||
|
||||
|
||||
def show_licenses(session):
|
||||
global exitcode
|
||||
for res in session.read(
|
||||
'/noderange/{0}/configuration/management_controller/licenses/'
|
||||
'all'.format(noderange)):
|
||||
for node in res.get('databynode', {}):
|
||||
for license in res['databynode'][node].get('License', []):
|
||||
msg = '{0}: {1}'.format(node, license.get('feature',
|
||||
'Unknown'))
|
||||
if license.get('state', 'Active') != 'Active':
|
||||
msg += ' ({0})'.format(license['state'])
|
||||
print(msg)
|
||||
|
||||
|
||||
def delete_license(session, licname):
|
||||
global exitcode
|
||||
licstodel = []
|
||||
for res in list(session.read(
|
||||
'/noderange/{0}/configuration/management_controller/licenses/'
|
||||
'all'.format(noderange))):
|
||||
for node in res.get('databynode', {}):
|
||||
for license in res['databynode'][node].get('License', []):
|
||||
if license.get('feature', None) == licname:
|
||||
prefix = '/nodes/{0}/configuration/management_controller/licenses/'.format(node)
|
||||
for currlic in list(session.read(prefix)):
|
||||
currlic = currlic.get('item', {}).get('href', 'all')
|
||||
if currlic == 'all':
|
||||
continue
|
||||
currname = list(session.read(prefix + currlic))[0]
|
||||
currname = currname.get('License', [{}])[0].get('feature', None)
|
||||
if currname == licname:
|
||||
list(session.delete(prefix + currlic))
|
||||
show_licenses(session)
|
||||
|
||||
try:
|
||||
session = client.Command()
|
||||
if upfile:
|
||||
install_license(session, upfile)
|
||||
elif downdir:
|
||||
save_licenses(session, downdir)
|
||||
elif delete:
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
delete_license(session, delete)
|
||||
else:
|
||||
show_licenses(session)
|
||||
except KeyboardInterrupt:
|
||||
print('')
|
||||
sys.exit(exitcode)
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/libexec/platform-python
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015-2017 Lenovo
|
||||
@@ -15,15 +15,13 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
__author__ = 'jjohnson2,alin37,wgrzeda'
|
||||
__author__ = 'jjohnson2,alin37'
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
|
||||
|
||||
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
@@ -41,18 +39,14 @@ def main():
|
||||
" or: %prog [options] noderange <nodeattribute>...")
|
||||
argparser.add_option('-b', '--blame', action='store_true',
|
||||
help='Show information about how attributes inherited')
|
||||
argparser.add_option('-d', '--delim', metavar="STRING", default = "\n",
|
||||
help='Delimiter separating the values')
|
||||
(options, args) = argparser.parse_args()
|
||||
noderange=""
|
||||
nodelist=""
|
||||
list = []
|
||||
try:
|
||||
noderange = args[0]
|
||||
nodelist = '/noderange/{0}/nodes/'.format(noderange)
|
||||
except IndexError:
|
||||
nodelist = '/nodes/'
|
||||
client.check_globbing(noderange)
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
showtype='all'
|
||||
@@ -66,9 +60,7 @@ def main():
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
exitcode = 1
|
||||
else:
|
||||
elem=(res['item']['href'].replace('/', ''))
|
||||
list.append(elem)
|
||||
print(options.delim.join(list))
|
||||
print res['item']['href'].replace('/', '')
|
||||
|
||||
sys.exit(exitcode)
|
||||
|
||||
|
||||
@@ -1,199 +0,0 @@
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2018 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
import time
|
||||
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
pass
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
import confluent.screensqueeze as sq
|
||||
|
||||
exitcode = 0
|
||||
|
||||
|
||||
def get_update_progress(session, url):
|
||||
for res in session.read(url):
|
||||
status = res['phase']
|
||||
percent = res['progress']
|
||||
detail = res['detail']
|
||||
if status == 'error':
|
||||
text = 'error!'
|
||||
else:
|
||||
text = '{0}: {1:3.0f}%'.format(status, percent)
|
||||
return text, status, detail
|
||||
|
||||
|
||||
def printerror(res, node=None):
|
||||
global exitcode
|
||||
if 'errorcode' in res:
|
||||
exitcode = res['errorcode']
|
||||
if 'error' in res:
|
||||
if node:
|
||||
sys.stderr.write('{0}: {1}\n'.format(node, res['error']))
|
||||
else:
|
||||
sys.stderr.write('{0}\n'.format(res['error']))
|
||||
if 'errorcode' not in res:
|
||||
exitcode = 1
|
||||
for node in res.get('databynode', ()):
|
||||
printerror(res['databynode'][node], node)
|
||||
|
||||
|
||||
def attach_media(noderange, media):
|
||||
global exitcode
|
||||
session = client.Command()
|
||||
if ':' not in media:
|
||||
sys.stderr.write('Full URL required for attach\n')
|
||||
sys.exit(1)
|
||||
resource = '/noderange/{0}/media/attach'.format(noderange)
|
||||
for res in session.update(resource, {'url': media}):
|
||||
printerror(res)
|
||||
list_media(noderange, media)
|
||||
|
||||
|
||||
def list_media(noderange, media):
|
||||
session = client.Command()
|
||||
resource = '/noderange/{0}/media/current'.format(noderange)
|
||||
for res in session.read(resource):
|
||||
printerror(res)
|
||||
for node in res.get('databynode', []):
|
||||
url = res['databynode'][node].get('url', None)
|
||||
name = res['databynode'][node].get('name', None)
|
||||
if (url and not url.startswith('file:') and
|
||||
not res['databynode'][node].get('secure', False)):
|
||||
name += ' (insecure)'
|
||||
if not name:
|
||||
continue
|
||||
print('{0}: {1}'.format(node, url + '/' + name if url else name))
|
||||
|
||||
|
||||
def detach_media(noderange, media):
|
||||
global exitcode
|
||||
session = client.Command()
|
||||
resource = '/noderange/{0}/media/detach'.format(noderange)
|
||||
for res in session.update(resource, {'detachall': 1}):
|
||||
printerror(res)
|
||||
|
||||
|
||||
def upload_media(noderange, media):
|
||||
global exitcode
|
||||
if not os.path.exists(media):
|
||||
sys.stderr.write('Unable to locate requested file {0}\n'.format(
|
||||
media))
|
||||
sys.exit(404)
|
||||
session = client.Command()
|
||||
output = sq.ScreenPrinter(noderange, session)
|
||||
filename = os.path.abspath(media)
|
||||
resource = '/noderange/{0}/media/uploads/'.format(noderange)
|
||||
upargs = {'filename': filename}
|
||||
noderrs = {}
|
||||
if session.unixdomain:
|
||||
of = open(filename, 'rb')
|
||||
try:
|
||||
session.add_file(filename, of.fileno(), 'rb')
|
||||
except Exception:
|
||||
pass
|
||||
nodeurls = {}
|
||||
for res in session.create(resource, upargs):
|
||||
if 'created' not in res:
|
||||
for nodename in res.get('databynode', ()):
|
||||
output.set_output(nodename, 'error!')
|
||||
noderrs[nodename] = res['databynode'][nodename].get(
|
||||
'error', 'Unknown Error')
|
||||
continue
|
||||
watchurl = res['created']
|
||||
currnode = watchurl.split('/')[1]
|
||||
nodeurls[currnode] = '/' + watchurl
|
||||
while nodeurls:
|
||||
for node in list(nodeurls):
|
||||
progress, status, err = get_update_progress(
|
||||
session, nodeurls[node])
|
||||
if status == 'error':
|
||||
exitcode = 1
|
||||
noderrs[node] = err
|
||||
if status in ('error', 'complete', 'pending'):
|
||||
list(session.delete(nodeurls[node]))
|
||||
del nodeurls[node]
|
||||
output.set_output(node, progress)
|
||||
time.sleep(2)
|
||||
allerrnodes = ','.join(noderrs)
|
||||
if noderrs:
|
||||
sys.stderr.write(
|
||||
'Nodes had errors receiving media ({0})!\n'.format(allerrnodes))
|
||||
for node in noderrs:
|
||||
sys.stderr.write('{0}: {1}\n'.format(node, noderrs[node]))
|
||||
list_media(noderange, media)
|
||||
|
||||
funmap = {
|
||||
'upload': upload_media,
|
||||
'attach': attach_media,
|
||||
'detachall': detach_media,
|
||||
'list': list_media,
|
||||
}
|
||||
|
||||
|
||||
class OptParser(optparse.OptionParser):
|
||||
|
||||
def format_epilog(self, formatter):
|
||||
return self.expand_prog_name(self.epilog)
|
||||
|
||||
def main():
|
||||
argparser = OptParser(
|
||||
usage="Usage: %prog <noderange> [list|upload|attach|detachall] "
|
||||
"<filename>|all|<url>",
|
||||
epilog='\nupload will take the specified file and upload it to the '
|
||||
'BMC.\n\n'
|
||||
'attach will instruct the BMC to connect a remote media to the '
|
||||
'specified url.\n\ndetachall will remove *ALL* uploaded and '
|
||||
'attached urls from the BMC\n\nlist shows currently mounted '
|
||||
'media.\n\nSee `man %prog` for more info.\n')
|
||||
(options, args) = argparser.parse_args()
|
||||
media = None
|
||||
try:
|
||||
noderange = args[0]
|
||||
operation = args[1]
|
||||
arglength = 2
|
||||
if operation in ('attach', 'upload'):
|
||||
media = args[2]
|
||||
arglength = 3
|
||||
if len(args) > arglength:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
try:
|
||||
handler = funmap[operation]
|
||||
except KeyError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
handler(noderange, media)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
sys.exit(exitcode)
|
||||
@@ -1,140 +0,0 @@
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2016-2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from collections import deque
|
||||
import optparse
|
||||
import os
|
||||
import select
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
pass
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
import confluent.sortutil as sortutil
|
||||
|
||||
|
||||
def run():
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog [options] noderange")
|
||||
argparser.add_option('-f', '-c', '--count', type='int', default=168,
|
||||
help='Number of commands to run at a time')
|
||||
argparser.add_option('-s', '--substitutename',
|
||||
help='Use a different name other than the nodename for ping, with {}, it is the entire name evaluated as an expression, otherwise it is used as a suffix')
|
||||
# among other things, FD_SETSIZE limits. Besides, spawning too many
|
||||
# processes can be unkind for the unaware on memory pressure and such...
|
||||
(options, args) = argparser.parse_args()
|
||||
if len(args) < 1:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(args[0])
|
||||
concurrentprocs = options.count
|
||||
c = client.Command()
|
||||
|
||||
currprocs = 0
|
||||
all = set([])
|
||||
pipedesc = {}
|
||||
pendingexecs = deque()
|
||||
exitcode = 0
|
||||
|
||||
nodemap = {}
|
||||
if options.substitutename:
|
||||
subname = options.substitutename
|
||||
if '{' not in subname:
|
||||
subname = '{node}' + subname
|
||||
for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]),
|
||||
{'expression': subname}):
|
||||
if 'error' in exp:
|
||||
sys.stderr.write(exp['error'] + '\n')
|
||||
exitcode |= exp.get('errorcode', 1)
|
||||
ex = exp.get('databynode', ())
|
||||
for node in ex:
|
||||
nodemap[node] = ex[node]['value']
|
||||
for rsp in c.read('/noderange/{0}/nodes/'.format(args[0])):
|
||||
if 'error' in rsp:
|
||||
sys.stderr.write(rsp['error'] + '\n')
|
||||
exitcode |= rsp.get('errorcode', 1)
|
||||
if rsp.get('item', {}).get('href', None):
|
||||
node = rsp['item']['href'][:-1]
|
||||
pingnode = nodemap.get(node, node)
|
||||
cmdv = ['ping', '-c', '1', '-W', '1', pingnode]
|
||||
if currprocs < concurrentprocs:
|
||||
currprocs += 1
|
||||
run_cmdv(pingnode, cmdv, all, pipedesc)
|
||||
else:
|
||||
pendingexecs.append((pingnode, cmdv))
|
||||
if not all or exitcode:
|
||||
sys.exit(exitcode)
|
||||
rdy, _, _ = select.select(all, [], [], 10)
|
||||
while all:
|
||||
pernodeout = {}
|
||||
for r in rdy:
|
||||
desc = pipedesc[r]
|
||||
node = desc['node']
|
||||
data = True
|
||||
while data and select.select([r], [], [], 0)[0]:
|
||||
data = r.readline()
|
||||
if not data:
|
||||
pop = desc['popen']
|
||||
ret = pop.poll()
|
||||
if ret is not None:
|
||||
exitcode = exitcode | ret
|
||||
all.discard(r)
|
||||
r.close()
|
||||
if desc['type'] == 'stdout':
|
||||
if ret:
|
||||
print('{0}: no_ping'.format(node))
|
||||
else:
|
||||
print('{0}: ping'.format(node))
|
||||
if pendingexecs:
|
||||
node, cmdv = pendingexecs.popleft()
|
||||
run_cmdv(node, cmdv, all, pipedesc)
|
||||
for node in sortutil.natural_sort(pernodeout):
|
||||
for line in pernodeout[node]:
|
||||
line = client.stringify(line)
|
||||
if options.nonodeprefix:
|
||||
sys.stdout.write(line)
|
||||
else:
|
||||
sys.stdout.write('{0}: {1}'.format(node, line))
|
||||
sys.stdout.flush()
|
||||
if all:
|
||||
rdy, _, _ = select.select(all, [], [], 10)
|
||||
sys.exit(exitcode)
|
||||
|
||||
|
||||
def run_cmdv(node, cmdv, all, pipedesc):
|
||||
nopen = subprocess.Popen(
|
||||
cmdv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
pipedesc[nopen.stdout] = {'node': node, 'popen': nopen,
|
||||
'type': 'stdout'}
|
||||
pipedesc[nopen.stderr] = {'node': node, 'popen': nopen,
|
||||
'type': 'stderr'}
|
||||
all.add(nopen.stdout)
|
||||
all.add(nopen.stderr)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python2
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015-2017 Lenovo
|
||||
@@ -32,57 +32,27 @@ if path.startswith('/opt'):
|
||||
import confluent.client as client
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog [options] <noderange> "
|
||||
"([status|on|off|shutdown|boot|reset|pdu_status|pdu_off|pdu_on])")
|
||||
argparser.add_option('-p', '--showprevious', dest='previous',
|
||||
action='store_true', default=False,
|
||||
help='Show previous power state')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to change power state, '
|
||||
'prompting if over the threshold')
|
||||
|
||||
usage="Usage: %prog [options] noderange "
|
||||
"([status|on|off|shutdown|boot|reset])")
|
||||
(options, args) = argparser.parse_args()
|
||||
try:
|
||||
noderange = args[0]
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
|
||||
setstate = None
|
||||
if len(args) > 1:
|
||||
setstate = args[1]
|
||||
if len(sys.argv) > 2:
|
||||
if setstate == 'softoff':
|
||||
setstate = 'shutdown'
|
||||
elif not sys.argv[2] in ('stat', 'state', 'status'):
|
||||
setstate = sys.argv[2]
|
||||
|
||||
if setstate not in (None, 'on', 'off', 'shutdown', 'boot', 'reset', 'pdu_status', 'pdu_stat', 'pdu_on', 'pdu_off', 'status', 'stat', 'state'):
|
||||
if setstate not in (None, 'on', 'off', 'shutdown', 'boot', 'reset'):
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
session.add_precede_key('oldstate')
|
||||
powurl = 'state'
|
||||
if setstate and setstate.startswith('pdu_'):
|
||||
setstate = setstate.replace('pdu_', '')
|
||||
powurl = 'inlets/all'
|
||||
if setstate in ('status', 'state', 'stat'):
|
||||
setstate = None
|
||||
|
||||
if options.previous:
|
||||
# get previous states
|
||||
prev = {}
|
||||
for rsp in session.read("/noderange/{0}/power/{1}".format(noderange, powurl)):
|
||||
# gets previous (current) states
|
||||
|
||||
databynode = rsp["databynode"]
|
||||
|
||||
for node in databynode:
|
||||
prev[node] = databynode[node]["state"]["value"]
|
||||
|
||||
# add dictionary to session
|
||||
session.add_precede_dict(prev)
|
||||
|
||||
def outhandler(node, res):
|
||||
for k in res[node]:
|
||||
client.cprint('{0}: {1}: {2}'.format(node, k.replace('inlet_', ''), res[node][k]))
|
||||
sys.exit(session.simple_noderange_command(noderange, '/power/{0}'.format(powurl), setstate, promptover=options.maxnodes, key='state', outhandler=outhandler))
|
||||
sys.exit(
|
||||
session.simple_noderange_command(noderange, '/power/state', setstate))
|
||||
@@ -1,57 +0,0 @@
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage='''\n %prog <noderange>
|
||||
\n ''')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to delete, '
|
||||
'prompting if over the threshold')
|
||||
(options, args) = argparser.parse_args()
|
||||
if len(args) != 1:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
noderange = args[0]
|
||||
client.check_globbing(noderange)
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
for r in session.delete('/noderange/{0}'.format(noderange)):
|
||||
if 'error' in r:
|
||||
sys.stderr.write(r['error'] + '\n')
|
||||
exitcode |= 1
|
||||
if 'deleted' in r:
|
||||
print('{0}: deleted'.format(r['deleted']))
|
||||
sys.exit(exitcode)
|
||||
@@ -1,52 +0,0 @@
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2019 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
argparser = optparse.OptionParser(usage="Usage: %prog <noderange> <newname>")
|
||||
(options, args) = argparser.parse_args()
|
||||
try:
|
||||
noderange = args[0]
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
identifystate = None
|
||||
if len(sys.argv) > 2:
|
||||
newname = sys.argv[2]
|
||||
else:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
sys.exit(
|
||||
session.simple_noderange_command(noderange, 'attributes/rename', newname))
|
||||
|
||||
@@ -1,66 +0,0 @@
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015-2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
argparser = optparse.OptionParser(usage="Usage: %prog <noderange>")
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to reseat, '
|
||||
'prompting if over the threshold')
|
||||
(options, args) = argparser.parse_args()
|
||||
try:
|
||||
noderange = args[0]
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
|
||||
errorNodes = set([])
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
success = session.simple_noderange_command(noderange, 'power/reseat', 'reseat', key='reseat', errnodes=errorNodes) # = 0 if successful
|
||||
|
||||
# Determine which nodes were successful and print them
|
||||
|
||||
allNodes = set([])
|
||||
|
||||
for node in session.read('/noderange/{0}/nodes/'.format(noderange)):
|
||||
allNodes.add(node['item']['href'].replace("/", ""))
|
||||
|
||||
goodNodes = allNodes - errorNodes
|
||||
|
||||
for node in goodNodes:
|
||||
print(node + ": Reseat successful")
|
||||
|
||||
|
||||
sys.exit(success)
|
||||
@@ -1,176 +0,0 @@
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2016-2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from collections import deque
|
||||
import optparse
|
||||
import os
|
||||
import select
|
||||
import shlex
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
pass
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
import confluent.screensqueeze as sq
|
||||
import confluent.sortutil as sortutil
|
||||
|
||||
|
||||
def run():
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog <file/directorylist> <noderange>:<destination>",
|
||||
)
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to run rsync to, '
|
||||
'prompting if over the threshold')
|
||||
argparser.add_option('-l', '--loginname', type='str',
|
||||
help='Username to use when connecting, defaults to current user.')
|
||||
argparser.add_option('-s', '--substitutename',
|
||||
help='Use a different name other than the nodename for rsync')
|
||||
argparser.add_option('-f', '-c', '--count', type='int', default=168,
|
||||
help='Number of nodes to concurrently rsync')
|
||||
# among other things, FD_SETSIZE limits. Besides, spawning too many
|
||||
# processes can be unkind for the unaware on memory pressure and such...
|
||||
argparser.disable_interspersed_args()
|
||||
(options, args) = argparser.parse_args()
|
||||
if len(args) < 2 or ':' not in args[-1]:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
concurrentprocs = options.count
|
||||
noderange, targpath = args[-1].split(':', 1)
|
||||
client.check_globbing(noderange)
|
||||
c = client.Command()
|
||||
cmdstr = ' '.join(args[:-1])
|
||||
cmdstr = 'rsync -av --info=progress2 ' + cmdstr
|
||||
|
||||
targname = options.substitutename
|
||||
if targname and '{' in targname:
|
||||
targname = targname + ':'
|
||||
elif targname:
|
||||
targname = '{node}' + targname + ':'
|
||||
else:
|
||||
targname = '{node}:'
|
||||
|
||||
if options.loginname:
|
||||
cmdstr += ' {}@'.format(options.loginname) + targname + targpath
|
||||
else:
|
||||
cmdstr += ' {}'.format(targname) + targpath
|
||||
|
||||
|
||||
currprocs = 0
|
||||
all = set([])
|
||||
pipedesc = {}
|
||||
pendingexecs = deque()
|
||||
exitcode = 0
|
||||
c.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
for exp in c.create('/noderange/{0}/attributes/expression'.format(noderange),
|
||||
{'expression': cmdstr}):
|
||||
if 'error' in exp:
|
||||
sys.stderr.write(exp['error'] + '\n')
|
||||
exitcode |= exp.get('errorcode', 1)
|
||||
ex = exp.get('databynode', ())
|
||||
for node in ex:
|
||||
cmd = ex[node]['value']
|
||||
if not isinstance(cmd, bytes) and not isinstance(cmd, str):
|
||||
cmd = cmd.encode('utf-8')
|
||||
cmdv = shlex.split(cmd)
|
||||
if currprocs < concurrentprocs:
|
||||
currprocs += 1
|
||||
run_cmdv(node, cmdv, all, pipedesc)
|
||||
else:
|
||||
pendingexecs.append((node, cmdv))
|
||||
if not all or exitcode:
|
||||
sys.exit(exitcode)
|
||||
rdy, _, _ = select.select(all, [], [], 10)
|
||||
nodeerrs = {}
|
||||
pernodeout = {}
|
||||
pernodefile = {}
|
||||
output = sq.ScreenPrinter(noderange, c)
|
||||
|
||||
while all:
|
||||
for r in rdy:
|
||||
desc = pipedesc[r]
|
||||
node = desc['node']
|
||||
data = True
|
||||
while data and select.select([r], [], [], 0)[0]:
|
||||
data = r.read(1)
|
||||
if data:
|
||||
if desc['type'] == 'stdout':
|
||||
if node not in pernodeout:
|
||||
pernodeout[node] = ''
|
||||
pernodeout[node] += client.stringify(data)
|
||||
if '\n' in pernodeout[node]:
|
||||
currout, pernodeout[node] = pernodeout[node].split('\n', 1)
|
||||
if currout:
|
||||
pernodefile[node] = os.path.basename(currout)
|
||||
if '\r' in pernodeout[node]:
|
||||
currout, pernodeout[node] = pernodeout[node].split('\r', 1)
|
||||
if currout:
|
||||
currout = currout.split()
|
||||
try:
|
||||
currout = currout[1]
|
||||
output.set_output(node, '{0}:{1}'.format(pernodefile[node], currout))
|
||||
except IndexError:
|
||||
pernodefile = currout[0]
|
||||
pass
|
||||
else:
|
||||
output.set_output(node, 'error!')
|
||||
if node not in nodeerrs:
|
||||
nodeerrs[node] = ''
|
||||
nodeerrs[node] += client.stringify(data)
|
||||
else:
|
||||
pop = desc['popen']
|
||||
ret = pop.poll()
|
||||
if ret is not None:
|
||||
exitcode = exitcode | ret
|
||||
all.discard(r)
|
||||
r.close()
|
||||
if node not in nodeerrs:
|
||||
output.set_output(node, 'complete')
|
||||
if desc['type'] == 'stdout' and pendingexecs:
|
||||
node, cmdv = pendingexecs.popleft()
|
||||
run_cmdv(node, cmdv, all, pipedesc)
|
||||
if all:
|
||||
rdy, _, _ = select.select(all, [], [], 10)
|
||||
for node in nodeerrs:
|
||||
for line in nodeerrs[node].split('\n'):
|
||||
sys.stderr.write('{0}: {1}\n'.format(node, line))
|
||||
sys.exit(exitcode)
|
||||
|
||||
|
||||
def run_cmdv(node, cmdv, all, pipedesc):
|
||||
nopen = subprocess.Popen(
|
||||
cmdv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
pipedesc[nopen.stdout] = {'node': node, 'popen': nopen,
|
||||
'type': 'stdout'}
|
||||
pipedesc[nopen.stderr] = {'node': node, 'popen': nopen,
|
||||
'type': 'stderr'}
|
||||
all.add(nopen.stdout)
|
||||
all.add(nopen.stderr)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python2
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2016-2017 Lenovo
|
||||
@@ -34,34 +34,21 @@ if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
import confluent.sortutil as sortutil
|
||||
devnull = None
|
||||
|
||||
|
||||
def run():
|
||||
global devnull
|
||||
devnull = open(os.devnull, 'rb')
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog [options] <noderange> <command expression>",
|
||||
epilog="Expressions are the same as in attributes, e.g. "
|
||||
"'ipmitool -H {hardwaremanagement.manager}' will be expanded.")
|
||||
argparser.add_option('-f', '-c', '--count', type='int', default=168,
|
||||
help='Number of commands to run at a time')
|
||||
argparser.add_option('-n', '--nonodeprefix', action='store_true',
|
||||
help='Do not prefix output with node names')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to run the command with, '
|
||||
'prompting if over the threshold')
|
||||
concurrentprocs = 168
|
||||
# among other things, FD_SETSIZE limits. Besides, spawning too many
|
||||
# processes can be unkind for the unaware on memory pressure and such...
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog node commandexpression",
|
||||
epilog="Expressions are the same as in attributes, e.g. "
|
||||
"'ipmitool -H {hardwaremanagement.manager}' will be expanded.")
|
||||
argparser.disable_interspersed_args()
|
||||
(options, args) = argparser.parse_args()
|
||||
if len(args) < 2:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
concurrentprocs = options.count
|
||||
client.check_globbing(args[0])
|
||||
c = client.Command()
|
||||
cmdstr = " ".join(args[1:])
|
||||
|
||||
@@ -69,79 +56,48 @@ def run():
|
||||
all = set([])
|
||||
pipedesc = {}
|
||||
pendingexecs = deque()
|
||||
exitcode = 0
|
||||
c.stop_if_noderange_over(args[0], options.maxnodes)
|
||||
|
||||
for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]),
|
||||
{'expression': cmdstr}):
|
||||
if 'error' in exp:
|
||||
sys.stderr.write(exp['error'] + '\n')
|
||||
exitcode |= exp.get('errorcode', 1)
|
||||
ex = exp.get('databynode', ())
|
||||
ex = exp['databynode']
|
||||
for node in ex:
|
||||
cmd = ex[node]['value']
|
||||
if not isinstance(cmd, bytes) and not isinstance(cmd, str):
|
||||
cmd = cmd.encode('utf-8')
|
||||
cmd = ex[node]['value'].encode('utf-8')
|
||||
cmdv = shlex.split(cmd)
|
||||
if currprocs < concurrentprocs:
|
||||
currprocs += 1
|
||||
run_cmdv(node, cmdv, all, pipedesc)
|
||||
else:
|
||||
pendingexecs.append((node, cmdv))
|
||||
if not all or exitcode:
|
||||
sys.exit(exitcode)
|
||||
|
||||
exitcode = 0
|
||||
rdy, _, _ = select.select(all, [], [], 10)
|
||||
while all:
|
||||
pernodeout = {}
|
||||
for r in rdy:
|
||||
data = r.readline()
|
||||
desc = pipedesc[r]
|
||||
node = desc['node']
|
||||
data = True
|
||||
while data and select.select([r], [], [], 0)[0]:
|
||||
data = r.readline()
|
||||
if data:
|
||||
if desc['type'] == 'stdout':
|
||||
if node not in pernodeout:
|
||||
pernodeout[node] = []
|
||||
pernodeout[node].append(data)
|
||||
else:
|
||||
data = client.stringify(data)
|
||||
if options.nonodeprefix:
|
||||
sys.stderr.write(data)
|
||||
else:
|
||||
sys.stderr.write('{0}: {1}'.format(node, data))
|
||||
sys.stderr.flush()
|
||||
if data:
|
||||
node = desc['node']
|
||||
if desc['type'] == 'stdout':
|
||||
sys.stdout.write('{0}: {1}'.format(node,data))
|
||||
else:
|
||||
pop = desc['popen']
|
||||
ret = pop.poll()
|
||||
if ret is not None:
|
||||
exitcode = exitcode | ret
|
||||
all.discard(r)
|
||||
r.close()
|
||||
if desc['type'] == 'stdout' and pendingexecs:
|
||||
node, cmdv = pendingexecs.popleft()
|
||||
run_cmdv(node, cmdv, all, pipedesc)
|
||||
for node in sortutil.natural_sort(pernodeout):
|
||||
for line in pernodeout[node]:
|
||||
line = client.stringify(line)
|
||||
if options.nonodeprefix:
|
||||
sys.stdout.write(line)
|
||||
else:
|
||||
sys.stdout.write('{0}: {1}'.format(node, line))
|
||||
sys.stdout.flush()
|
||||
sys.stderr.write('{0}: {1}'.format(node, data))
|
||||
else:
|
||||
pop = desc['popen']
|
||||
ret = pop.poll()
|
||||
if ret is not None:
|
||||
exitcode = exitcode | ret
|
||||
all.discard(r)
|
||||
if desc['type'] == 'stdout' and pendingexecs:
|
||||
node, cmdv = pendingexecs.popleft()
|
||||
run_cmdv(node, cmdv, all, pipedesc)
|
||||
if all:
|
||||
rdy, _, _ = select.select(all, [], [], 10)
|
||||
sys.exit(exitcode)
|
||||
|
||||
|
||||
def run_cmdv(node, cmdv, all, pipedesc):
|
||||
try:
|
||||
nopen = subprocess.Popen(
|
||||
cmdv, stdin=devnull, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
except OSError as e:
|
||||
if e.errno == 2:
|
||||
sys.stderr.write('{0}: Unable to find local executable file "{1}"'.format(node, cmdv[0]))
|
||||
return
|
||||
raise
|
||||
nopen = subprocess.Popen(
|
||||
cmdv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
pipedesc[nopen.stdout] = {'node': node, 'popen': nopen,
|
||||
'type': 'stdout'}
|
||||
pipedesc[nopen.stderr] = {'node': node, 'popen': nopen,
|
||||
@@ -151,4 +107,4 @@ def run_cmdv(node, cmdv, all, pipedesc):
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
||||
run()
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python2
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015-2017 Lenovo
|
||||
@@ -32,24 +32,10 @@ path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
|
||||
class hybridcsv(csv.excel):
|
||||
lineterminator = '\n'
|
||||
|
||||
|
||||
def floatformat(num):
|
||||
fm = u'{:.5f}'.format(num).rstrip('0')
|
||||
if fm[-1:] == u'.':
|
||||
return fm + u'0'
|
||||
return fm
|
||||
|
||||
csv.register_dialect('hybrid', hybridcsv)
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
sensorcollections = {
|
||||
'all': 'sensors/hardware/all/all',
|
||||
'energy': 'sensors/hardware/energy/all',
|
||||
'temperature': 'sensors/hardware/temperature/all',
|
||||
'temp': 'sensors/hardware/temperature/all',
|
||||
'power': 'sensors/hardware/power/all',
|
||||
@@ -78,7 +64,6 @@ if options.numreadings:
|
||||
options.interval = 1
|
||||
try:
|
||||
noderange = args[0]
|
||||
client.check_globbing(noderange)
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
@@ -93,6 +78,7 @@ for sensorgroup in args[1:]:
|
||||
sensors.append('sensors/hardware/all/' + sensor)
|
||||
if not sensors:
|
||||
sensors = ['sensors/hardware/all/all']
|
||||
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
sensorheaders = {}
|
||||
@@ -127,8 +113,7 @@ def sensorpass(showout=True, appendtime=False):
|
||||
continue
|
||||
for redundant_state in ('Non-Critical', 'Critical'):
|
||||
try:
|
||||
if sensedata.get('states', False):
|
||||
sensedata['states'].remove(redundant_state)
|
||||
sensedata['states'].remove(redundant_state)
|
||||
except ValueError:
|
||||
pass
|
||||
resultdata[node][sensedata['name']] = sensedata
|
||||
@@ -140,18 +125,15 @@ def sensorpass(showout=True, appendtime=False):
|
||||
if showout:
|
||||
if sensedata['value'] is None:
|
||||
showval = ''
|
||||
elif isinstance(sensedata['value'], float):
|
||||
showval = u' {0} '.format(floatformat(sensedata['value']))
|
||||
else:
|
||||
showval = u' {0} '.format(sensedata['value'])
|
||||
if sensedata['units'] not in (None, u''):
|
||||
showval += sensedata['units']
|
||||
if sensedata.get('health', 'ok') != 'ok':
|
||||
if sensedata['health'] != 'ok':
|
||||
datadescription = [sensedata['health']]
|
||||
else:
|
||||
datadescription = []
|
||||
if sensedata.get('states', False):
|
||||
datadescription.extend(sensedata['states'])
|
||||
datadescription.extend(sensedata['states'])
|
||||
if datadescription:
|
||||
if showval == '':
|
||||
showval += u' {0}'.format(
|
||||
@@ -162,19 +144,15 @@ def sensorpass(showout=True, appendtime=False):
|
||||
if appendtime:
|
||||
showval += ' @' + time.strftime(
|
||||
'%Y-%m-%dT%H:%M:%S')
|
||||
printval = u'{0}: {1}:{2}'.format(
|
||||
node, sensedata['name'], showval)
|
||||
if not isinstance(printval, str):
|
||||
printval = printval.encode('utf-8')
|
||||
print(printval)
|
||||
sys.stdout.flush()
|
||||
print(u'{0}: {1}:{2}'.format(
|
||||
node, sensedata['name'], showval).encode('utf-8'))
|
||||
return resultdata
|
||||
|
||||
|
||||
def format_csv(csvwriter, orderedsensors, resdata, showtime=True):
|
||||
for nodekey in resdata:
|
||||
if showtime:
|
||||
if isinstance(showtime, int):
|
||||
if showtime.is_integer():
|
||||
rowdata = [time.strftime('%Y-%m-%dT%H:%M:%S'), nodekey]
|
||||
else:
|
||||
rowdata = [time.strftime('%Y-%m-%dT%H:%M:%S.') +
|
||||
@@ -197,13 +175,10 @@ def format_csv(csvwriter, orderedsensors, resdata, showtime=True):
|
||||
datum = ','.join([datum, healthstates])
|
||||
else:
|
||||
datum = healthstates
|
||||
if isinstance(datum, float):
|
||||
datum = floatformat(datum)
|
||||
rowdata.append(datum)
|
||||
except KeyError:
|
||||
rowdata.append('N/A')
|
||||
csvwriter.writerow(rowdata)
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def main():
|
||||
@@ -217,14 +192,10 @@ def main():
|
||||
orderedsensors.append(name)
|
||||
orderedsensors.sort()
|
||||
for name in orderedsensors:
|
||||
headername = sensorheaders[name]
|
||||
if (not isinstance(headername, str) and
|
||||
not isinstance(headername, bytes)):
|
||||
headername = headername.encode('utf-8')
|
||||
headernames.append(headername)
|
||||
headernames.append(sensorheaders[name].encode('utf-8'))
|
||||
if options.csv:
|
||||
linebyline = False
|
||||
csvwriter = csv.writer(sys.stdout, dialect='hybrid')
|
||||
csvwriter = csv.writer(sys.stdout)
|
||||
if options.interval:
|
||||
csvwriter.writerow(['time', 'node'] + headernames)
|
||||
else:
|
||||
@@ -245,7 +216,7 @@ def main():
|
||||
sys.exit(exitcode)
|
||||
sleeptime = nextstart - os.times()[4]
|
||||
if sleeptime > 0:
|
||||
time.sleep(sleeptime)
|
||||
time.sleep(nextstart - os.times()[4])
|
||||
else:
|
||||
if options.csv:
|
||||
format_csv(csvwriter, orderedsensors, resdata, showtime=False)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python2
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015 Lenovo
|
||||
@@ -32,7 +32,7 @@ if path.startswith('/opt'):
|
||||
import confluent.client as client
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage='Usage: %prog [options] <noderange> [default|cd|network|setup|hd|usb|floppy]')
|
||||
usage='Usage: %prog [options] noderange [default|cd|network|setup|hd]')
|
||||
argparser.add_option('-b', '--bios', dest='biosmode',
|
||||
action='store_true', default=False,
|
||||
help='Request BIOS style boot (rather than UEFI)')
|
||||
@@ -43,21 +43,17 @@ argparser.add_option('-p', '--persist', dest='persist', action='store_true',
|
||||
argparser.add_option('-u', '--uefi', dest='uefi', action='store_true',
|
||||
default=True,
|
||||
help='Request UEFI style boot (rather than BIOS)')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to modify next boot device, '
|
||||
'prompting if over the threshold')
|
||||
|
||||
(options, args) = argparser.parse_args()
|
||||
|
||||
try:
|
||||
noderange = args[0]
|
||||
client.check_globbing(noderange)
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
bootdev = None
|
||||
if len(args) > 1:
|
||||
bootdev = args[1]
|
||||
if len(sys.argv) > 2:
|
||||
bootdev = sys.argv[2]
|
||||
if bootdev in ('net', 'pxe'):
|
||||
bootdev = 'network'
|
||||
session = client.Command()
|
||||
@@ -66,7 +62,6 @@ if options.biosmode:
|
||||
bootmode = 'bios'
|
||||
else:
|
||||
bootmode = 'uefi'
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
sys.exit(session.simple_noderange_command(noderange, '/boot/nextdevice', bootdev,
|
||||
bootmode=bootmode,
|
||||
persistent=options.persist))
|
||||
persistent=options.persist))
|
||||
+38
-114
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python2
|
||||
#!/usr/bin/env python
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2016-2017 Lenovo
|
||||
@@ -19,6 +19,7 @@ from collections import deque
|
||||
import optparse
|
||||
import os
|
||||
import select
|
||||
import shlex
|
||||
import signal
|
||||
import subprocess
|
||||
import sys
|
||||
@@ -33,41 +34,21 @@ if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
import confluent.sortutil as sortutil
|
||||
devnull = None
|
||||
|
||||
|
||||
def run():
|
||||
global devnull
|
||||
devnull = open(os.devnull, 'rb')
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog [options] noderange commandexpression",
|
||||
epilog="Expressions are the same as in attributes, e.g. "
|
||||
"'ipmitool -H {hardwaremanagement.manager}' will be expanded.")
|
||||
argparser.add_option('-f', '-c', '--count', type='int', default=168,
|
||||
help='Number of commands to run at a time')
|
||||
argparser.add_option('-l', '--loginname', type='str',
|
||||
help='Username to use when connecting, defaults to current user.')
|
||||
argparser.add_option('-n', '--nonodeprefix', action='store_true',
|
||||
help='Do not prefix output with node names')
|
||||
argparser.add_option('-p', '--port', type='int', default=0,
|
||||
help='Specify a custom port for ssh')
|
||||
argparser.add_option('-s', '--substitutename',
|
||||
help='Use a different name other than the nodename for ssh')
|
||||
argparser.add_option('-x', '--noexpression', action='store_true',
|
||||
help='Suppress expression expansion of command')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to run remote ssh command to, '
|
||||
'prompting if over the threshold')
|
||||
concurrentprocs = 168
|
||||
# among other things, FD_SETSIZE limits. Besides, spawning too many
|
||||
# processes can be unkind for the unaware on memory pressure and such...
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog node commandexpression",
|
||||
epilog="Expressions are the same as in attributes, e.g. "
|
||||
"'ipmitool -H {hardwaremanagement.manager}' will be expanded.")
|
||||
argparser.disable_interspersed_args()
|
||||
(options, args) = argparser.parse_args()
|
||||
if len(args) < 2:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(args[0])
|
||||
concurrentprocs = options.count
|
||||
c = client.Command()
|
||||
cmdstr = " ".join(args[1:])
|
||||
|
||||
@@ -75,97 +56,40 @@ def run():
|
||||
all = set([])
|
||||
pipedesc = {}
|
||||
pendingexecs = deque()
|
||||
exitcode = 0
|
||||
|
||||
c.stop_if_noderange_over(args[0], options.maxnodes)
|
||||
nodemap = {}
|
||||
if options.substitutename:
|
||||
subname = options.substitutename
|
||||
if '{' not in subname:
|
||||
subname = '{node}' + subname
|
||||
for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]),
|
||||
{'expression': subname}):
|
||||
if 'error' in exp:
|
||||
sys.stderr.write(exp['error'] + '\n')
|
||||
exitcode |= exp.get('errorcode', 1)
|
||||
ex = exp.get('databynode', ())
|
||||
for node in ex:
|
||||
nodemap[node] = ex[node]['value']
|
||||
cmdparms = []
|
||||
if options.noexpression:
|
||||
for res in c.read('/noderange/{0}/nodes/'.format(args[0])):
|
||||
if 'error' in res:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
exitcode |= res.get('errorcode', 1)
|
||||
break
|
||||
node = res['item']['href'][:-1]
|
||||
cmdparms.append((node, cmdstr))
|
||||
else:
|
||||
for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]),
|
||||
{'expression': cmdstr}):
|
||||
if 'error' in exp:
|
||||
sys.stderr.write(exp['error'] + '\n')
|
||||
exitcode |= exp.get('errorcode', 1)
|
||||
ex = exp.get('databynode', ())
|
||||
for node in ex:
|
||||
cmdparms.append((node, ex[node]['value']))
|
||||
for node, cmd in cmdparms:
|
||||
sshnode = nodemap.get(node, node)
|
||||
if not isinstance(cmd, str) and not isinstance(cmd, bytes):
|
||||
cmd = cmd.encode('utf-8')
|
||||
cmdv = ['ssh']
|
||||
if options.port:
|
||||
cmdv += ['-p', '{0}'.format(options.port)]
|
||||
if options.loginname:
|
||||
cmdv += ['-l', options.loginname]
|
||||
cmdv += [sshnode, cmd]
|
||||
if currprocs < concurrentprocs:
|
||||
currprocs += 1
|
||||
run_cmdv(node, cmdv, all, pipedesc)
|
||||
else:
|
||||
pendingexecs.append((node, cmdv))
|
||||
if not all or exitcode:
|
||||
sys.exit(exitcode)
|
||||
for exp in c.create('/noderange/{0}/attributes/expression'.format(args[0]),
|
||||
{'expression': cmdstr}):
|
||||
ex = exp['databynode']
|
||||
for node in ex:
|
||||
cmd = ex[node]['value'].encode('utf-8')
|
||||
cmdv = ['ssh', node] + shlex.split(cmd)
|
||||
if currprocs < concurrentprocs:
|
||||
currprocs += 1
|
||||
run_cmdv(node, cmdv, all, pipedesc)
|
||||
else:
|
||||
pendingexecs.append((node, cmdv))
|
||||
|
||||
exitcode = 0
|
||||
rdy, _, _ = select.select(all, [], [], 10)
|
||||
while all:
|
||||
pernodeout = {}
|
||||
for r in rdy:
|
||||
data = r.readline()
|
||||
desc = pipedesc[r]
|
||||
node = desc['node']
|
||||
data = True
|
||||
while data and select.select([r], [], [], 0)[0]:
|
||||
data = r.readline()
|
||||
if data:
|
||||
if desc['type'] == 'stdout':
|
||||
if node not in pernodeout:
|
||||
pernodeout[node] = []
|
||||
pernodeout[node].append(data)
|
||||
else:
|
||||
data = client.stringify(data)
|
||||
if options.nonodeprefix:
|
||||
sys.stderr.write(data)
|
||||
else:
|
||||
sys.stderr.write('{0}: {1}'.format(node, data))
|
||||
sys.stderr.flush()
|
||||
if data:
|
||||
node = desc['node']
|
||||
if desc['type'] == 'stdout':
|
||||
sys.stdout.write('{0}: {1}'.format(node,data))
|
||||
else:
|
||||
pop = desc['popen']
|
||||
ret = pop.poll()
|
||||
if ret is not None:
|
||||
exitcode = exitcode | ret
|
||||
all.discard(r)
|
||||
r.close()
|
||||
if desc['type'] == 'stdout' and pendingexecs:
|
||||
node, cmdv = pendingexecs.popleft()
|
||||
run_cmdv(node, cmdv, all, pipedesc)
|
||||
for node in sortutil.natural_sort(pernodeout):
|
||||
for line in pernodeout[node]:
|
||||
line = client.stringify(line)
|
||||
if options.nonodeprefix:
|
||||
sys.stdout.write(line)
|
||||
else:
|
||||
line = line.lstrip('\x08')
|
||||
sys.stdout.write('{0}: {1}'.format(node, line))
|
||||
sys.stdout.flush()
|
||||
sys.stderr.write('{0}: {1}'.format(node, data))
|
||||
else:
|
||||
pop = desc['popen']
|
||||
ret = pop.poll()
|
||||
if ret is not None:
|
||||
exitcode = exitcode | ret
|
||||
all.discard(r)
|
||||
if desc['type'] == 'stdout' and pendingexecs:
|
||||
node, cmdv = pendingexecs.popleft()
|
||||
run_cmdv(node, cmdv, all, pipedesc)
|
||||
if all:
|
||||
rdy, _, _ = select.select(all, [], [], 10)
|
||||
sys.exit(exitcode)
|
||||
@@ -173,7 +97,7 @@ def run():
|
||||
|
||||
def run_cmdv(node, cmdv, all, pipedesc):
|
||||
nopen = subprocess.Popen(
|
||||
cmdv, stdin=devnull, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
cmdv, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
pipedesc[nopen.stdout] = {'node': node, 'popen': nopen,
|
||||
'type': 'stdout'}
|
||||
pipedesc[nopen.stderr] = {'node': node, 'popen': nopen,
|
||||
@@ -183,4 +107,4 @@ def run_cmdv(node, cmdv, all, pipedesc):
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
run()
|
||||
run()
|
||||
@@ -1,250 +0,0 @@
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2018 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
|
||||
try:
|
||||
input = raw_input
|
||||
except NameError:
|
||||
pass
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
pass
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
exitcode = 0
|
||||
|
||||
class OptParser(optparse.OptionParser):
|
||||
|
||||
def format_epilog(self, formatter):
|
||||
return self.expand_prog_name(self.epilog)
|
||||
|
||||
def mbtohuman(mb):
|
||||
if mb > 1000000:
|
||||
return '{0:.3f} TB'.format(mb/1000000.0)
|
||||
if mb > 1000:
|
||||
return '{0:.3f} GB'.format(mb/1000.0)
|
||||
return '{0:.3f} MB'.format(mb)
|
||||
|
||||
def showstorage(noderange, options, args):
|
||||
global exitcode
|
||||
session = client.Command()
|
||||
disks = {}
|
||||
arrays = {}
|
||||
volumes = {}
|
||||
scfg = session.read('/noderange/{0}/configuration/storage/all'.format(
|
||||
noderange))
|
||||
_print_cfg(scfg)
|
||||
|
||||
|
||||
def _print_cfg(scfg):
|
||||
global exitcode
|
||||
storagebynode = {}
|
||||
for e in scfg:
|
||||
if 'error' in e:
|
||||
sys.stderr.write(e['error'] + '\n')
|
||||
exitcode = e.get('errorcode', 1)
|
||||
for node in e.get('databynode', {}):
|
||||
curr = e['databynode'][node]
|
||||
if 'error' in curr:
|
||||
if 'no available drives' in curr['error']:
|
||||
curr['error'] += ' (drives must be in unconfigured state to be available, they must not be in jbod or online state)'
|
||||
sys.stderr.write('{0}: {1}\n'.format(node, curr['error']))
|
||||
exitcode = curr.get('errorcode', 1)
|
||||
continue
|
||||
if node not in storagebynode:
|
||||
storagebynode[node] = {'disks': [], 'arrays': [],
|
||||
'volumes': []}
|
||||
storagebynode[node][curr['type'] + 's'].append(curr)
|
||||
for node in storagebynode:
|
||||
for disk in sorted(storagebynode[node]['disks'],
|
||||
key=lambda x: x['name']):
|
||||
print('{0}: Disk {1} Description: {2}'.format(
|
||||
node, disk['name'], disk['description']))
|
||||
print('{0}: Disk {1} State: {2}'.format(node, disk['name'],
|
||||
disk['state']))
|
||||
print('{0}: Disk {1} FRU: {2}'.format(node, disk['name'],
|
||||
disk['fru']))
|
||||
print('{0}: Disk {1} Serial Number: {2}'.format(node, disk['name'],
|
||||
disk['serial']))
|
||||
if disk['array']:
|
||||
print('{0}: Disk {1} Array: {2}'.format(node, disk['name'],
|
||||
disk['array']))
|
||||
for arr in storagebynode[node]['arrays']:
|
||||
print('{0}: Array {1} Available Capacity: {2}'.format(
|
||||
node, arr['id'], mbtohuman(arr['available'])))
|
||||
print('{0}: Array {1} Total Capacity: {2}'.format(
|
||||
node, arr['id'], mbtohuman(arr['capacity'])))
|
||||
print('{0}: Array {1} RAID: {2}'.format(node, arr['id'],
|
||||
arr['raid']))
|
||||
print('{0}: Array {1} Disks: {2}'.format(node, arr['id'], ','.join(
|
||||
arr['disks'])))
|
||||
print(
|
||||
'{0}: Array {1} Volumes: {2}'.format(node, arr['id'], ','.join(
|
||||
arr['volumes'])))
|
||||
for vol in storagebynode[node]['volumes']:
|
||||
print('{0}: Volume {1}: Size: {2}'.format(node, vol['name'],
|
||||
mbtohuman(vol['size'])))
|
||||
print('{0}: Volume {1}: State: {2}'.format(node, vol['name'],
|
||||
vol['state']))
|
||||
print('{0}: Volume {1}: Array {2}'.format(node, vol['name'],
|
||||
vol['array']))
|
||||
|
||||
|
||||
def createstorage(noderange, options, args):
|
||||
if options.raidlevel is None or options.disks is None:
|
||||
sys.stderr.write('-r and -d are required arguments to create array\n')
|
||||
sys.exit(1)
|
||||
session = client.Command()
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
names = options.name
|
||||
if names is None:
|
||||
names = ''.join(args)
|
||||
parms = {'disks': options.disks, 'raidlevel': options.raidlevel,
|
||||
'name': names}
|
||||
if options.size:
|
||||
parms['size'] = options.size
|
||||
if options.stripsizes:
|
||||
parms['stripsizes'] = options.stripsizes
|
||||
_print_cfg(session.create(
|
||||
'/noderange/{0}/configuration/storage/volumes/{1}'.format(
|
||||
noderange, names), parms))
|
||||
|
||||
|
||||
def deletestorage(noderange, options, args):
|
||||
if options.name is None:
|
||||
if len(args) == 1:
|
||||
names = args[0]
|
||||
else:
|
||||
sys.stderr.write('-n is required to indicate volume(s) to delete\n')
|
||||
sys.exit(1)
|
||||
else:
|
||||
names = options.name
|
||||
session = client.Command()
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
for rsp in session.delete(
|
||||
'/noderange/{0}/configuration/storage/volumes/{1}'.format(
|
||||
noderange, names)):
|
||||
if 'deleted' in rsp:
|
||||
print('Deleted: {0}'.format(rsp['deleted']))
|
||||
elif 'databynode' in rsp:
|
||||
for node in rsp['databynode']:
|
||||
if 'error' in rsp['databynode'][node]:
|
||||
sys.stderr.write('{0}: {1}\n'.format(
|
||||
node, rsp['databynode'][node]['error']))
|
||||
else:
|
||||
sys.stderr.write('{0}: {1}\n'.format(
|
||||
node, repr(rsp['databynode'][node])))
|
||||
else:
|
||||
print(repr(rsp))
|
||||
|
||||
|
||||
def setdisk(noderange, options, args):
|
||||
if options.disks is None:
|
||||
if len(args):
|
||||
names = args.pop(0)
|
||||
else:
|
||||
sys.stderr.write('-d is required to indicate disk to modify\n')
|
||||
sys.exit(1)
|
||||
else:
|
||||
names = options.disks
|
||||
if not len(args) or args[0] not in ('hotspare', 'jbod', 'unconfigured'):
|
||||
sys.stderr.write('diskset requires valid state as argument (hotspare, jbod, unconfigured)\n')
|
||||
sys.exit(1)
|
||||
session = client.Command()
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
scfg = session.update('/noderange/{0}/configuration/storage/disks/{1}'.format(noderange, names), {'state': args[0]})
|
||||
_print_cfg(scfg)
|
||||
|
||||
funmap = {
|
||||
'create': createstorage,
|
||||
'show': showstorage,
|
||||
'diskset': setdisk,
|
||||
'delete': deletestorage,
|
||||
'rm': deletestorage,
|
||||
}
|
||||
|
||||
|
||||
|
||||
def main():
|
||||
argparser = OptParser(
|
||||
usage='Usage: %prog <noderange> [show|create|delete|diskset] [hotspare|jbod|unconfigured] [options]',
|
||||
epilog='',
|
||||
)
|
||||
argparser.add_option('-r', '--raidlevel', type='int',
|
||||
help='RAID level to use when creating an array')
|
||||
argparser.add_option('-d', '--disks', type='str',
|
||||
help='Comma separated list of disks to use, or the '
|
||||
'word "rest" to indicate use of all available '
|
||||
'disks')
|
||||
argparser.add_option('-i', '--interactive', default=False, action='store_true',
|
||||
help='Confirm any potentially destructive changes interactively')
|
||||
argparser.add_option('-s', '--size', type='str',
|
||||
help='Comma separated list of sizes to use when '
|
||||
'creating volumes. The sizes may be absolute '
|
||||
'size (e.g. 16gb), percentage (10%) or the word '
|
||||
'"rest" to use remaining capacity, default '
|
||||
'behavior is to use all capacity to make a '
|
||||
'volume')
|
||||
argparser.add_option('-n', '--name', type='str',
|
||||
help='Comma separated list of names to use when '
|
||||
'naming volumes, or selecting a volume for '
|
||||
'delete. Default behavior is to use '
|
||||
'implementation provided default names.')
|
||||
argparser.add_option('-z', '--stripsizes', type='str',
|
||||
help='Comma separated list of stripsizes to use when creating volumes. '
|
||||
'This value is in kilobytes. The default behavior is to allow the '
|
||||
'storage controller to decide.')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to configure storage on, '
|
||||
'prompting if over the threshold')
|
||||
(options, args) = argparser.parse_args()
|
||||
if len(args) == 1:
|
||||
args.append('show')
|
||||
try:
|
||||
noderange = args[0]
|
||||
operation = args[1]
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
try:
|
||||
handler = funmap[operation]
|
||||
except KeyError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
if options.interactive and operation != 'show':
|
||||
showstorage(noderange, None, None)
|
||||
sys.stdout.write('The above configuration will be changed by this action, do you wish to continue (y/N): ')
|
||||
confirm = input()
|
||||
if confirm and confirm.lower()[0] != 'y':
|
||||
sys.stdout.write('Aborting\n')
|
||||
sys.exit(1)
|
||||
handler(noderange, options, args[2:])
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,152 +0,0 @@
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2018 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
import time
|
||||
|
||||
try:
|
||||
signal.signal(signal.SIGPIPE, signal.SIG_DFL)
|
||||
except AttributeError:
|
||||
pass
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
import confluent.screensqueeze as sq
|
||||
|
||||
exitcode = 0
|
||||
|
||||
|
||||
def get_update_progress(session, url):
|
||||
for res in session.read(url):
|
||||
status = res['phase']
|
||||
percent = res['progress']
|
||||
detail = res['detail']
|
||||
if status == 'error':
|
||||
text = 'error!'
|
||||
else:
|
||||
text = '{0}: {1:3.0f}%'.format(status, percent)
|
||||
return text, status, detail
|
||||
|
||||
|
||||
def printerror(res, node=None):
|
||||
global exitcode
|
||||
if 'errorcode' in res:
|
||||
exitcode = res['errorcode']
|
||||
if 'error' in res:
|
||||
if node:
|
||||
sys.stderr.write('{0}: {1}\n'.format(node, res['error']))
|
||||
else:
|
||||
sys.stderr.write('{0}\n'.format(res['error']))
|
||||
if 'errorcode' not in res:
|
||||
exitcode = 1
|
||||
for node in res.get('databynode', ()):
|
||||
printerror(res['databynode'][node], node)
|
||||
|
||||
|
||||
|
||||
def download_servicedata(noderange, media, options):
|
||||
global exitcode
|
||||
session = client.Command()
|
||||
output = sq.ScreenPrinter(noderange, session)
|
||||
filename = os.path.abspath(media)
|
||||
resource = '/noderange/{0}/support/servicedata/'.format(noderange)
|
||||
upargs = {'filename': filename}
|
||||
noderrs = {}
|
||||
nodeurls = {}
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
for res in session.create(resource, upargs):
|
||||
if 'created' not in res:
|
||||
for nodename in res.get('databynode', ()):
|
||||
output.set_output(nodename, 'error!')
|
||||
noderrs[nodename] = res['databynode'][nodename].get(
|
||||
'error', 'Unknown Error')
|
||||
continue
|
||||
watchurl = res['created']
|
||||
currnode = watchurl.split('/')[1]
|
||||
nodeurls[currnode] = '/' + watchurl
|
||||
while nodeurls:
|
||||
for node in list(nodeurls):
|
||||
progress, status, err = get_update_progress(
|
||||
session, nodeurls[node])
|
||||
if status == 'error':
|
||||
exitcode = 1
|
||||
noderrs[node] = err
|
||||
if status in ('error', 'complete', 'pending'):
|
||||
list(session.delete(nodeurls[node]))
|
||||
del nodeurls[node]
|
||||
output.set_output(node, progress)
|
||||
time.sleep(2)
|
||||
allerrnodes = ','.join(noderrs)
|
||||
if noderrs:
|
||||
sys.stderr.write(
|
||||
'Nodes had errors retrieving service data ({0})!\n'.format(allerrnodes))
|
||||
for node in noderrs:
|
||||
sys.stderr.write('{0}: {1}\n'.format(node, noderrs[node]))
|
||||
|
||||
funmap = {
|
||||
'servicedata': download_servicedata,
|
||||
}
|
||||
|
||||
|
||||
class OptParser(optparse.OptionParser):
|
||||
|
||||
def format_epilog(self, formatter):
|
||||
return self.expand_prog_name(self.epilog)
|
||||
|
||||
def main():
|
||||
argparser = OptParser(
|
||||
usage="Usage: %prog <noderange> servicedata "
|
||||
"<directory or filename>",
|
||||
epilog='\nservicedata will save service data to the given '
|
||||
'directory. It is saved to the location on the relevant '
|
||||
'management server (the confluent server if running remote, '
|
||||
'and the collective.manager if in collective)\n'
|
||||
'\n\nSee `man %prog` for more info.\n')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to download diagnostic data from, '
|
||||
'prompting if over the threshold')
|
||||
(options, args) = argparser.parse_args()
|
||||
media = None
|
||||
try:
|
||||
noderange = args[0]
|
||||
operation = args[1]
|
||||
arglength = 2
|
||||
if operation == 'servicedata':
|
||||
media = args[2]
|
||||
arglength = 3
|
||||
if len(args) > arglength:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
try:
|
||||
handler = funmap[operation]
|
||||
except KeyError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
handler(noderange, media, options)
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
@@ -1,200 +0,0 @@
|
||||
#!/usr/bin/python2
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2019 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
import csv
|
||||
import io
|
||||
import numpy as np
|
||||
import sys
|
||||
|
||||
try:
|
||||
import sixel
|
||||
|
||||
class DumbWriter(sixel.SixelWriter):
|
||||
def restore_position(self, output):
|
||||
return
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
||||
def iterm_draw(data):
|
||||
databuf = data.getbuffer()
|
||||
datalen = len(databuf)
|
||||
data = base64.b64encode(databuf).decode('utf8')
|
||||
sys.stdout.write(
|
||||
'\x1b]1337;File=inline=1;size={}:'.format(datalen))
|
||||
sys.stdout.write(data)
|
||||
sys.stdout.write('\a')
|
||||
sys.stdout.write('\n')
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def kitty_draw(data):
|
||||
data = base64.b64encode(data.getbuffer())
|
||||
while data:
|
||||
chunk, data = data[:4096], data[4096:]
|
||||
m = 1 if data else 0
|
||||
sys.stdout.write('\x1b_Ga=T,f=100,m={};'.format(m))
|
||||
sys.stdout.write(chunk.decode('utf8'))
|
||||
sys.stdout.write('\x1b\\')
|
||||
sys.stdout.flush()
|
||||
sys.stdout.write('\n')
|
||||
|
||||
|
||||
def plot(gui, output, plotdata, bins, fmt):
|
||||
import matplotlib as mpl
|
||||
if gui and mpl.get_backend() == 'agg':
|
||||
sys.stderr.write('Error: No GUI backend available and -g specified!\n')
|
||||
if not gui:
|
||||
mpl.use('Agg')
|
||||
import matplotlib.pyplot as plt
|
||||
n, bins, patches = plt.hist(plotdata, bins)
|
||||
plt.show()
|
||||
if not gui:
|
||||
if output:
|
||||
tdata = output
|
||||
else:
|
||||
tdata = io.BytesIO()
|
||||
plt.savefig(tdata)
|
||||
if not gui and not output:
|
||||
if fmt == 'sixel':
|
||||
writer = DumbWriter()
|
||||
writer.draw(tdata)
|
||||
elif fmt == 'kitty':
|
||||
kitty_draw(tdata)
|
||||
elif fmt == 'iterm':
|
||||
iterm_draw(tdata)
|
||||
return n, bins
|
||||
|
||||
def textplot(plotdata, bins):
|
||||
n, bins = np.histogram(plotdata, bins)
|
||||
labels = []
|
||||
for bin in bins:
|
||||
labels.append('{0:0.1f}'.format(bin))
|
||||
width = 80
|
||||
# Since this will be primarily piped into, hard to get
|
||||
# terminal width
|
||||
labelwidth = 0
|
||||
for lab in labels:
|
||||
if len(lab) > labelwidth:
|
||||
labelwidth = len(lab)
|
||||
width -= (labelwidth) + 1
|
||||
labelfmt = '{{0:>{0}s}}|'.format(labelwidth)
|
||||
maxn = 0.0
|
||||
for lgth in n:
|
||||
if lgth > maxn:
|
||||
maxn = float(lgth)
|
||||
for i in range(len(n)):
|
||||
print(labelfmt.format(labels[i]) + '=' * int(np.round((n[i]/maxn) * width)))
|
||||
return n, bins
|
||||
|
||||
histogram = False
|
||||
aparser = argparse.ArgumentParser(description='Quick access to common statistics')
|
||||
aparser.add_argument('-c', type=int, default=0, help='Column number to analyze (default is last column)')
|
||||
aparser.add_argument('-d', default=None, help='Value used to separate columns')
|
||||
aparser.add_argument('-x', default=False, action='store_true', help='Output histogram in graphical format')
|
||||
aparser.add_argument('-f', default='sixel', help='Format for histogram output (sixel/iterm/kitty)')
|
||||
aparser.add_argument('-s', default=0, help='Number of header lines to skip before processing')
|
||||
aparser.add_argument('-g', default=False, action='store_true', help='Open histogram in separate graphical window')
|
||||
aparser.add_argument('-o', default=None, help='Output histogram to the specified filename in PNG format')
|
||||
aparser.add_argument('-t', default=False, action='store_true', help='Output a histogram in text format')
|
||||
aparser.add_argument('-v', default=False, action='store_true', help='Attempt to list nodes relevant to each histogram bar (requires -s, -o, or -t)')
|
||||
aparser.add_argument('-b', type=int, default=10, help='Number of bins to use in histogram (default is 10)')
|
||||
args = aparser.parse_args(sys.argv[1:])
|
||||
plotdata = []
|
||||
headlines = int(args.s)
|
||||
while headlines >= 0:
|
||||
data = sys.stdin.readline()
|
||||
headlines -= 1
|
||||
if args.d:
|
||||
delimiter = args.d
|
||||
else:
|
||||
if '\t' in data:
|
||||
delimiter = '\t'
|
||||
elif ' ' in data:
|
||||
delimiter = ' '
|
||||
elif ',' in data:
|
||||
delimiter = ','
|
||||
else:
|
||||
delimiter = ' ' # handle single column
|
||||
data = list(csv.reader([data], delimiter=delimiter))[0]
|
||||
nodebydatum = {}
|
||||
idx = args.c - 1
|
||||
autoidx = False
|
||||
while data:
|
||||
node = None
|
||||
if ':' in data[0]:
|
||||
node, data[0] = data[0].split(':', 1)
|
||||
else:
|
||||
node = data[0]
|
||||
if idx == -1 and not autoidx:
|
||||
while not autoidx:
|
||||
try:
|
||||
datum = float(data[idx])
|
||||
except ValueError:
|
||||
idx -= 1
|
||||
continue
|
||||
except IndexError:
|
||||
sys.stderr.write('Unable to identify a numerical column\n')
|
||||
sys.exit(1)
|
||||
autoidx = True
|
||||
else:
|
||||
datum = float(data[idx])
|
||||
if node:
|
||||
if datum in nodebydatum:
|
||||
nodebydatum[datum].add(node)
|
||||
else:
|
||||
nodebydatum[datum] = set([node])
|
||||
plotdata.append(datum)
|
||||
data = sys.stdin.readline()
|
||||
data = list(csv.reader([data], delimiter=delimiter))[0]
|
||||
n = None
|
||||
if args.g or args.o or args.x:
|
||||
n, bins = plot(args.g, args.o, plotdata, bins=args.b, fmt=args.f)
|
||||
if args.t:
|
||||
n, bins = textplot(plotdata, bins=args.b)
|
||||
print('Samples: {5} Min: {3} Median: {0} Mean: {1} Max: {4} StandardDeviation: {2} Sum: {6}'.format(np.median(plotdata), np.mean(plotdata), np.std(plotdata), np.min(plotdata), np.max(plotdata), len(plotdata), np.sum(plotdata)))
|
||||
if args.v and n is not None and nodebydatum:
|
||||
print('')
|
||||
currbin = bins[0]
|
||||
bins = bins[1:]
|
||||
currbinmembers = []
|
||||
for datum in sorted(nodebydatum):
|
||||
if datum > bins[0]:
|
||||
nextbin = None
|
||||
endbin = bins[0]
|
||||
while len(bins) and bins[0] < datum:
|
||||
nextbin = bins[0]
|
||||
bins = bins[1:]
|
||||
if not nextbin:
|
||||
nextbin = np.max(plotdata)
|
||||
print('Entries between {0} and {1}'.format(currbin, endbin))
|
||||
currbin = nextbin
|
||||
print('-' * 80)
|
||||
print(','.join(sorted(currbinmembers)))
|
||||
print('')
|
||||
print('')
|
||||
currbinmembers = []
|
||||
for node in nodebydatum[datum]:
|
||||
currbinmembers.append(node)
|
||||
if currbinmembers:
|
||||
print('Entries between {0} and {1}'.format(currbin, np.max(plotdata)))
|
||||
print('-' * 80)
|
||||
print(','.join(sorted(currbinmembers)))
|
||||
print('')
|
||||
print('')
|
||||
@@ -1 +0,0 @@
|
||||
../confluent_server/builddeb
|
||||
@@ -1,7 +1,7 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2014 IBM Corporation
|
||||
# Copyright 2015-2019 Lenovo
|
||||
# Copyright 2015-2016 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -15,131 +15,17 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
try:
|
||||
import anydbm as dbm
|
||||
except ImportError:
|
||||
import dbm
|
||||
import csv
|
||||
import anydbm as dbm
|
||||
import errno
|
||||
import fnmatch
|
||||
import hashlib
|
||||
import os
|
||||
import shlex
|
||||
import socket
|
||||
import ssl
|
||||
import sys
|
||||
import confluent.tlvdata as tlvdata
|
||||
import confluent.sortutil as sortutil
|
||||
|
||||
SO_PASSCRED = 16
|
||||
|
||||
_attraliases = {
|
||||
'bmc': 'hardwaremanagement.manager',
|
||||
'bmcuser': 'secret.hardwaremanagementuser',
|
||||
'switchuser': 'secret.hardwaremanagementuser',
|
||||
'bmcpass': 'secret.hardwaremanagementpassword',
|
||||
'switchpass': 'secret.hardwaremanagementpassword',
|
||||
}
|
||||
|
||||
try:
|
||||
getinput = raw_input
|
||||
except NameError:
|
||||
getinput = input
|
||||
|
||||
|
||||
class NestedDict(dict):
|
||||
def __missing__(self, key):
|
||||
value = self[key] = type(self)()
|
||||
return value
|
||||
|
||||
|
||||
def stringify(instr):
|
||||
# Normalize unicode and bytes to 'str', correcting for
|
||||
# current python version
|
||||
if isinstance(instr, bytes) and not isinstance(instr, str):
|
||||
return instr.decode('utf-8')
|
||||
elif not isinstance(instr, bytes) and not isinstance(instr, str):
|
||||
return instr.encode('utf-8')
|
||||
return instr
|
||||
|
||||
class Tabulator(object):
|
||||
def __init__(self, headers):
|
||||
self.headers = headers
|
||||
self.rows = []
|
||||
|
||||
def add_row(self, row):
|
||||
self.rows.append(row)
|
||||
|
||||
def get_table(self, order=None):
|
||||
i = 0
|
||||
fmtstr = ''
|
||||
separator = []
|
||||
for head in self.headers:
|
||||
if order and order == head:
|
||||
order = i
|
||||
neededlen = len(head)
|
||||
for row in self.rows:
|
||||
if len(row[i]) > neededlen:
|
||||
neededlen = len(row[i])
|
||||
separator.append('-' * (neededlen + 1))
|
||||
fmtstr += '{{{0}:>{1}}}|'.format(i, neededlen + 1)
|
||||
i = i + 1
|
||||
fmtstr = fmtstr[:-1]
|
||||
yield fmtstr.format(*self.headers)
|
||||
yield fmtstr.format(*separator)
|
||||
if order is not None:
|
||||
for row in sorted(
|
||||
self.rows,
|
||||
key=lambda x: sortutil.naturalize_string(x[order])):
|
||||
yield fmtstr.format(*row)
|
||||
else:
|
||||
for row in self.rows:
|
||||
yield fmtstr.format(*row)
|
||||
|
||||
def write_csv(self, output, order=None):
|
||||
output = csv.writer(output)
|
||||
output.writerow(self.headers)
|
||||
i = 0
|
||||
for head in self.headers:
|
||||
if order and order == head:
|
||||
order = i
|
||||
i = i + 1
|
||||
if order is not None:
|
||||
for row in sorted(
|
||||
self.rows,
|
||||
key=lambda x: sortutil.naturalize_string(x[order])):
|
||||
output.writerow(row)
|
||||
else:
|
||||
for row in self.rows:
|
||||
output.writerow(row)
|
||||
|
||||
|
||||
def printerror(res, node=None):
|
||||
exitcode = 0
|
||||
if 'errorcode' in res:
|
||||
exitcode = res['errorcode']
|
||||
for node in res.get('databynode', {}):
|
||||
exitcode = res['databynode'][node].get('errorcode', exitcode)
|
||||
if 'error' in res['databynode'][node]:
|
||||
sys.stderr.write('{0}: {1}\n'.format(node, res['databynode'][node]['error']))
|
||||
if exitcode == 0:
|
||||
exitcode = 1
|
||||
if 'error' in res:
|
||||
if node:
|
||||
sys.stderr.write('{0}: {1}\n'.format(node, res['error']))
|
||||
else:
|
||||
sys.stderr.write('{0}\n'.format(res['error']))
|
||||
if 'errorcode' not in res:
|
||||
exitcode = 1
|
||||
return exitcode
|
||||
|
||||
|
||||
def cprint(txt):
|
||||
try:
|
||||
print(txt)
|
||||
except UnicodeEncodeError:
|
||||
print(txt.encode('utf8'))
|
||||
sys.stdout.flush()
|
||||
|
||||
def _parseserver(string):
|
||||
if ']:' in string:
|
||||
@@ -157,11 +43,8 @@ def _parseserver(string):
|
||||
|
||||
class Command(object):
|
||||
def __init__(self, server=None):
|
||||
self._prevdict = None
|
||||
self._prevkeyname = None
|
||||
self.connection = None
|
||||
self._currnoderange = None
|
||||
self.unixdomain = False
|
||||
if server is None:
|
||||
if 'CONFLUENT_HOST' in os.environ:
|
||||
self.serverloc = os.environ['CONFLUENT_HOST']
|
||||
@@ -171,13 +54,9 @@ class Command(object):
|
||||
self.serverloc = server
|
||||
if os.path.isabs(self.serverloc) and os.path.exists(self.serverloc):
|
||||
self._connect_unix()
|
||||
self.unixdomain = True
|
||||
elif self.serverloc == '/var/run/confluent/api.sock':
|
||||
raise Exception('Confluent service is not available')
|
||||
else:
|
||||
self._connect_tls()
|
||||
self.protversion = int(tlvdata.recv(self.connection).split(
|
||||
b'--')[1].strip()[1:])
|
||||
tlvdata.recv(self.connection)
|
||||
authdata = tlvdata.recv(self.connection)
|
||||
if authdata['authpassed'] == 1:
|
||||
self.authenticated = True
|
||||
@@ -188,13 +67,6 @@ class Command(object):
|
||||
passphrase = os.environ['CONFLUENT_PASSPHRASE']
|
||||
self.authenticate(username, passphrase)
|
||||
|
||||
def add_file(self, name, handle, mode):
|
||||
if self.protversion < 3:
|
||||
raise Exception('Not supported with connected confluent server')
|
||||
if not self.unixdomain:
|
||||
raise Exception('Can only add a file to a unix domain connection')
|
||||
tlvdata.send(self.connection, {'filename': name, 'mode': mode}, handle)
|
||||
|
||||
def authenticate(self, username, password):
|
||||
tlvdata.send(self.connection,
|
||||
{'username': username, 'password': password})
|
||||
@@ -205,13 +77,8 @@ class Command(object):
|
||||
def add_precede_key(self, keyname):
|
||||
self._prevkeyname = keyname
|
||||
|
||||
def add_precede_dict(self, dict):
|
||||
self._prevdict = dict
|
||||
|
||||
def handle_results(self, ikey, rc, res, errnodes=None, outhandler=None):
|
||||
def handle_results(self, ikey, rc, res):
|
||||
if 'error' in res:
|
||||
if errnodes is not None:
|
||||
errnodes.add(self._currnoderange)
|
||||
sys.stderr.write('Error: {0}\n'.format(res['error']))
|
||||
if 'errorcode' in res:
|
||||
return res['errorcode']
|
||||
@@ -222,8 +89,6 @@ class Command(object):
|
||||
res = res['databynode']
|
||||
for node in res:
|
||||
if 'error' in res[node]:
|
||||
if errnodes is not None:
|
||||
errnodes.add(node)
|
||||
sys.stderr.write('{0}: Error: {1}\n'.format(
|
||||
node, res[node]['error']))
|
||||
if 'errorcode' in res[node]:
|
||||
@@ -238,21 +103,15 @@ class Command(object):
|
||||
else:
|
||||
val = repr(res[node][ikey])
|
||||
if self._prevkeyname and self._prevkeyname in res[node]:
|
||||
cprint('{0}: {2}->{1}'.format(
|
||||
print('{0}: {2}->{1}'.format(
|
||||
node, val, res[node][self._prevkeyname]['value']))
|
||||
elif self._prevdict and node in self._prevdict:
|
||||
cprint('{0}: {2}->{1}'.format(
|
||||
node, val, self._prevdict[node]))
|
||||
else:
|
||||
cprint('{0}: {1}'.format(node, val))
|
||||
elif outhandler:
|
||||
outhandler(node, res)
|
||||
print('{0}: {1}'.format(node, val))
|
||||
return rc
|
||||
|
||||
def simple_noderange_command(self, noderange, resource, input=None,
|
||||
key=None, errnodes=None, promptover=None, outhandler=None, **kwargs):
|
||||
key=None, **kwargs):
|
||||
try:
|
||||
self._currnoderange = noderange
|
||||
rc = 0
|
||||
if resource[0] == '/':
|
||||
resource = resource[1:]
|
||||
@@ -264,45 +123,16 @@ class Command(object):
|
||||
if input is None:
|
||||
for res in self.read('/noderange/{0}/{1}'.format(
|
||||
noderange, resource)):
|
||||
rc = self.handle_results(ikey, rc, res, errnodes, outhandler)
|
||||
rc = self.handle_results(ikey, rc, res)
|
||||
else:
|
||||
self.stop_if_noderange_over(noderange, promptover)
|
||||
kwargs[ikey] = input
|
||||
for res in self.update('/noderange/{0}/{1}'.format(
|
||||
noderange, resource), kwargs):
|
||||
rc = self.handle_results(ikey, rc, res, errnodes, outhandler)
|
||||
self._currnoderange = None
|
||||
rc = self.handle_results(ikey, rc, res)
|
||||
return rc
|
||||
except KeyboardInterrupt:
|
||||
cprint('')
|
||||
print('')
|
||||
return 0
|
||||
|
||||
def stop_if_noderange_over(self, noderange, maxnodes):
|
||||
if maxnodes is None:
|
||||
return
|
||||
nsize = self.get_noderange_size(noderange)
|
||||
if nsize > maxnodes:
|
||||
if nsize == 1:
|
||||
nodename = list(self.read(
|
||||
'/noderange/{0}/nodes/'.format(noderange)))[0].get('item', {}).get('href', None)
|
||||
nodename = nodename[:-1]
|
||||
p = getinput('Command is about to affect node {0}, continue (y/n)? '.format(nodename))
|
||||
else:
|
||||
p = getinput('Command is about to affect {0} nodes, continue (y/n)? '.format(nsize))
|
||||
if p.lower() != 'y':
|
||||
sys.stderr.write('Aborting at user request\n')
|
||||
sys.exit(1)
|
||||
raise Exception("Aborting at user request")
|
||||
|
||||
|
||||
def get_noderange_size(self, noderange):
|
||||
numnodes = 0
|
||||
for node in self.read('/noderange/{0}/nodes/'.format(noderange)):
|
||||
if node.get('item', {}).get('href', None):
|
||||
numnodes += 1
|
||||
else:
|
||||
raise Exception("Error trying to size noderange {0}".format(noderange))
|
||||
return numnodes
|
||||
|
||||
def simple_nodegroups_command(self, noderange, resource, input=None, key=None, **kwargs):
|
||||
try:
|
||||
@@ -325,7 +155,7 @@ class Command(object):
|
||||
rc = self.handle_results(ikey, rc, res)
|
||||
return rc
|
||||
except KeyboardInterrupt:
|
||||
cprint('')
|
||||
print('')
|
||||
return 0
|
||||
|
||||
def read(self, path, parameters=None):
|
||||
@@ -392,22 +222,22 @@ class Command(object):
|
||||
certreqs = ssl.CERT_NONE
|
||||
knownhosts = True
|
||||
self.connection = ssl.wrap_socket(self.connection, ca_certs=cacert,
|
||||
cert_reqs=certreqs)
|
||||
cert_reqs=certreqs,
|
||||
ssl_version=ssl.PROTOCOL_TLSv1)
|
||||
if knownhosts:
|
||||
certdata = self.connection.getpeercert(binary_form=True)
|
||||
fingerprint = 'sha512$' + hashlib.sha512(certdata).hexdigest()
|
||||
fingerprint = fingerprint.encode('utf-8')
|
||||
hostid = '@'.join((port, server))
|
||||
khf = dbm.open(os.path.join(clientcfgdir, "knownhosts"), 'c', 384)
|
||||
if hostid in khf:
|
||||
if fingerprint == khf[hostid]:
|
||||
return
|
||||
else:
|
||||
replace = getinput(
|
||||
replace = raw_input(
|
||||
"MISMATCHED CERTIFICATE DATA, ACCEPT NEW? (y/n):")
|
||||
if replace not in ('y', 'Y'):
|
||||
raise Exception("BAD CERTIFICATE")
|
||||
cprint('Adding new key for %s:%s' % (server, port))
|
||||
print 'Adding new key for %s:%s' % (server, port)
|
||||
khf[hostid] = fingerprint
|
||||
|
||||
|
||||
@@ -436,82 +266,47 @@ def send_request(operation, path, server, parameters=None):
|
||||
result = tlvdata.recv(server)
|
||||
|
||||
|
||||
def attrrequested(attr, attrlist, seenattributes, node=None):
|
||||
def attrrequested(attr, attrlist, seenattributes):
|
||||
for candidate in attrlist:
|
||||
truename = candidate
|
||||
if candidate.startswith('hm'):
|
||||
candidate = candidate.replace('hm', 'hardwaremanagement', 1)
|
||||
if candidate in _attraliases:
|
||||
candidate = _attraliases[candidate]
|
||||
if fnmatch.fnmatch(attr.lower(), candidate.lower()):
|
||||
if node is None:
|
||||
seenattributes.add(truename)
|
||||
else:
|
||||
seenattributes[node][truename] = True
|
||||
if candidate == attr:
|
||||
seenattributes.add(truename)
|
||||
return True
|
||||
elif attr.lower().startswith(candidate.lower() + '.'):
|
||||
if node is None:
|
||||
seenattributes.add(truename)
|
||||
else:
|
||||
seenattributes[node][truename] = 1
|
||||
elif attr.startswith(candidate + '.'):
|
||||
seenattributes.add(truename)
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def printattributes(session, requestargs, showtype, nodetype, noderange, options):
|
||||
path = '/{0}/{1}/attributes/{2}'.format(nodetype, noderange, showtype)
|
||||
return print_attrib_path(path, session, requestargs, options)
|
||||
|
||||
def _sort_attrib(k):
|
||||
if isinstance(k[1], dict) and k[1].get('sortid', None) is not None:
|
||||
return k[1]['sortid']
|
||||
return k[0]
|
||||
|
||||
def print_attrib_path(path, session, requestargs, options, rename=None, attrprefix=None):
|
||||
exitcode = 0
|
||||
seenattributes = NestedDict()
|
||||
allnodes = set([])
|
||||
for res in session.read(path):
|
||||
seenattributes = set([])
|
||||
for res in session.read('/{0}/{1}/attributes/{2}'.format(nodetype, noderange, showtype)):
|
||||
if 'error' in res:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
exitcode = 1
|
||||
continue
|
||||
for node in sorted(res['databynode']):
|
||||
allnodes.add(node)
|
||||
for attr, val in sorted(res['databynode'][node].items(), key=_sort_attrib):
|
||||
if attr == 'error':
|
||||
sys.stderr.write('{0}: Error: {1}\n'.format(node, val))
|
||||
continue
|
||||
if attr == 'errorcode':
|
||||
exitcode |= val
|
||||
continue
|
||||
seenattributes[node][attr] = True
|
||||
if rename:
|
||||
printattr = rename.get(attr, attr)
|
||||
else:
|
||||
printattr = attr
|
||||
if attrprefix:
|
||||
printattr = attrprefix + printattr
|
||||
for node in res['databynode']:
|
||||
for attr in res['databynode'][node]:
|
||||
seenattributes.add(attr)
|
||||
currattr = res['databynode'][node][attr]
|
||||
if show_attr(attr, requestargs, seenattributes, options, node):
|
||||
if (requestargs is None or requestargs == [] or attrrequested(attr, requestargs, seenattributes)):
|
||||
if 'value' in currattr:
|
||||
if currattr['value'] is not None:
|
||||
val = currattr['value']
|
||||
if isinstance(val, list):
|
||||
val = ','.join(val)
|
||||
attrout = '{0}: {1}: {2}'.format(
|
||||
node, printattr, val).strip()
|
||||
node, attr, currattr['value'])
|
||||
else:
|
||||
attrout = '{0}: {1}:'.format(node, printattr)
|
||||
attrout = '{0}: {1}:'.format(node, attr)
|
||||
elif 'isset' in currattr:
|
||||
if currattr['isset']:
|
||||
attrout = '{0}: {1}: ********'.format(node,
|
||||
printattr)
|
||||
attrout = '{0}: {1}: ********'.format(node, attr)
|
||||
else:
|
||||
attrout = '{0}: {1}:'.format(node, printattr)
|
||||
elif isinstance(currattr, dict) and 'broken' in currattr:
|
||||
attrout = '{0}: {1}:'.format(node, attr)
|
||||
elif 'broken' in currattr:
|
||||
attrout = '{0}: {1}: *ERROR* BROKEN EXPRESSION: ' \
|
||||
'{2}'.format(node, printattr,
|
||||
'{2}'.format(node, attr,
|
||||
currattr['broken'])
|
||||
elif isinstance(currattr, list) or isinstance(currattr, tuple):
|
||||
attrout = '{0}: {1}: {2}'.format(node, attr, ','.join(map(str, currattr)))
|
||||
@@ -519,14 +314,11 @@ def print_attrib_path(path, session, requestargs, options, rename=None, attrpref
|
||||
dictout = []
|
||||
for k, v in currattr.items:
|
||||
dictout.append("{0}={1}".format(k, v))
|
||||
attrout = '{0}: {1}: {2}'.format(node, printattr, ','.join(map(str, dictout)))
|
||||
attrout = '{0}: {1}: {2}'.format(node, attr, ','.join(map(str, dictout)))
|
||||
else:
|
||||
cprint("CODE ERROR" + repr(attr))
|
||||
try:
|
||||
blame = options.blame
|
||||
except AttributeError:
|
||||
blame = False
|
||||
if blame or (isinstance(currattr, dict) and 'broken' in currattr):
|
||||
print ("CODE ERROR" + repr(attr))
|
||||
|
||||
if options.blame or 'broken' in currattr:
|
||||
blamedata = []
|
||||
if 'inheritedfrom' in currattr:
|
||||
blamedata.append('inherited from group {0}'.format(
|
||||
@@ -538,80 +330,16 @@ def print_attrib_path(path, session, requestargs, options, rename=None, attrpref
|
||||
currattr['expression']))
|
||||
if blamedata:
|
||||
attrout += ' (' + ', '.join(blamedata) + ')'
|
||||
try:
|
||||
comparedefault = options.comparedefault
|
||||
except AttributeError:
|
||||
comparedefault = False
|
||||
if comparedefault:
|
||||
try:
|
||||
exclude = options.exclude
|
||||
except AttributeError:
|
||||
exclude = False
|
||||
if ((requestargs and not exclude) or
|
||||
(currattr.get('default', None) is not None and
|
||||
currattr.get('value', None) is not None and
|
||||
currattr['value'] != currattr['default'])):
|
||||
cval = ','.join(currattr['value']) if isinstance(
|
||||
currattr['value'], list) else currattr['value']
|
||||
dval = ','.join(currattr['default']) if isinstance(
|
||||
currattr['default'], list) else currattr['default']
|
||||
cprint('{0}: {1}: {2} (Default: {3})'.format(
|
||||
node, printattr, cval, dval))
|
||||
else:
|
||||
|
||||
try:
|
||||
details = options.detail
|
||||
except AttributeError:
|
||||
details = False
|
||||
if details:
|
||||
if currattr.get('help', None):
|
||||
attrout += u' (Help: {0})'.format(
|
||||
currattr['help'])
|
||||
if currattr.get('possible', None):
|
||||
try:
|
||||
attrout += u' (Choices: {0})'.format(
|
||||
','.join(currattr['possible']))
|
||||
except TypeError:
|
||||
pass
|
||||
cprint(attrout)
|
||||
somematched = set([])
|
||||
printmissing = set([])
|
||||
badnodes = NestedDict()
|
||||
print attrout
|
||||
if not exitcode:
|
||||
if requestargs:
|
||||
for attr in requestargs:
|
||||
for node in allnodes:
|
||||
if attr in seenattributes[node]:
|
||||
somematched.add(attr)
|
||||
else:
|
||||
badnodes[node][attr] = True
|
||||
exitcode = 1
|
||||
for node in sortutil.natural_sort(badnodes):
|
||||
for attr in badnodes[node]:
|
||||
if attr in somematched:
|
||||
sys.stderr.write(
|
||||
'Error: {0} matches no valid value for {1}\n'.format(
|
||||
attr, node))
|
||||
else:
|
||||
printmissing.add(attr)
|
||||
for missing in printmissing:
|
||||
sys.stderr.write('Error: {0} not a valid attribute\n'.format(attr))
|
||||
if attr not in seenattributes:
|
||||
sys.stderr.write('Error: {0} not a valid attribute\n'.format(attr))
|
||||
exitcode = 1
|
||||
return exitcode
|
||||
|
||||
|
||||
def show_attr(attr, requestargs, seenattributes, options, node):
|
||||
try:
|
||||
reverse = options.exclude
|
||||
except AttributeError:
|
||||
reverse = False
|
||||
if requestargs is None or requestargs == []:
|
||||
return True
|
||||
processattr = attrrequested(attr, requestargs, seenattributes, node)
|
||||
if reverse:
|
||||
processattr = not processattr
|
||||
return processattr
|
||||
|
||||
|
||||
def printgroupattributes(session, requestargs, showtype, nodetype, noderange, options):
|
||||
exitcode = 0
|
||||
seenattributes = set([])
|
||||
@@ -635,7 +363,7 @@ def printgroupattributes(session, requestargs, showtype, nodetype, noderange, op
|
||||
attrout = '{0}: {1}: ********'.format(noderange, attr)
|
||||
else:
|
||||
attrout = '{0}: {1}:'.format(noderange, attr)
|
||||
elif isinstance(currattr, dict) and 'broken' in currattr:
|
||||
elif 'broken' in currattr:
|
||||
attrout = '{0}: {1}: *ERROR* BROKEN EXPRESSION: ' \
|
||||
'{2}'.format(noderange, attr,
|
||||
currattr['broken'])
|
||||
@@ -649,8 +377,8 @@ def printgroupattributes(session, requestargs, showtype, nodetype, noderange, op
|
||||
dictout.append("{0}={1}".format(k, v))
|
||||
attrout = '{0}: {1}: {2}'.format(noderange, attr, ','.join(map(str, dictout)))
|
||||
else:
|
||||
cprint("CODE ERROR" + repr(attr))
|
||||
cprint(attrout)
|
||||
print ("CODE ERROR" + repr(attr))
|
||||
print attrout
|
||||
if not exitcode:
|
||||
if requestargs:
|
||||
for attr in requestargs:
|
||||
@@ -659,7 +387,7 @@ def printgroupattributes(session, requestargs, showtype, nodetype, noderange, op
|
||||
exitcode = 1
|
||||
return exitcode
|
||||
|
||||
def updateattrib(session, updateargs, nodetype, noderange, options, dictassign=None):
|
||||
def updateattrib(session, updateargs, nodetype, noderange, options):
|
||||
# update attribute
|
||||
exitcode = 0
|
||||
if options.clear:
|
||||
@@ -673,89 +401,28 @@ def updateattrib(session, updateargs, nodetype, noderange, options, dictassign=N
|
||||
exitcode = res['errorcode']
|
||||
sys.stderr.write('Error: ' + res['error'] + '\n')
|
||||
sys.exit(exitcode)
|
||||
elif hasattr(options, 'environment') and options.environment:
|
||||
for key in updateargs[1:]:
|
||||
key = key.replace('.', '_')
|
||||
value = os.environ.get(
|
||||
key, os.environ[key.upper()])
|
||||
# Let's do one pass to make sure that there's not a usage problem
|
||||
for key in updateargs[1:]:
|
||||
key = key.replace('.', '_')
|
||||
value = os.environ.get(
|
||||
key, os.environ[key.upper()])
|
||||
if (nodetype == "nodegroups"):
|
||||
exitcode = session.simple_nodegroups_command(noderange,
|
||||
'attributes/all',
|
||||
value, key)
|
||||
else:
|
||||
exitcode = session.simple_noderange_command(noderange,
|
||||
'attributes/all',
|
||||
value, key)
|
||||
sys.exit(exitcode)
|
||||
elif dictassign:
|
||||
for key in dictassign:
|
||||
if nodetype == 'nodegroups':
|
||||
exitcode = session.simple_nodegroups_command(
|
||||
noderange, 'attributes/all', dictassign[key], key)
|
||||
else:
|
||||
exitcode = session.simple_noderange_command(
|
||||
noderange, 'attributes/all', dictassign[key], key)
|
||||
else:
|
||||
if "=" in updateargs[1]:
|
||||
try:
|
||||
for val in updateargs[1:]:
|
||||
val = val.split('=', 1)
|
||||
if val[0][-1] in (',', '-', '^'):
|
||||
key = val[0][:-1]
|
||||
if val[0][-1] == ',':
|
||||
value = {'prepend': val[1]}
|
||||
elif val[0][-1] in ('-', '^'):
|
||||
value = {'remove': val[1]}
|
||||
if len(updateargs[1:]) > 1:
|
||||
for val in updateargs[1:]:
|
||||
val = val.split('=')
|
||||
if (nodetype == "nodegroups"):
|
||||
exitcode = session.simple_nodegroups_command(noderange, 'attributes/all'.format(noderange),
|
||||
val[1],val[0])
|
||||
else:
|
||||
exitcode = session.simple_noderange_command(noderange, 'attributes/all'.format(noderange),
|
||||
val[1], val[0])
|
||||
else:
|
||||
val = updateargs[1].split('=')
|
||||
if nodetype == "nodegroups" :
|
||||
exitcode = session.simple_nodegroups_command(noderange, 'attributes/all'.format(noderange),
|
||||
val[1], val[0])
|
||||
else:
|
||||
key = val[0]
|
||||
value = val[1]
|
||||
if (nodetype == "nodegroups"):
|
||||
exitcode = session.simple_nodegroups_command(noderange, 'attributes/all',
|
||||
value, key)
|
||||
else:
|
||||
exitcode = session.simple_noderange_command(noderange, 'attributes/all',
|
||||
value, key)
|
||||
exitcode = session.simple_noderange_command(noderange, 'attributes/all'.format(noderange),
|
||||
val[1], val[0])
|
||||
except:
|
||||
sys.stderr.write('Error: {0} not a valid expression\n'.format(str(updateargs[1:])))
|
||||
exitcode = 1
|
||||
sys.exit(exitcode)
|
||||
return exitcode
|
||||
|
||||
|
||||
# So we try to prevent bad things from happening when globbing
|
||||
# We tried to head this off at the shell, but the various solutions would end
|
||||
# up breaking the shell in various ways (breaking pipe capability if using
|
||||
# DEBUG, breaking globbing if in pipe, etc)
|
||||
# Then we tried to parse the original commandline instead, however shlex isn't
|
||||
# going to parse full bourne language (e.g. knowing that '|' and '>' and
|
||||
# a world of other things would not be in our command line
|
||||
# so finally, just make sure the noderange appears verbatim in the command line
|
||||
# if we glob to something, then bash will change noderange and this should
|
||||
# detect it and save the user from tragedy
|
||||
def check_globbing(noderange):
|
||||
if not os.path.exists(noderange):
|
||||
return True
|
||||
rawargs = os.environ.get('CURRENT_CMDLINE', None)
|
||||
if rawargs:
|
||||
rawargs = shlex.split(rawargs)
|
||||
for arg in rawargs:
|
||||
if arg.startswith('$'):
|
||||
arg = arg[1:]
|
||||
if arg.endswith(';'):
|
||||
arg = arg[:-1]
|
||||
arg = os.environ.get(arg, '$' + arg)
|
||||
if arg.startswith(noderange):
|
||||
break
|
||||
else:
|
||||
sys.stderr.write(
|
||||
'Shell glob conflict detected, specified target "{0}" '
|
||||
'not in command line, but is a file. You can use "set -f" in '
|
||||
'bash or change directories such that there is no filename '
|
||||
'that would conflict.'
|
||||
'\n'.format(noderange))
|
||||
sys.exit(1)
|
||||
|
||||
@@ -1,342 +0,0 @@
|
||||
#!/usr/bin/python3
|
||||
import collections
|
||||
import os
|
||||
import struct
|
||||
import sys
|
||||
import time
|
||||
import fcntl
|
||||
import select
|
||||
import termios
|
||||
import tty
|
||||
|
||||
def writeout(data):
|
||||
if isinstance(data, str) and not isinstance(data, bytes):
|
||||
data = data.encode('utf8')
|
||||
try:
|
||||
if hasattr(sys.stdout, 'buffer'):
|
||||
sys.stdout.buffer.write(data)
|
||||
else:
|
||||
sys.stdout.write(data)
|
||||
except IOError:
|
||||
time.sleep(0.1)
|
||||
pass
|
||||
|
||||
|
||||
class LogReplay(object):
|
||||
def __init__(self, logfile, cblfile):
|
||||
self.bin = open(cblfile, 'rb')
|
||||
self.txt = open(logfile, 'rb')
|
||||
self.cleardata = []
|
||||
self.clearidx = 0
|
||||
self.pendingdata = collections.deque([])
|
||||
self.priordata = collections.deque([])
|
||||
self.laststamp = None
|
||||
self.needclear = False
|
||||
self.lasttxt = ''
|
||||
|
||||
def _rewind(self, datasize=None):
|
||||
curroffset = self.bin.tell() - 16
|
||||
if self.cleardata and self.clearidx > 1:
|
||||
self.clearidx -= 1
|
||||
priordata = self.cleardata[self.clearidx - 1]
|
||||
return curroffset, priordata
|
||||
self.cleardata = []
|
||||
self.clearidx = 0
|
||||
newoffset = curroffset - 32
|
||||
if newoffset < 0: #TODO: Follow a log roll
|
||||
newoffset = 0
|
||||
if datasize:
|
||||
while datasize > 0 and newoffset > 0:
|
||||
self.bin.seek(newoffset)
|
||||
tmprec = self.bin.read(16)
|
||||
newoffset -= 32
|
||||
tmprec = struct.unpack('!BBIHIBBH', tmprec)
|
||||
if tmprec[1] == 2:
|
||||
datasize -= tmprec[3]
|
||||
if newoffset >= 0:
|
||||
self.bin.seek(newoffset)
|
||||
return curroffset, None
|
||||
|
||||
def debuginfo(self):
|
||||
return '{0}, {1}'.format(self.bin.tell(), self.clearidx)
|
||||
|
||||
def get_output(self, reverse=False):
|
||||
endoffset = None
|
||||
output = b''
|
||||
if reverse: # Forget the uncommited future, if present
|
||||
output += b'\x1b[2J\x1b[H'
|
||||
endoffset, priordata = self._rewind(4096)
|
||||
if priordata is not None:
|
||||
return priordata, 1
|
||||
elif self.needclear:
|
||||
output += b'\x1b[2J\x1b[H'
|
||||
self.needclear = False
|
||||
if self.cleardata and self.clearidx < len(self.cleardata):
|
||||
datachunk = self.cleardata[self.clearidx]
|
||||
self.clearidx += 1
|
||||
return datachunk, 1
|
||||
self.cleardata = []
|
||||
self.clearidx = 0
|
||||
while (not reverse) or (self.bin.tell() < endoffset):
|
||||
record = self.bin.read(16)
|
||||
if not record:
|
||||
return b'', 0
|
||||
record = struct.unpack('!BBIHIBBH', record)
|
||||
if record[0] > 16:
|
||||
# Unsupported record, skip
|
||||
self.bin.seek(record[0] - 16, 1)
|
||||
continue
|
||||
type = record[1]
|
||||
offset = record[2]
|
||||
size = record[3]
|
||||
evtdata = record[5]
|
||||
auxdata = record[6]
|
||||
if type == 3:
|
||||
#TODO: provide data for status bar
|
||||
continue
|
||||
elif type == 2:
|
||||
self.laststamp = record[4]
|
||||
self.txt.seek(offset)
|
||||
txtout = self.txt.read(size)
|
||||
if reverse and self.bin.tell() < endoffset:
|
||||
output += txtout
|
||||
continue
|
||||
self.paginate(txtout)
|
||||
if self.cleardata:
|
||||
self.clearidx = 0
|
||||
if not self.cleardata[0]:
|
||||
self.cleardata = self.cleardata[1:]
|
||||
if self.cleardata:
|
||||
if reverse:
|
||||
output = self.cleardata[-1]
|
||||
self.clearidx = len(self.cleardata)
|
||||
else:
|
||||
output += self.cleardata[0]
|
||||
self.clearidx = 1
|
||||
else:
|
||||
output += txtout
|
||||
break
|
||||
if endoffset is not None and endoffset >= 0:
|
||||
self.bin.seek(endoffset)
|
||||
self.lasttxt = output
|
||||
return output, 1
|
||||
|
||||
def paginate(self, txtout):
|
||||
cleardata = [txtout]
|
||||
nextcleardata = []
|
||||
for sep in (b'\x1b[2J', b'\x1b[H\x1b[J'):
|
||||
replacementcleardata = []
|
||||
for txtout in cleardata:
|
||||
nextcleardata = txtout.split(sep)
|
||||
if len(nextcleardata) > 1:
|
||||
for idx in range(1, len(nextcleardata)):
|
||||
nextcleardata[idx] = sep + nextcleardata[idx]
|
||||
replacementcleardata.extend(nextcleardata)
|
||||
nextcleardata = []
|
||||
cleardata = replacementcleardata
|
||||
if len(cleardata) > 1:
|
||||
self.cleardata = cleardata
|
||||
return
|
||||
|
||||
def begin(self):
|
||||
self.needclear = True
|
||||
self.bin.seek(0)
|
||||
|
||||
def end(self):
|
||||
self.bin.seek(0, 2)
|
||||
|
||||
def search(self, searchstr, skipfirst=False):
|
||||
overlap = len(searchstr)
|
||||
firstoffset = self.bin.tell()
|
||||
lastoffset = firstoffset
|
||||
output = self.lasttxt
|
||||
while searchstr not in output:
|
||||
output, delay = self.get_output()
|
||||
if not self.cleardata and lastoffset == self.bin.tell():
|
||||
self.bin.seek(firstoffset)
|
||||
return b'', 0
|
||||
lastoffset = self.bin.tell()
|
||||
if skipfirst:
|
||||
output, delay = self.get_output()
|
||||
while searchstr not in output:
|
||||
output, delay = self.get_output()
|
||||
if not self.cleardata and lastoffset == self.bin.tell():
|
||||
self.bin.seek(firstoffset)
|
||||
return b'', 0
|
||||
lastoffset = self.bin.tell()
|
||||
clear = b'\x1b[2J\x1b[H'
|
||||
txtchunks = self.lasttxt.split(searchstr)
|
||||
output = clear + txtchunks[0]
|
||||
txtchunks = txtchunks[1:]
|
||||
while txtchunks:
|
||||
output += b'\x1b[7m' + searchstr + b'\x1b[27m' + txtchunks[0]
|
||||
txtchunks = txtchunks[1:]
|
||||
return output, 0
|
||||
|
||||
|
||||
def _replay_to_console(txtfile, binfile):
|
||||
replay = LogReplay(txtfile, binfile)
|
||||
oldtcattr = termios.tcgetattr(sys.stdin.fileno())
|
||||
tty.setraw(sys.stdin.fileno())
|
||||
currfl = fcntl.fcntl(sys.stdin.fileno(), fcntl.F_GETFL)
|
||||
fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, currfl | os.O_NONBLOCK)
|
||||
reverse = False
|
||||
skipnext = False
|
||||
quitit = False
|
||||
searchstr = None
|
||||
writeout('\x1b[2J\x1b[;H')
|
||||
prepend = ''
|
||||
try:
|
||||
while not quitit:
|
||||
if not skipnext:
|
||||
newdata, delay = replay.get_output(reverse)
|
||||
skipnext = False
|
||||
reverse = False
|
||||
if newdata:
|
||||
if prepend:
|
||||
newdata = prepend + newdata
|
||||
prepend = b''
|
||||
writeout(newdata)
|
||||
newdata = ''
|
||||
writeout('\x1b]0;[Time: {0}]\x07'.format(
|
||||
time.strftime('%m/%d %H:%M:%S', time.localtime(replay.laststamp))))
|
||||
try:
|
||||
if hasattr(sys.stdout, 'buffer'):
|
||||
sys.stdout.buffer.flush()
|
||||
else:
|
||||
sys.stdout.flush()
|
||||
except IOError:
|
||||
pass
|
||||
while True:
|
||||
select.select((sys.stdin,), (), (), 86400)
|
||||
myinput = sys.stdin.read()
|
||||
if myinput.startswith('\x1b[C') or myinput.startswith('\x1bOC') or myinput == '\r': # right
|
||||
break
|
||||
elif myinput.startswith('\x1b[D') or myinput.startswith('\x1bOD') or myinput == 'y': # left
|
||||
writeout('\x1b[2J\x1b[;H')
|
||||
reverse = True
|
||||
break
|
||||
elif myinput == 'G' or myinput.startswith('\x1b[F'):
|
||||
replay.end()
|
||||
reverse = True
|
||||
break
|
||||
elif myinput == 'g' or myinput.startswith('\x1b[H'):
|
||||
replay.begin()
|
||||
break
|
||||
elif myinput.lower() == 'q' or myinput == '\x03':
|
||||
quitit = True
|
||||
break
|
||||
elif myinput.lower() == 'd':
|
||||
writeout('\x1b];{0}\x07'.format(replay.debuginfo()))
|
||||
if hasattr(sys.stdout, 'buffer'):
|
||||
sys.stdout.buffer.flush()
|
||||
else:
|
||||
sys.stdout.flush()
|
||||
elif myinput.lower() == '/':
|
||||
sys.stdout.write('\x1b7\x1b[99999;0H\x1b[2K')
|
||||
searchstr = ''
|
||||
nxtchr = '/'
|
||||
while '\r' not in searchstr:
|
||||
sys.stdout.write(nxtchr)
|
||||
sys.stdout.flush()
|
||||
select.select((sys.stdin,), (), (), 86400)
|
||||
nxtchr = sys.stdin.read(1)
|
||||
if nxtchr in ('\x08', '\x7f'):
|
||||
searchstr = searchstr[:-1]
|
||||
nxtchr = '\x08 \x08'
|
||||
else:
|
||||
searchstr += nxtchr
|
||||
if not isinstance(searchstr, bytes):
|
||||
searchstr = searchstr.encode('utf8')
|
||||
searchstr = searchstr[:-1]
|
||||
newdata, delay = replay.search(searchstr)
|
||||
if not newdata:
|
||||
sys.stdout.write('\x1b[1K\rNo match found')
|
||||
prepend = b'\x1b[1K\x1b8'
|
||||
else:
|
||||
sys.stdout.write('\x1b[1K\x1b8')
|
||||
sys.stdout.flush()
|
||||
skipnext = True
|
||||
break
|
||||
elif myinput.lower() == 'n' and searchstr:
|
||||
newdata, delay = replay.search(searchstr, True)
|
||||
if not newdata:
|
||||
sys.stdout.write('\x1b7\x1b[99999;0H\x1b[2KNo more matches found')
|
||||
sys.stdout.flush()
|
||||
prepend = b'\x1b[1K\x1b8'
|
||||
skipnext = True
|
||||
break
|
||||
else:
|
||||
pass # print(repr(myinput))
|
||||
except Exception:
|
||||
currfl = fcntl.fcntl(sys.stdin.fileno(), fcntl.F_GETFL)
|
||||
fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, currfl ^ os.O_NONBLOCK)
|
||||
termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, oldtcattr)
|
||||
writeout('\x1b[m\x1b[?25h\n')
|
||||
raise
|
||||
currfl = fcntl.fcntl(sys.stdin.fileno(), fcntl.F_GETFL)
|
||||
fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, currfl ^ os.O_NONBLOCK)
|
||||
termios.tcsetattr(sys.stdin.fileno(), termios.TCSANOW, oldtcattr)
|
||||
writeout('\x1b[m\x1b[?25h\n')
|
||||
|
||||
def replay_to_console(txtfile):
|
||||
if os.path.exists(txtfile + '.cbl'):
|
||||
binfile = txtfile + '.cbl'
|
||||
elif '.' not in txtfile:
|
||||
if '/' not in txtfile:
|
||||
txtfile = os.getcwd() + '/' + txtfile
|
||||
sys.stderr.write('Unable to locate cbl file: "{0}"\n'.format(txtfile + '.cbl'))
|
||||
sys.exit(1)
|
||||
else:
|
||||
fileparts = txtfile.split('.')
|
||||
prefix = '.'.join(fileparts[:-1])
|
||||
binfile = prefix + '.cbl.' + fileparts[-1]
|
||||
if not os.path.exists(binfile):
|
||||
sys.stderr.write('Unable to locate cbl file: "{0}"\n'.format(binfile))
|
||||
sys.exit(1)
|
||||
_replay_to_console(txtfile, binfile)
|
||||
|
||||
def dump_to_console(txtfile):
|
||||
if os.path.exists(txtfile + '.cbl'):
|
||||
binfile = txtfile + '.cbl'
|
||||
elif '.' not in txtfile:
|
||||
if '/' not in txtfile:
|
||||
txtfile = os.getcwd() + '/' + txtfile
|
||||
sys.stderr.write('Unable to locate cbl file: "{0}"\n'.format(txtfile + '.cbl'))
|
||||
sys.exit(1)
|
||||
else:
|
||||
fileparts = txtfile.split('.')
|
||||
prefix = '.'.join(fileparts[:-1])
|
||||
binfile = prefix + '.cbl.' + fileparts[-1]
|
||||
if not os.path.exists(binfile):
|
||||
sys.stderr.write('Unable to locate cbl file: "{0}"\n'.format(binfile))
|
||||
sys.exit(1)
|
||||
replay = LogReplay(txtfile, binfile)
|
||||
quitit = False
|
||||
writeout('\x1b[2J\x1b[;H')
|
||||
prepend = ''
|
||||
try:
|
||||
while not quitit:
|
||||
newdata, delay = replay.get_output(False)
|
||||
if newdata:
|
||||
if prepend:
|
||||
newdata = prepend + newdata
|
||||
prepend = b''
|
||||
writeout(newdata.replace(b'\r\n', '\r\n[ {0} ] '.format(time.strftime('%m/%d %H:%M:%S', time.localtime(replay.laststamp))).encode('utf8')))
|
||||
newdata = ''
|
||||
try:
|
||||
if hasattr(sys.stdout, 'buffer'):
|
||||
sys.stdout.buffer.flush()
|
||||
else:
|
||||
sys.stdout.flush()
|
||||
except IOError:
|
||||
pass
|
||||
else:
|
||||
break
|
||||
except Exception:
|
||||
writeout('\x1b[m\x1b[?25h\n')
|
||||
raise
|
||||
writeout('\x1b[m\x1b[?25h\n')
|
||||
|
||||
if __name__ == '__main__':
|
||||
replay_to_console(sys.argv[1])
|
||||
@@ -1,125 +0,0 @@
|
||||
# Copyright 2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import fcntl
|
||||
import sys
|
||||
import struct
|
||||
import termios
|
||||
|
||||
def get_screengeom():
|
||||
return struct.unpack('hh', fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ,
|
||||
b'....'))
|
||||
class ScreenPrinter(object):
|
||||
|
||||
def __init__(self, noderange, client, textlen=4):
|
||||
self.squeeze = sys.stdout.isatty()
|
||||
self.textlen = textlen
|
||||
self.noderange = noderange
|
||||
self.client = client
|
||||
self.nodeoutput = {}
|
||||
self.nodelist = []
|
||||
self.nodepos = {}
|
||||
self.lastrows = 0
|
||||
self.fieldchanged = False
|
||||
maxlen = 0
|
||||
for ans in self.client.read('/noderange/{0}/nodes/'.format(noderange)):
|
||||
if 'error' in ans:
|
||||
sys.stderr.write(ans['error'])
|
||||
continue
|
||||
nodename = ans['item']['href'][:-1]
|
||||
if len(nodename) > maxlen:
|
||||
maxlen = len(nodename)
|
||||
self.nodelist.append(nodename)
|
||||
self.nodeoutput[nodename] = ''
|
||||
self.nodenamelen = maxlen
|
||||
self.textlen = textlen
|
||||
self.fieldwidth = maxlen + textlen + 1 # 1 for column
|
||||
|
||||
def set_output(self, node, text):
|
||||
if self.nodeoutput.get(node, None) == text:
|
||||
return
|
||||
self.nodeoutput[node] = text
|
||||
if len(text) >= self.textlen:
|
||||
self.textlen = len(text) + 1
|
||||
self.fieldwidth = self.textlen + self.nodenamelen + 1
|
||||
self.fieldchanged = True
|
||||
self.drawscreen(node)
|
||||
|
||||
def drawscreen(self, node=None):
|
||||
if self.squeeze:
|
||||
currheight, currwidth = get_screengeom()
|
||||
currheight -= 2
|
||||
if currheight < 1:
|
||||
currheight = 1
|
||||
numfields = currwidth // self.fieldwidth
|
||||
fieldformat = '{{0:>{0}}}:{{1:{1}}}'.format(self.nodenamelen,
|
||||
self.textlen)
|
||||
#sys.stdout.write('\x1b[2J\x1b[;H') # clear screen
|
||||
if len(self.nodelist) < (numfields * currheight):
|
||||
numfields = len(self.nodelist) // currheight + 1
|
||||
else:
|
||||
numfields = 1
|
||||
fieldformat = '{0}: {1}'
|
||||
if self.squeeze:
|
||||
columns = [self.nodelist[x:x+currheight] for x in range(0, len(self.nodelist), currheight)]
|
||||
if self.lastrows:
|
||||
sys.stdout.write('\x1b[{0}A'.format(self.lastrows))
|
||||
if node and self.lastrows == len(columns[0]) and not self.fieldchanged:
|
||||
targline, targcol = self.nodepos[node]
|
||||
if targline:
|
||||
sys.stdout.write('\x1b[{0}B'.format(targline))
|
||||
if targcol:
|
||||
sys.stdout.write('\x1b[{0}C'.format(targcol))
|
||||
sys.stdout.write(fieldformat.format(node, self.nodeoutput[node]))
|
||||
sys.stdout.write('\r\x1b[{0}B'.format(self.lastrows - targline))
|
||||
else:
|
||||
self.lastrows = 0
|
||||
self.fieldchanged = False
|
||||
column = 0
|
||||
for currow in range(0, len(columns[0])):
|
||||
sys.stdout.write('\x1b[2K')
|
||||
for col in columns:
|
||||
try:
|
||||
node = col[currow]
|
||||
self.nodepos[node] = (currow, column)
|
||||
sys.stdout.write(fieldformat.format(node, self.nodeoutput[node]))
|
||||
column += len(fieldformat.format(node, self.nodeoutput[node]))
|
||||
except IndexError:
|
||||
break
|
||||
sys.stdout.write('\n')
|
||||
column = 0
|
||||
self.lastrows += 1
|
||||
sys.stdout.write('\x1b[J')
|
||||
else:
|
||||
if node:
|
||||
nodes = [node]
|
||||
else:
|
||||
nodes = self.nodelist
|
||||
for node in nodes:
|
||||
sys.stdout.write(fieldformat.format(node, self.nodeoutput[node]))
|
||||
sys.stdout.write('\n')
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import confluent.client as client
|
||||
c = client.Command()
|
||||
p = ScreenPrinter('d1-d12', c)
|
||||
p.set_output('d3', 'Upload: 67%')
|
||||
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2014 IBM Corporation
|
||||
# Copyright 2015-2016 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import re
|
||||
|
||||
numregex = re.compile('([0-9]+)')
|
||||
|
||||
|
||||
def naturalize_string(key):
|
||||
"""Analyzes string in a human way to enable natural sort
|
||||
|
||||
:param key: The node name to analyze
|
||||
:returns: A structure that can be consumed by 'sorted'
|
||||
"""
|
||||
return [int(text) if text.isdigit() else text.lower()
|
||||
for text in re.split(numregex, key)]
|
||||
|
||||
|
||||
def natural_sort(iterable):
|
||||
"""Return a sort using natural sort if possible
|
||||
|
||||
:param iterable:
|
||||
:return:
|
||||
"""
|
||||
try:
|
||||
return sorted(iterable, key=naturalize_string)
|
||||
except TypeError:
|
||||
# The natural sort attempt failed, fallback to ascii sort
|
||||
return sorted(iterable)
|
||||
@@ -53,16 +53,13 @@ class TermHandler(object):
|
||||
connection = None
|
||||
try:
|
||||
connection, address = self.socket.accept()
|
||||
connection.sendall(b"confetty control v1--\n")
|
||||
connection.sendall("confetty control v1--\n")
|
||||
cmd = connection.recv(8)
|
||||
if b'GETWINID' == cmd:
|
||||
winid = os.environ['WINDOWID']
|
||||
if not isinstance(winid, bytes):
|
||||
winid = winid.encode('utf8')
|
||||
connection.sendall(winid)
|
||||
if 'GETWINID' == cmd:
|
||||
connection.sendall(os.environ['WINDOWID'])
|
||||
connection.close()
|
||||
except BaseException:
|
||||
pass
|
||||
finally:
|
||||
if connection is not None:
|
||||
connection.close()
|
||||
connection.close()
|
||||
@@ -1,222 +0,0 @@
|
||||
# Copyright 2017 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import difflib
|
||||
import re
|
||||
import sys
|
||||
|
||||
try:
|
||||
range = xrange
|
||||
except NameError:
|
||||
pass
|
||||
|
||||
|
||||
|
||||
numregex = re.compile('([0-9]+)')
|
||||
def humanify_nodename(nodename):
|
||||
"""Analyzes nodename in a human way to enable natural sort
|
||||
|
||||
:param nodename: The node name to analyze
|
||||
:returns: A structure that can be consumed by 'sorted'
|
||||
"""
|
||||
return [int(text) if text.isdigit() else text.lower()
|
||||
for text in re.split(numregex, nodename)]
|
||||
|
||||
|
||||
def _colorize_line(orig, mask):
|
||||
highlighted = False
|
||||
newline = orig[0]
|
||||
for i in range(1, len(orig)):
|
||||
if i > len(mask) - 1:
|
||||
if highlighted:
|
||||
newline += '\x1b[0m'
|
||||
newline += orig[i:]
|
||||
break
|
||||
if highlighted and mask[i] == ' ':
|
||||
highlighted = False
|
||||
newline += '\x1b[0m'
|
||||
elif not highlighted and mask[i] != ' ':
|
||||
highlighted = True
|
||||
newline += '\x1b[31m'
|
||||
newline += orig[i]
|
||||
newline += '\x1b[0m'
|
||||
return newline
|
||||
|
||||
|
||||
def near_diff(idx, diffdata):
|
||||
maxidx = idx + 3
|
||||
if maxidx > len(diffdata) -1:
|
||||
maxidx = len(diffdata) - 1
|
||||
idx = idx - 3
|
||||
if idx < 0:
|
||||
idx = 0
|
||||
while idx <= maxidx:
|
||||
if len(diffdata[idx]) > 0 and diffdata[idx][0] in ('+', '-'):
|
||||
return True
|
||||
idx += 1
|
||||
return False
|
||||
|
||||
|
||||
def colordiff(first, second):
|
||||
diffdata = list(difflib.ndiff(first, second))
|
||||
quiet = True
|
||||
for i in range(len(diffdata)):
|
||||
if i < len(diffdata) - 1 and diffdata[i + 1].startswith('?'):
|
||||
if quiet:
|
||||
quiet = False
|
||||
yield '@@'
|
||||
yield _colorize_line(diffdata[i], diffdata[i + 1])
|
||||
elif not diffdata[i].startswith('?') and near_diff(i, diffdata):
|
||||
if quiet:
|
||||
quiet = False
|
||||
yield '@@'
|
||||
yield diffdata[i]
|
||||
elif not diffdata[i].startswith('?'):
|
||||
quiet = True
|
||||
|
||||
|
||||
|
||||
class GroupedData(object):
|
||||
'''A post processor to sort and compare per-node data
|
||||
|
||||
:param confluentconnection: If given, will attempt to use the connection to abbreviate noderanges
|
||||
'''
|
||||
|
||||
def __init__(self, confluentconnection=None):
|
||||
self.bynode = {}
|
||||
self.byoutput = {}
|
||||
self.header = {}
|
||||
self.client = confluentconnection
|
||||
|
||||
def generate_byoutput(self):
|
||||
self.byoutput = {}
|
||||
for n in self.bynode:
|
||||
output = '\n'.join(self.bynode[n])
|
||||
if output not in self.byoutput:
|
||||
self.byoutput[output] = set([n])
|
||||
else:
|
||||
self.byoutput[output].add(n)
|
||||
|
||||
def add_line(self, node, line):
|
||||
if node not in self.bynode:
|
||||
self.bynode[node] = [line]
|
||||
else:
|
||||
self.bynode[node].append(line)
|
||||
|
||||
def get_group_text(self, nodes):
|
||||
if self.client:
|
||||
headerkey = ','.join(sorted(nodes))
|
||||
if headerkey in self.header:
|
||||
return self.header[headerkey]
|
||||
noderange = ''
|
||||
for reply in self.client.create('/noderange//abbreviate',
|
||||
{'nodes': sorted(nodes)}):
|
||||
noderange = reply['noderange']
|
||||
self.header[headerkey] = noderange
|
||||
return noderange
|
||||
else:
|
||||
return ','.join(sorted(nodes, key=humanify_nodename))
|
||||
|
||||
def print_all(self, output=sys.stdout, skipmodal=False, reverse=False,
|
||||
count=False):
|
||||
self.generate_byoutput()
|
||||
modaloutput = None
|
||||
ismodal = True
|
||||
|
||||
if reverse:
|
||||
outdatalist = sorted(
|
||||
self.byoutput, key=lambda x: [len(self.byoutput[x]),
|
||||
humanify_nodename(
|
||||
self.get_group_text(
|
||||
self.byoutput[x]
|
||||
))])
|
||||
else:
|
||||
outdatalist = sorted(
|
||||
self.byoutput, key=lambda x: [0 - len(self.byoutput[x]),
|
||||
humanify_nodename(
|
||||
self.get_group_text(
|
||||
self.byoutput[x]
|
||||
))])
|
||||
if reverse and skipmodal:
|
||||
# if reversed, the last is biggest and should be skipped if modal
|
||||
outdatalist = outdatalist[:-1]
|
||||
for outdata in outdatalist:
|
||||
if not reverse and skipmodal:
|
||||
# If big first, this makes skipmodal skip first
|
||||
skipmodal = False
|
||||
continue
|
||||
currout = '====================================\n'
|
||||
currout += self.get_group_text(self.byoutput[outdata])
|
||||
currout += '\n====================================\n'
|
||||
if count:
|
||||
currout += 'Count: {0}'.format(len(list(
|
||||
self.byoutput[outdata])))
|
||||
currout += '\n====================================\n'
|
||||
currout += outdata
|
||||
currout += '\n\n'
|
||||
output.write(currout)
|
||||
output.flush()
|
||||
|
||||
def print_deviants(self, output=sys.stdout, skipmodal=False, reverse=False,
|
||||
count=False, basenode=None):
|
||||
self.generate_byoutput()
|
||||
modaloutput = None
|
||||
ismodal = True
|
||||
revoutput = []
|
||||
if basenode:
|
||||
for checkout in self.byoutput:
|
||||
if basenode in self.byoutput[checkout]:
|
||||
modaloutput = checkout
|
||||
for outdata in sorted(
|
||||
self.byoutput, key=lambda x: [0 if modaloutput == x else 1,
|
||||
0 - len(self.byoutput[x]),
|
||||
humanify_nodename(
|
||||
self.get_group_text(
|
||||
self.byoutput[x]
|
||||
))]):
|
||||
if modaloutput is None:
|
||||
modaloutput = outdata
|
||||
if skipmodal:
|
||||
skipmodal = False
|
||||
ismodal = False
|
||||
continue
|
||||
currout = '====================================\n'
|
||||
currout += self.get_group_text(self.byoutput[outdata])
|
||||
currout += '\n====================================\n'
|
||||
if count:
|
||||
currout += 'Count: {0}'.format(len(list(
|
||||
self.byoutput[outdata])))
|
||||
currout += '\n====================================\n'
|
||||
if ismodal:
|
||||
ismodal = False
|
||||
currout += outdata
|
||||
else:
|
||||
currout += '\n'.join(colordiff(modaloutput.split('\n'),
|
||||
outdata.split('\n')))
|
||||
currout += '\n\n'
|
||||
if reverse:
|
||||
revoutput.append(currout)
|
||||
else:
|
||||
output.write(currout)
|
||||
for currout in reversed(revoutput):
|
||||
output.write(currout)
|
||||
output.flush()
|
||||
|
||||
if __name__ == '__main__':
|
||||
groupoutput = GroupedData()
|
||||
for line in sys.stdin.read().split('\n'):
|
||||
if not line:
|
||||
continue
|
||||
groupoutput.add_line(*line.split(': ', 1))
|
||||
groupoutput.print_deviants()
|
||||
@@ -17,4 +17,4 @@
|
||||
|
||||
|
||||
class Types(object):
|
||||
text, json, filehandle = range(3)
|
||||
text, json = range(2)
|
||||
|
||||
@@ -15,97 +15,11 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import array
|
||||
import ctypes
|
||||
import ctypes.util
|
||||
import confluent.tlv as tlv
|
||||
try:
|
||||
import eventlet.green.socket as socket
|
||||
import eventlet.green.select as select
|
||||
except ImportError:
|
||||
import socket
|
||||
import select
|
||||
from datetime import datetime
|
||||
import json
|
||||
import os
|
||||
import struct
|
||||
|
||||
try:
|
||||
unicode
|
||||
except NameError:
|
||||
unicode = str
|
||||
|
||||
try:
|
||||
range = xrange
|
||||
except NameError:
|
||||
pass
|
||||
|
||||
class iovec(ctypes.Structure): # from uio.h
|
||||
_fields_ = [('iov_base', ctypes.c_void_p),
|
||||
('iov_len', ctypes.c_size_t)]
|
||||
|
||||
|
||||
iovec_ptr = ctypes.POINTER(iovec)
|
||||
|
||||
|
||||
class cmsghdr(ctypes.Structure): # also from bits/socket.h
|
||||
_fields_ = [('cmsg_len', ctypes.c_size_t),
|
||||
('cmsg_level', ctypes.c_int),
|
||||
('cmsg_type', ctypes.c_int)]
|
||||
|
||||
@classmethod
|
||||
def init_data(cls, cmsg_len, cmsg_level, cmsg_type, cmsg_data):
|
||||
Data = ctypes.c_ubyte * ctypes.sizeof(cmsg_data)
|
||||
class _flexhdr(ctypes.Structure):
|
||||
_fields_ = cls._fields_ + [('cmsg_data', Data)]
|
||||
|
||||
datab = Data(*bytearray(cmsg_data))
|
||||
return _flexhdr(cmsg_len=cmsg_len, cmsg_level=cmsg_level,
|
||||
cmsg_type=cmsg_type, cmsg_data=datab)
|
||||
|
||||
|
||||
def CMSG_LEN(length):
|
||||
sizeof_cmshdr = ctypes.sizeof(cmsghdr)
|
||||
return ctypes.c_size_t(CMSG_ALIGN(sizeof_cmshdr).value + length)
|
||||
|
||||
|
||||
SCM_RIGHTS = 1
|
||||
|
||||
|
||||
class msghdr(ctypes.Structure): # from bits/socket.h
|
||||
_fields_ = [('msg_name', ctypes.c_void_p),
|
||||
('msg_namelen', ctypes.c_uint),
|
||||
('msg_iov', ctypes.POINTER(iovec)),
|
||||
('msg_iovlen', ctypes.c_size_t),
|
||||
('msg_control', ctypes.c_void_p),
|
||||
('msg_controllen', ctypes.c_size_t),
|
||||
('msg_flags', ctypes.c_int)]
|
||||
|
||||
|
||||
def CMSG_ALIGN(length): # bits/socket.h
|
||||
ret = (length + ctypes.sizeof(ctypes.c_size_t) - 1
|
||||
& ~(ctypes.sizeof(ctypes.c_size_t) - 1))
|
||||
return ctypes.c_size_t(ret)
|
||||
|
||||
|
||||
def CMSG_SPACE(length): # bits/socket.h
|
||||
ret = CMSG_ALIGN(length).value + CMSG_ALIGN(ctypes.sizeof(cmsghdr)).value
|
||||
return ctypes.c_size_t(ret)
|
||||
|
||||
|
||||
class ClientFile(object):
|
||||
def __init__(self, name, mode, fd):
|
||||
self.fileobject = os.fdopen(fd, mode)
|
||||
self.filename = name
|
||||
|
||||
libc = ctypes.CDLL(ctypes.util.find_library('c'))
|
||||
recvmsg = libc.recvmsg
|
||||
recvmsg.argtypes = [ctypes.c_int, ctypes.POINTER(msghdr), ctypes.c_int]
|
||||
recvmsg.restype = ctypes.c_int
|
||||
sendmsg = libc.sendmsg
|
||||
sendmsg.argtypes = [ctypes.c_int, ctypes.POINTER(msghdr), ctypes.c_int]
|
||||
sendmsg.restype = ctypes.c_size_t
|
||||
|
||||
def decodestr(value):
|
||||
ret = None
|
||||
try:
|
||||
@@ -115,39 +29,26 @@ def decodestr(value):
|
||||
ret = value.decode('cp437')
|
||||
except UnicodeDecodeError:
|
||||
ret = value
|
||||
except AttributeError:
|
||||
return value
|
||||
return ret
|
||||
|
||||
def unicode_dictvalues(dictdata):
|
||||
for key in dictdata:
|
||||
if isinstance(dictdata[key], bytes):
|
||||
if isinstance(dictdata[key], str):
|
||||
dictdata[key] = decodestr(dictdata[key])
|
||||
elif isinstance(dictdata[key], datetime):
|
||||
dictdata[key] = dictdata[key].strftime('%Y-%m-%dT%H:%M:%S')
|
||||
elif isinstance(dictdata[key], list):
|
||||
_unicode_list(dictdata[key])
|
||||
for i in xrange(len(dictdata[key])):
|
||||
if isinstance(dictdata[key][i], str):
|
||||
dictdata[key][i] = decodestr(dictdata[key][i])
|
||||
elif isinstance(dictdata[key][i], dict):
|
||||
unicode_dictvalues(dictdata[key][i])
|
||||
elif isinstance(dictdata[key], dict):
|
||||
unicode_dictvalues(dictdata[key])
|
||||
|
||||
|
||||
def _unicode_list(currlist):
|
||||
for i in range(len(currlist)):
|
||||
if isinstance(currlist[i], str):
|
||||
currlist[i] = decodestr(currlist[i])
|
||||
elif isinstance(currlist[i], dict):
|
||||
unicode_dictvalues(currlist[i])
|
||||
elif isinstance(currlist[i], list):
|
||||
_unicode_list(currlist[i])
|
||||
|
||||
|
||||
def send(handle, data, filehandle=None):
|
||||
if isinstance(data, unicode):
|
||||
try:
|
||||
data = data.encode('utf-8')
|
||||
except AttributeError:
|
||||
pass
|
||||
if isinstance(data, bytes) or isinstance(data, unicode):
|
||||
def send(handle, data):
|
||||
if isinstance(data, str):
|
||||
# plain text, e.g. console data
|
||||
tl = len(data)
|
||||
if tl == 0:
|
||||
@@ -169,37 +70,11 @@ def send(handle, data, filehandle=None):
|
||||
if tl > 16777215:
|
||||
raise Exception("JSON data exceeds protocol limits")
|
||||
# xor in the type (0b1 << 24)
|
||||
if filehandle is None:
|
||||
tl |= 16777216
|
||||
handle.sendall(struct.pack("!I", tl))
|
||||
handle.sendall(sdata)
|
||||
else:
|
||||
tl |= (2 << 24)
|
||||
handle.sendall(struct.pack("!I", tl))
|
||||
cdtype = ctypes.c_ubyte * len(sdata)
|
||||
cdata = cdtype.from_buffer(bytearray(sdata))
|
||||
ciov = iovec(iov_base=ctypes.addressof(cdata),
|
||||
iov_len=ctypes.c_size_t(ctypes.sizeof(cdata)))
|
||||
fd = ctypes.c_int(filehandle)
|
||||
cmh = cmsghdr.init_data(
|
||||
cmsg_len=CMSG_LEN(
|
||||
ctypes.sizeof(fd)), cmsg_level=socket.SOL_SOCKET,
|
||||
cmsg_type=SCM_RIGHTS, cmsg_data=fd)
|
||||
mh = msghdr(msg_name=None, msg_len=0, msg_iov=iovec_ptr(ciov),
|
||||
msg_iovlen=1, msg_control=ctypes.addressof(cmh),
|
||||
msg_controllen=ctypes.c_size_t(ctypes.sizeof(cmh)))
|
||||
sendmsg(handle.fileno(), mh, 0)
|
||||
tl |= 16777216
|
||||
handle.sendall(struct.pack("!I", tl))
|
||||
handle.sendall(sdata)
|
||||
|
||||
|
||||
def recvall(handle, size):
|
||||
rd = handle.recv(size)
|
||||
while len(rd) < size:
|
||||
nd = handle.recv(size - len(rd))
|
||||
if not nd:
|
||||
raise Exception("Error reading data")
|
||||
rd += nd
|
||||
return rd
|
||||
|
||||
def recv(handle):
|
||||
tl = handle.recv(4)
|
||||
if not tl:
|
||||
@@ -219,46 +94,12 @@ def recv(handle):
|
||||
datatype = (tl & 2130706432) >> 24 # grab 7 bits from near beginning
|
||||
if dlen == 0:
|
||||
return None
|
||||
if datatype == tlv.Types.filehandle:
|
||||
filehandles = array.array('i')
|
||||
rawbuffer = bytearray(2048)
|
||||
pkttype = ctypes.c_ubyte * 2048
|
||||
data = pkttype.from_buffer(rawbuffer)
|
||||
cmsgsize = CMSG_SPACE(ctypes.sizeof(ctypes.c_int)).value
|
||||
cmsgarr = bytearray(cmsgsize)
|
||||
cmtype = ctypes.c_ubyte * cmsgsize
|
||||
cmsg = cmtype.from_buffer(cmsgarr)
|
||||
cmsg.cmsg_level = socket.SOL_SOCKET
|
||||
cmsg.cmsg_type = SCM_RIGHTS
|
||||
cmsg.cmsg_len = CMSG_LEN(ctypes.sizeof(ctypes.c_int))
|
||||
iov = iovec()
|
||||
iov.iov_base = ctypes.addressof(data)
|
||||
iov.iov_len = 2048
|
||||
msg = msghdr()
|
||||
msg.msg_iov = ctypes.pointer(iov)
|
||||
msg.msg_iovlen = 1
|
||||
msg.msg_control = ctypes.addressof(cmsg)
|
||||
msg.msg_controllen = ctypes.sizeof(cmsg)
|
||||
select.select([handle], [], [])
|
||||
i = recvmsg(handle.fileno(), ctypes.pointer(msg), 0)
|
||||
cdata = cmsgarr[CMSG_LEN(0).value:]
|
||||
data = rawbuffer[:i]
|
||||
if cmsg.cmsg_level == socket.SOL_SOCKET and cmsg.cmsg_type == SCM_RIGHTS:
|
||||
try:
|
||||
filehandles.fromstring(bytes(
|
||||
cdata[:len(cdata) - len(cdata) % filehandles.itemsize]))
|
||||
except AttributeError:
|
||||
filehandles.frombytes(bytes(
|
||||
cdata[:len(cdata) - len(cdata) % filehandles.itemsize]))
|
||||
data = json.loads(bytes(data))
|
||||
return ClientFile(data['filename'], data['mode'], filehandles[0])
|
||||
else:
|
||||
data = handle.recv(dlen)
|
||||
while len(data) < dlen:
|
||||
ndata = handle.recv(dlen - len(data))
|
||||
if not ndata:
|
||||
raise Exception("Error reading data")
|
||||
data += ndata
|
||||
data = handle.recv(dlen)
|
||||
while len(data) < dlen:
|
||||
ndata = handle.recv(dlen - len(data))
|
||||
if not ndata:
|
||||
raise Exception("Error reading data")
|
||||
data += ndata
|
||||
if datatype == tlv.Types.text:
|
||||
return data
|
||||
elif datatype == tlv.Types.json:
|
||||
|
||||
@@ -7,13 +7,13 @@ Name: %{name}
|
||||
Version: %{version}
|
||||
Release: %{release}
|
||||
Source0: %{name}-%{version}.tar.gz
|
||||
License: Apache2
|
||||
License: UNKNOWN
|
||||
Group: Development/Libraries
|
||||
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
|
||||
Prefix: %{_prefix}
|
||||
BuildArch: noarch
|
||||
Vendor: Lenovo
|
||||
Url: http://github.com/lenovo/confluent
|
||||
Vendor: Jarrod Johnson <jjohnson2@lenovo.com>
|
||||
Url: http://xcat.sf.net/
|
||||
Obsoletes: confluent_common
|
||||
|
||||
%description
|
||||
@@ -24,25 +24,14 @@ a confluent server.
|
||||
%setup -n %{name}-%{version} -n %{name}-%{version}
|
||||
|
||||
%build
|
||||
%if "%{dist}" == ".el7"
|
||||
python2 setup.py build
|
||||
%else
|
||||
python3 setup.py build
|
||||
%endif
|
||||
python setup.py build
|
||||
|
||||
%install
|
||||
%if "%{dist}" == ".el7"
|
||||
python2 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES --install-scripts=/opt/confluent/bin --install-purelib=/opt/confluent/lib/python
|
||||
%else
|
||||
python3 setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES --install-scripts=/opt/confluent/bin --install-purelib=/opt/confluent/lib/python
|
||||
%endif
|
||||
python setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES --install-scripts=/opt/confluent/bin --install-purelib=/opt/confluent/lib/python
|
||||
|
||||
|
||||
%clean
|
||||
rm -rf $RPM_BUILD_ROOT
|
||||
|
||||
%files -f INSTALLED_FILES
|
||||
%license /opt/confluent/share/licenses/confluent_client/LICENSE
|
||||
%license /opt/confluent/share/licenses/confluent_client/COPYRIGHT
|
||||
%defattr(-,root,root)
|
||||
/opt/confluent
|
||||
|
||||
@@ -2,388 +2,3 @@ PATH=/opt/confluent/bin:$PATH
|
||||
export PATH
|
||||
MANPATH=/opt/confluent/share/man:$MANPATH
|
||||
export MANPATH
|
||||
# The aliases below are to signify that file globbing is unwelcome at the shell
|
||||
# this avoids a problem if a user does a noderange like 'n[21-33] and there is a file
|
||||
# in the directory like 'n3' that causes the parameter to change and target a totally
|
||||
# different node
|
||||
# Unfortunately in bourne shell, we cannot reliably ensure a prepended set-f
|
||||
# and an appended set +f are both run. alias seems to be the only mechanism
|
||||
# that can intervene before glob expansion, but it lacks power.
|
||||
# putting it into a function to append is all well and good, *except* that
|
||||
# if doing something like 'nodepower compute|grep' causes set -f to execute
|
||||
# in current shell, and the function to be in a subshell and leaves globbing
|
||||
# disabled in the parent shell. Instead, store the current command in a
|
||||
# variable and use that to check for misglobbed noderanges, which was the goal
|
||||
alias nodeattrib='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeattrib'
|
||||
alias nodebmcreset='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodebmcreset'
|
||||
alias nodeboot='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeboot'
|
||||
alias nodeconfig='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeconfig'
|
||||
alias nodeconsole='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeconsole'
|
||||
alias nodedeploy='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodedeploy'
|
||||
alias nodedefine='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodedefine'
|
||||
alias nodeeventlog='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeeventlog'
|
||||
alias nodefirmware='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodefirmware'
|
||||
alias nodegroupattrib='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodegroupattrib'
|
||||
alias nodegroupdefine='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodegroupdefine'
|
||||
alias nodegroupremove='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodegroupremove'
|
||||
alias nodehealth='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodehealth'
|
||||
alias nodeidentify='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeidentify'
|
||||
alias nodeinventory='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeinventory'
|
||||
alias nodelist='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodelist'
|
||||
alias nodemedia='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodemedia'
|
||||
alias nodepower='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodepower'
|
||||
alias noderemove='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; noderemove'
|
||||
alias nodereseat='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodereseat'
|
||||
alias noderun='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; noderun'
|
||||
alias nodesensors='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodesensors'
|
||||
alias nodesetboot='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodesetboot'
|
||||
alias nodestorage='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodestorage -i'
|
||||
alias nodeshell='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodeshell'
|
||||
alias nodelicense='CURRENT_CMDLINE=$(HISTTIMEFORMAT= builtin history 1); export CURRENT_CMDLINE; nodelicense'
|
||||
# Do not continue for non-bash shells, the rest of this sets up bash completion functions
|
||||
[ -z "$BASH_VERSION" -o -z "$PS1" ] && return
|
||||
|
||||
|
||||
_confluent_get_args()
|
||||
{
|
||||
CMPARGS=($COMP_LINE)
|
||||
NUMARGS=$((COMP_CWORD+1))
|
||||
if [ "${COMP_WORDS[COMP_CWORD]}" == '' ]; then
|
||||
CMPARGS+=("")
|
||||
fi
|
||||
GENNED=""
|
||||
for CAND in ${COMP_CANDIDATES[@]}; do
|
||||
candarray=(${CAND//,/ })
|
||||
matched=0
|
||||
for c in "${candarray[@]}"; do
|
||||
for arg in "${CMPARGS[@]}"; do
|
||||
if [ "$arg" = "$c" ]; then
|
||||
matched=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
done
|
||||
if [ 0 = $matched ]; then
|
||||
for c in "${candarray[@]}"; do
|
||||
GENNED+=" $c"
|
||||
done
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
function _confluent_generic_completion()
|
||||
{
|
||||
_confluent_get_args
|
||||
if [ $NUMARGS -ge 3 ] && [ ! -z "$GENNED" ]; then
|
||||
COMPREPLY=($(compgen -W "$GENNED" -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
fi
|
||||
if [ $NUMARGS -lt 3 ]; then
|
||||
_confluent_nr_completion
|
||||
return;
|
||||
fi
|
||||
}
|
||||
function _confluent_generic_ng_completion()
|
||||
{
|
||||
_confluent_get_args
|
||||
if [ $NUMARGS -ge 3 ] && [ ! -z "$GENNED" ]; then
|
||||
COMPREPLY=($(compgen -W "$GENNED" -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
fi
|
||||
if [ $NUMARGS -lt 3 ]; then
|
||||
_confluent_ng_completion
|
||||
return;
|
||||
fi
|
||||
}
|
||||
_confluent_nodeidentify_completion()
|
||||
{
|
||||
COMP_CANDIDATES=("on,off,blink -h")
|
||||
_confluent_generic_completion
|
||||
}
|
||||
|
||||
|
||||
_confluent_nodesetboot_completion()
|
||||
{
|
||||
COMP_CANDIDATES=("default,cd,network,setup,hd,floppy,usb -h -b -p")
|
||||
_confluent_generic_completion
|
||||
}
|
||||
|
||||
_confluent_nodepower_completion()
|
||||
{
|
||||
COMP_CANDIDATES=("boot,off,on,status -h -p")
|
||||
_confluent_generic_completion
|
||||
}
|
||||
|
||||
_confluent_nodemedia_completion()
|
||||
{
|
||||
COMP_CANDIDATES=("list,upload,attach,detachall -h")
|
||||
_confluent_get_args
|
||||
if [ $NUMARGS -gt 3 ] && [ ${CMPARGS[-2]} == 'upload' ]; then
|
||||
compopt -o default
|
||||
COMPREPLY=()
|
||||
return
|
||||
fi
|
||||
if [ $NUMARGS -ge 3 ] && [ ! -z "$GENNED" ]; then
|
||||
COMPREPLY=($(compgen -W "$GENNED" -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
return;
|
||||
fi
|
||||
if [ $NUMARGS -lt 3 ]; then
|
||||
_confluent_nr_completion
|
||||
return;
|
||||
fi
|
||||
}
|
||||
|
||||
_confluent_nodefirmware_completion()
|
||||
{
|
||||
_confluent_get_args
|
||||
if [ $NUMARGS == 3 ]; then
|
||||
COMPREPLY=($(compgen -W "list update" -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
return;
|
||||
fi
|
||||
if [ $NUMARGS -gt 3 ] && [ ${CMPARGS[2]} == 'update' ]; then
|
||||
compopt -o default
|
||||
COMPREPLY=()
|
||||
return
|
||||
fi
|
||||
if [ $NUMARGS -lt 3 ]; then
|
||||
_confluent_nr_completion
|
||||
return;
|
||||
fi
|
||||
}
|
||||
|
||||
_confluent_osimage_completion()
|
||||
{
|
||||
_confluent_get_args
|
||||
if [ $NUMARGS == 2 ]; then
|
||||
COMPREPLY=($(compgen -W "initialize import updateboot rebase" -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
return
|
||||
elif [ ${CMPARGS[1]} == 'initialize' ]; then
|
||||
COMPREPLY=($(compgen -W "-h -u -s -t -i" -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
elif [ ${CMPARGS[1]} == 'import' ]; then
|
||||
compopt -o default
|
||||
COMPREPLY=()
|
||||
return
|
||||
elif [ ${CMPARGS[1]} == 'updateboot' -o ${CMPARGS[1]} == 'rebase' ]; then
|
||||
COMPREPLY=($(compgen -W "-n $(confetty show /deployment/profiles|sed -e 's/\///')" -- "${COMP_WORDS[COMP_CWORD]}"))
|
||||
return
|
||||
fi
|
||||
}
|
||||
|
||||
_confluent_imgutil_completion()
|
||||
{
|
||||
_confluent_get_args
|
||||
if [ $NUMARGS == 2 ]; then
|
||||
COMPREPLY=($(compgen -W "build exec unpack pack capture" -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
return
|
||||
elif [ ${CMPARGS[1]} == 'build' ]; then
|
||||
COMPREPLY=($(compgen -W "-s $(ls /var/lib/confluent/distributions)" -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
return
|
||||
elif [ ${CMPARGS[1]} == 'unpack' ]; then
|
||||
if [ $NUMARGS == 3 ]; then
|
||||
COMPREPLY=($(compgen -W "-s $(ls /var/lib/confluent/public/os)" -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
return
|
||||
fi
|
||||
compopt -o dirnames
|
||||
return
|
||||
elif [ ${CMPARGS[1]} == 'pack' ]; then
|
||||
if [ $NUMARGS == 3 ]; then
|
||||
compopt -o dirnames
|
||||
fi
|
||||
COMPREPLY=()
|
||||
return
|
||||
elif [ ${CMPARGS[1]} == 'capture' ]; then
|
||||
if [ $NUMARGS == 3 ]; then
|
||||
_confluent_nn_completion
|
||||
return
|
||||
fi
|
||||
return
|
||||
elif [ ${CMPARGS[1]} == 'exec' ]; then
|
||||
compopt -o dirnames
|
||||
COMPREPLY=($(compgen -W "-v" -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
return
|
||||
fi
|
||||
}
|
||||
_confluent_nodedeploy_completion()
|
||||
{
|
||||
_confluent_get_args
|
||||
if [ $NUMARGS -gt 2 ]; then
|
||||
COMPREPLY=($(compgen -W "-n $(confetty show /deployment/profiles|sed -e 's/\///')" -- "${COMP_WORDS[COMP_CWORD]}"))
|
||||
return
|
||||
fi
|
||||
if [ $NUMARGS -lt 3 ]; then
|
||||
_confluent_nr_completion
|
||||
return;
|
||||
fi
|
||||
}
|
||||
|
||||
_confluent_nodeshell_completion()
|
||||
{
|
||||
_confluent_get_args
|
||||
if [ $NUMARGS == 3 ]; then
|
||||
COMPREPLY=($(compgen -c -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
return
|
||||
fi
|
||||
if [ $NUMARGS -gt 3 ]; then
|
||||
compopt -o default
|
||||
COMPREPLY=()
|
||||
return
|
||||
fi
|
||||
if [ $NUMARGS -lt 3 ]; then
|
||||
_confluent_nr_completion
|
||||
return;
|
||||
fi
|
||||
}
|
||||
|
||||
_confluent_nodelicense_completion()
|
||||
{
|
||||
_confluent_get_args
|
||||
if [ $NUMARGS == 3 ]; then
|
||||
COMPREPLY=($(compgen -W "install list save delete" -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
return;
|
||||
fi
|
||||
if [ $NUMARGS == 4 ] && [ ${CMPARGS[2]} == 'install' ]; then
|
||||
compopt -o default
|
||||
COMPREPLY=()
|
||||
return
|
||||
fi
|
||||
if [ $NUMARGS == 4 ] && [ ${CMPARGS[2]} == 'save' ]; then
|
||||
compopt -o dirnames
|
||||
COMPREPLY=()
|
||||
return
|
||||
fi
|
||||
if [ $NUMARGS -lt 3 ]; then
|
||||
_confluent_nr_completion
|
||||
return
|
||||
fi
|
||||
}
|
||||
|
||||
_confluent_nodesupport_completion()
|
||||
{
|
||||
_confluent_get_args
|
||||
if [ $NUMARGS == 3 ]; then
|
||||
COMPREPLY=($(compgen -W "servicedata" -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
return;
|
||||
fi
|
||||
if [ $NUMARGS == 4 ] && [ ${CMPARGS[2]} == 'servicedata' ]; then
|
||||
compopt -o dirnames
|
||||
COMPREPLY=()
|
||||
return
|
||||
fi
|
||||
if [ $NUMARGS -lt 3 ]; then
|
||||
_confluent_nr_completion
|
||||
return
|
||||
fi
|
||||
}
|
||||
|
||||
_confluent_nodeattrib_completion()
|
||||
{
|
||||
COMP_CANDIDATES=$(nodeattrib '~.>1' all | awk '{print $2}'|sed -e 's/://')
|
||||
_confluent_generic_completion
|
||||
}
|
||||
|
||||
_confluent_define_completion()
|
||||
{
|
||||
COMP_CANDIDATES=$(nodegroupattrib 'everything' all | awk '{print $2}'|sed -e 's/://')
|
||||
_confluent_get_args
|
||||
if [ $NUMARGS -ge 3 ] && [ ! -z "$GENNED" ]; then
|
||||
COMPREPLY=($(compgen -W "$GENNED" -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
fi
|
||||
}
|
||||
|
||||
_confluent_nodegroupattrib_completion()
|
||||
{
|
||||
COMP_CANDIDATES=$(nodegroupattrib 'everything' all | awk '{print $2}'|sed -e 's/://')
|
||||
_confluent_generic_ng_completion
|
||||
}
|
||||
_confluent_nn_completion()
|
||||
{
|
||||
_confluent_get_args
|
||||
INPUT=${COMP_WORDS[COMP_CWORD]}
|
||||
INPUT=${INPUT##*,-}
|
||||
INPUT=${INPUT##*,}
|
||||
INPUT=${INPUT##*@}
|
||||
PREFIX=""
|
||||
if [ "$INPUT" != "${COMP_WORDS[COMP_CWORD]}" ]; then
|
||||
PREFIX=${COMP_WORDS[COMP_CWORD]}
|
||||
PREFIX=$(echo $PREFIX | sed -e 's/,[^,@-]*$/,/' -e 's/,-[^,@]*$/,-/' -e 's/@[^,@]*/@/')
|
||||
fi
|
||||
|
||||
COMPREPLY=($(compgen -W "$(nodelist | sed -e s/^/$PREFIX/)" -- "${COMP_WORDS[COMP_CWORD]}"))
|
||||
}
|
||||
_confluent_nr_completion()
|
||||
{
|
||||
CMPARGS=($COMP_LINE)
|
||||
_confluent_get_args
|
||||
if [ $NUMARGS -gt 2 ]; then
|
||||
compopt -o default
|
||||
COMPREPLY=()
|
||||
return;
|
||||
fi
|
||||
INPUT=${COMP_WORDS[COMP_CWORD]}
|
||||
INPUT=${INPUT##*,-}
|
||||
INPUT=${INPUT##*,}
|
||||
INPUT=${INPUT##*@}
|
||||
PREFIX=""
|
||||
if [ "$INPUT" != "${COMP_WORDS[COMP_CWORD]}" ]; then
|
||||
PREFIX=${COMP_WORDS[COMP_CWORD]}
|
||||
PREFIX=$(echo $PREFIX | sed -e 's/,[^,@-]*$/,/' -e 's/,-[^,@]*$/,-/' -e 's/@[^,@]*/@/')
|
||||
fi
|
||||
|
||||
COMPREPLY=($(compgen -W "$(confetty show /nodegroups|sed -e 's/\///' -e s/^/$PREFIX/;nodelist | sed -e s/^/$PREFIX/)" -- "${COMP_WORDS[COMP_CWORD]}"))
|
||||
}
|
||||
_confluent_ng_completion()
|
||||
{
|
||||
_confluent_get_args
|
||||
if [ $NUMARGS -gt 2 ]; then
|
||||
return;
|
||||
fi
|
||||
INPUT=${COMP_WORDS[COMP_CWORD]}
|
||||
INPUT=${INPUT##*,-}
|
||||
INPUT=${INPUT##*,}
|
||||
INPUT=${INPUT##*@}
|
||||
PREFIX=""
|
||||
if [ "$INPUT" != "${COMP_WORDS[COMP_CWORD]}" ]; then
|
||||
PREFIX=${COMP_WORDS[COMP_CWORD]}
|
||||
PREFIX=$(echo $PREFIX | sed -e 's/,[^,@-]*$/,/' -e 's/,-[^,@]*$/,-/' -e 's/@[^,@]*/@/')
|
||||
fi
|
||||
|
||||
COMPREPLY=($(compgen -W "$(confetty show /nodegroups|sed -e 's/\///' -e s/^/$PREFIX/)" -- "${COMP_WORDS[COMP_CWORD]}"))
|
||||
}
|
||||
|
||||
_confluent_nodediscover_completion()
|
||||
{
|
||||
_confluent_get_args
|
||||
if [ $NUMARGS == 2 ]; then
|
||||
COMPREPLY=($(compgen -W "list assign rescan clear subscribe unsubscribe register" -- ${COMP_WORDS[COMP_CWORD]}))
|
||||
return;
|
||||
fi
|
||||
|
||||
}
|
||||
complete -F _confluent_nodediscover_completion nodediscover
|
||||
complete -F _confluent_nodeattrib_completion nodeattrib
|
||||
complete -F _confluent_nr_completion nodebmcreset
|
||||
complete -F _confluent_nodesetboot_completion nodeboot
|
||||
complete -F _confluent_nr_completion nodeconfig
|
||||
complete -F _confluent_nn_completion nodeconsole
|
||||
complete -F _confluent_define_completion nodedefine
|
||||
complete -F _confluent_define_completion nodegroupdefine
|
||||
complete -F _confluent_nr_completion nodeeventlog
|
||||
complete -F _confluent_nodefirmware_completion nodefirmware
|
||||
complete -F _confluent_nodedeploy_completion nodedeploy
|
||||
complete -F _confluent_osimage_completion osdeploy
|
||||
complete -F _confluent_imgutil_completion imgutil
|
||||
complete -F _confluent_nodegroupattrib_completion nodegroupattrib
|
||||
complete -F _confluent_ng_completion nodegroupremove
|
||||
complete -F _confluent_nr_completion nodehealth
|
||||
complete -F _confluent_nodeidentify_completion nodeidentify
|
||||
complete -F _confluent_nr_completion nodeinventory
|
||||
complete -F _confluent_nodeattrib_completion nodelist
|
||||
complete -F _confluent_nodemedia_completion nodemedia
|
||||
complete -F _confluent_nodepower_completion nodepower
|
||||
complete -F _confluent_nr_completion noderemove
|
||||
complete -F _confluent_nr_completion nodereseat
|
||||
complete -F _confluent_nodeshell_completion noderun
|
||||
complete -F _confluent_nr_completion nodesensors
|
||||
complete -F _confluent_nodesetboot_completion nodesetboot
|
||||
complete -F _confluent_nodeshell_completion nodeshell
|
||||
complete -F _confluent_nodesupport_completion nodesupport
|
||||
complete -F _confluent_nodelicense_completion nodelicense
|
||||
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
collate(1) -- Organize text input by node
|
||||
==============================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`<other command> | collate [-a] [-b] [-d] [-w] [-g] [-s] [-c] [-r] [-l lognametemplate]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
**collate** takes input in the form of <nodename>: <data> and groups
|
||||
the output for each <nodename> together, and checks for identical data to further group nodes together.
|
||||
Output groups are sorted in descending size such that the topmost group is the largest, and the
|
||||
last output group is the smallest. In the event of equally sized output groups,
|
||||
the groups are sorted alphanumerically by their header, and each header has
|
||||
node and group names sorted alphanumerically.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-a`, `--abbreviate`:
|
||||
Use confluent to attempt to shorten the noderange. This can help identify
|
||||
when output differs along a telling group boundary. For example, if
|
||||
output suggests a large number of nodes are unreachable, abbreviate
|
||||
showing 'rack1' as being unreachable may make more obvious a possible cause.
|
||||
|
||||
* `-b BASE`, `--base=BASE`:
|
||||
Use given node as reference for comparison when using
|
||||
-d, instead of using the most common result
|
||||
|
||||
* `-d`, `--diff`:
|
||||
Express all but the most common result group in terms of diff from
|
||||
the most common result group
|
||||
|
||||
* `-w`, `--watch`:
|
||||
Update results dynamically as data becomes available, rather than
|
||||
waiting for the command to fully complete.
|
||||
|
||||
* `-g`, `--groupcount`:
|
||||
Show count of output groups rather than the actual
|
||||
output
|
||||
|
||||
* `-s`, `--skipcommon`:
|
||||
Suppress printing of the most common result text group. This is used to
|
||||
focus on stray output against a well known and expected result.
|
||||
|
||||
* `-c`, `--count`:
|
||||
Print a count of the number of nodes in an output group under the
|
||||
noderange.
|
||||
|
||||
* `-r`, `--reverse`:
|
||||
Rather than starting with most common to least common, start with
|
||||
the least common and print the most common last.
|
||||
|
||||
* `-l LOG`, `--log=LOG`:
|
||||
Save output per node to individual log files, replacing {node} in the name
|
||||
with the nodename of each
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
* Organizing power state of multiple nodes:
|
||||
`# nodepower n1-n12 | collate`
|
||||
`====================================`
|
||||
`n1,n2,n3,n4,n7,n8,n9,n10,n11,n12`
|
||||
`====================================`
|
||||
`on`
|
||||
` `
|
||||
`====================================`
|
||||
`n5,n6`
|
||||
`====================================`
|
||||
`off`
|
||||
|
||||
* Using diff to detect distinct UEFI configuration
|
||||
|
||||
`# pasu n1-n4 show Processors|collate -d -s`
|
||||
`====================================`
|
||||
`n3`
|
||||
`====================================`
|
||||
`@@`
|
||||
` Processors.ProcessorPerformanceStates=Enable`
|
||||
` Processors.C-States=Enable`
|
||||
` Processors.PackageACPIC-StateLimit=ACPI C3`
|
||||
`- Processors.C1EnhancedMode=Enable`
|
||||
`+ Processors.C1EnhancedMode=Disable`
|
||||
`- Processors.Hyper-Threading=Enable`
|
||||
`+ Processors.Hyper-Threading=Disable`
|
||||
` Processors.ExecuteDisableBit=Enable`
|
||||
` Processors.IntelVirtualizationTechnology=Enable`
|
||||
` `
|
||||
`====================================`
|
||||
`n1`
|
||||
`====================================`
|
||||
`@@`
|
||||
` Processors.ProcessorPerformanceStates=Enable`
|
||||
` Processors.C-States=Enable`
|
||||
` Processors.PackageACPIC-StateLimit=ACPI C3`
|
||||
`- Processors.C1EnhancedMode=Enable`
|
||||
`+ Processors.C1EnhancedMode=Disable`
|
||||
` Processors.Hyper-Threading=Enable`
|
||||
` Processors.ExecuteDisableBit=Enable`
|
||||
|
||||
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
collective(1) -- Check and manage a confluent collective
|
||||
==============================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`collective invite <server>`
|
||||
`collective join <server> [-i TOKEN]`
|
||||
`collective show`
|
||||
`collective gencert`
|
||||
`collective delete`
|
||||
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
**collective** helps manage the collective mode of confluent, where multiple
|
||||
confluent servers are linked together to act as one. For example, the procedure to set up
|
||||
a collective to run on three servers called mgt1, mgt2, and mgt3, first install and start
|
||||
confluent as usual on the three servers. On mgt1, run `collective invite mgt2` and an
|
||||
invitation token will be output. On mgt2, either run `collective join mgt1` to paste
|
||||
the token interactively, or `collective join mgt1 -i <token>`. At this point, either
|
||||
mgt1 or mgt2 can bring in mgt3. For example on mgt2 run `collective invite mgt3` and
|
||||
on mgt3 run `collective join mgt2 -i <token>`
|
||||
|
||||
This can be linked together in the following manner with ssh:
|
||||
on mgt1:
|
||||
`# ssh mgt2 collective join mgt1 -i $(collective invite mgt2)`
|
||||
|
||||
Note that a collective is only redundant with 3 or more members. The collective
|
||||
will function so long as more than half of the members are online. A collective
|
||||
of two members is supported, but without redundancy.
|
||||
|
||||
Also note that the collective leader role is dynamic, but has no impact on interacting
|
||||
with confluent. It is merely an internal role that can dynamically change depending
|
||||
on circumstances.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-i`:
|
||||
Provide the token as an argument rather than interactively.
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
* Inviting a server called mgt2:
|
||||
`# collective invite mgt2`
|
||||
`bWd0MkA+BNQ6XAxMXlqJJa+EQRlihL/k9xCXnasgSQXZr989Pa1/ln7G3e1Ncxx6BMzMqqreHJVkPr2FrzjNit/UgHlg`
|
||||
|
||||
* On mgt2, joining mgt1:
|
||||
`# collective join mgt1 -i bWd0MkA+BNQ6XAxMXlqJJa+EQRlihL/k9xCXnasgSQXZr989Pa1/ln7G3e1Ncxx6BMzMqqreHJVkPr2FrzjNit/UgHlg`
|
||||
`Success`
|
||||
|
||||
* Showing the collective state:
|
||||
`# collective show`
|
||||
`Quorum: True`
|
||||
`Leader: mgt1`
|
||||
`Active collective members:`
|
||||
` mgt2`
|
||||
|
||||
@@ -15,21 +15,6 @@ be mostly an aid for developing client software, with
|
||||
day to day administration generally being easier with
|
||||
the various function specific commands.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-s SERVER:PORT`, `--server=SERVER:PORT`:
|
||||
Confluent instance to connect to
|
||||
|
||||
* `-c PATH`, `--control=PATH`:
|
||||
Path to offer terminal control
|
||||
|
||||
* `-m MINTIME`, `--mintime=MINTIME`:
|
||||
Minimum time to run or else pause for input (used to
|
||||
keep a terminal from closing quickly on error)
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## COMMANDS
|
||||
|
||||
The CLI may be navigated by shell commands and some other
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
confluent2hosts(8) -- Generate /etc/hosts entries for nodes
|
||||
========================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`confluent2hosts -i <ip expression> -n <name expression> <noderange>`
|
||||
`confluent2hosts -a <noderange>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`confluent2hosts` can be used to help generate /etc/hosts entries for a
|
||||
noderange. There are two general approaches.
|
||||
|
||||
It can be used ad-hoc, using -i and -n to specify the address and name portions respectively. This accepts the standard confluent expression syntax, allowing for things like 172.30.1.{n1} or {node}.{dns.domain} or {bmc}.
|
||||
|
||||
It can also read from the confluent db, using `-a`. In this mode, each net.<value>.<attribute> group is pulled together into hosts lines. ipv4_address and ipv6_address fields are associated with the corresponding hostname attributes.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
* Generate /etc/hosts entries ad-hoc using default name:
|
||||
`# confluent2hosts -i 10.2.3.{n1} d9-d12`
|
||||
|
||||
* Generate /etc/hosts entries ad-hoc using alternative name:
|
||||
`# confluent2hosts -i 10.2.3.{n1} -n "{node}-alt {node}-alt.{dns.domain}" d9-d12`
|
||||
|
||||
* Generate /etc/hosts entries using the confluent DB as a reference:
|
||||
`# confluent2hosts -a d9-d12`
|
||||
|
||||
@@ -3,7 +3,8 @@ confluentdbutil(8) -- Backup or restore confluent database
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`confluentdbutil [options] [dump|restore] <path>`
|
||||
|
||||
`confluentdbutil [options] <dump|restore> <path>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -15,28 +16,11 @@ the json files (password protected, removed from the files, or unprotected).
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-p PASSWORD`, `--password=PASSWORD`:
|
||||
* `-p`, `--password`:
|
||||
If specified, information such as usernames and passwords will be encrypted
|
||||
using the given password.
|
||||
|
||||
* `-i`, `--interactivepassword`:
|
||||
Prompt for password.
|
||||
|
||||
* `-r`, `--redact`:
|
||||
Indicates to replace usernames and passwords with a dummy string rather
|
||||
than included.
|
||||
|
||||
* `-u`, `--unprotected`:
|
||||
The keys.json file will include the encryption keys without any protection.
|
||||
|
||||
* `-s`, `--skipkeys`:
|
||||
This specifies to dump the encrypted data without
|
||||
dumping the keys needed to decrypt it. This is
|
||||
suitable for an automated incremental backup, where an
|
||||
earlier password protected dump has a protected
|
||||
keys.json file, and only the protected data is needed.
|
||||
keys do not change and as such they do not require
|
||||
incremental backup.
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
The keys.json file will include the encryption keys without any protection.
|
||||
+8
-77
@@ -3,12 +3,9 @@ nodeattrib(8) -- List or change confluent nodes attributes
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodeattrib [-b] <noderange> [all|<nodeattribute>...]`
|
||||
`nodeattrib [-b] <noderange> [<nodeattribute>...]`
|
||||
`nodeattrib <noderange> [<nodeattribute1=value1> <nodeattribute2=value2> ...]`
|
||||
`nodeattrib -c <noderange> <nodeattribute1> <nodeattribute2> ...`
|
||||
`nodeattrib -e <noderange> <nodeattribute1> <nodeattribute2> ...`
|
||||
`nodeattrib -p <noderange> <nodeattribute1> <nodeattribute2> ...`
|
||||
`nodeattrib <noderange> -s <attributes.batch> ...`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -21,57 +18,18 @@ displayed. If `-b` is specified, it will also display information on
|
||||
how inherited and expression based attributes are defined. Attributes can be
|
||||
straightforward values, or an expression as documented in nodeattribexpressions(5).
|
||||
For a full list of attributes, run `nodeattrib <node> all` against a node.
|
||||
If `-c` is specified, this will set the nodeattribute to a null value.
|
||||
If `-c` is specified, this will set the nodeattribute to a null valid.
|
||||
This is different from setting the value to an empty string.
|
||||
|
||||
Attributes may be specified by wildcard, for example `net.*switch` will report
|
||||
all attributes that begin with `net.` and end with `switch`.
|
||||
|
||||
If the word all is specified, then all available attributes are given.
|
||||
Omitting any attribute name or the word 'all' will display only attributes
|
||||
that are currently set.
|
||||
|
||||
For the `groups` attribute, it is possible to add a group by doing
|
||||
`groups,=<newgroup>` and to remove by doing `groups^=<oldgroup>`
|
||||
|
||||
Note that `nodeattrib <group>` will likely not provide the expected behavior.
|
||||
See nodegroupattrib(8) command on how to manage attributes on a group level. Running
|
||||
nodeattrib on a group will simply set node-specific attributes on each individual
|
||||
member of the group.
|
||||
|
||||
Additionally, if wanting to change from a node level attribute value to inherit from a
|
||||
member group, it is required to use '-c' to clear the attribute. Conversely, assigning
|
||||
to a blank value will allow masking a group defined attribute with an empty value.
|
||||
See nodegroupattrib(8) command on how to manage attributes on a group level.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-b`, `--blame`:
|
||||
Annotate inherited and expression based attributes to show their base value.
|
||||
|
||||
* `-c`, `--clear`:
|
||||
Clear specified nodeattributes.
|
||||
|
||||
* `-e`, `--environment`:
|
||||
Set specified attributes based on exported environment variable of matching name.
|
||||
Environment variable names may be lower case or all upper case.
|
||||
Replace . with _ as needed (e.g. info.note may be specified as either $info_note or $INFO_NOTE)
|
||||
|
||||
* `-p`, `--prompt`:
|
||||
Request interactive prompting to provide values rather than the command line
|
||||
or environment variables.
|
||||
|
||||
* `-s`, `--set`:
|
||||
Set attributes using a batch file rather than the command line. The attributes in the batch file
|
||||
can be specified as one line of key=value pairs simmilar to command line or each attribute can
|
||||
be in its own line. Lines that start with # sign will be read as a comment. See EXAMPLES for batch
|
||||
file syntax.
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Prompt if trying to set attributes on more than
|
||||
specified number of nodes.
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
Clear specified nodeattributes
|
||||
|
||||
## EXAMPLES
|
||||
* Listing matching nodes of a simple noderange:
|
||||
@@ -90,12 +48,12 @@ to a blank value will allow masking a group defined attribute with an empty valu
|
||||
`# nodeattrib n1,n2 hardwaremanagement --blame`
|
||||
`n1: hardwaremanagement.manager: 172.30.3.1`
|
||||
`n1: hardwaremanagement.method: ipmi (inherited from group everything)`
|
||||
`n1: net.switch: r8e1`
|
||||
`n1: net.switchport: 14`
|
||||
`n1: hardwaremanagement.switch: r8e1`
|
||||
`n1: hardwaremanagement.switchport: 14`
|
||||
`n2: hardwaremanagement.manager: 172.30.3.2`
|
||||
`n2: hardwaremanagement.method: ipmi (inherited from group everything)`
|
||||
`n2: net.switch: r8e1`
|
||||
`n2: net.switchport: 2`
|
||||
`n2: hardwaremanagement.switch: r8e1`
|
||||
`n2: hardwaremanagement.switchport: 2`
|
||||
|
||||
* Listing matching nodes of a simple noderange that are set:
|
||||
`# nodeattrib n1-n2 current`
|
||||
@@ -117,33 +75,6 @@ to a blank value will allow masking a group defined attribute with an empty valu
|
||||
`n1: console.method: `
|
||||
`n2: console.method: `
|
||||
|
||||
* List all switches that a node is described as connected to:
|
||||
`# nodeattrib d1 net.*switch`
|
||||
`d1: net.mgt.switch: mgtswitch1`
|
||||
`d1: net.pxe.switch: pxeswitch1`
|
||||
`d1: net.switch:`
|
||||
|
||||
* Setting attributes using a batch file with syntax similar to command line:
|
||||
`# cat nodeattributes.batch`
|
||||
`# power`
|
||||
`power.psu1.outlet=3 power.psu1.pdu=pdu2`
|
||||
`# nodeattrib n41 -s nodeattributes.batch`
|
||||
`n41: 3`
|
||||
`n41: pdu2`
|
||||
|
||||
* Setting attributes using a batch file with syntax where each attribute is in its own line:
|
||||
`# cat nodeattributes.batch`
|
||||
`# management`
|
||||
`custom.mgt.switch=switch_main`
|
||||
`custom.mgt.switch.port=swp4`
|
||||
`# nodeattrib n41 -s nodeattributes.batch`
|
||||
`n41: switch_main`
|
||||
`n41: swp4`
|
||||
|
||||
|
||||
|
||||
## SEE ALSO
|
||||
|
||||
nodegroupattrib(8), nodeattribexpressions(5)
|
||||
|
||||
## ATTRIBUTES
|
||||
@@ -9,18 +9,6 @@ expression to generate the value.
|
||||
An expression will contain some directives wrapped in `{}` characters. Within
|
||||
`{}` are a number of potential substitute values and operations.
|
||||
|
||||
Note that syntax of expressions can have overlap with the shell syntax.
|
||||
For example:
|
||||
|
||||
`$ echo (n2)`
|
||||
`-bash: syntax error near unexpected token `n2'`
|
||||
|
||||
In such a case, it helps to quote the expression to allow it to be passed:
|
||||
|
||||
`$ echo '(n2)'`
|
||||
`(n2)`
|
||||
|
||||
|
||||
The most common operation is to extract a number from the nodename. These
|
||||
values are available as n1, n2, etc. So for example attributes for a node named
|
||||
b1o2r3u4 would have {n1} as 1, {n2} as 2, {n3} as 3, and {n4} as 4.
|
||||
@@ -51,7 +39,7 @@ number of operators at the end to indicate formatting changes.
|
||||
|
||||
Another common element to pull into an expression is the node name in whole:
|
||||
|
||||
`hardwaremanagement.manager={node}-imm`
|
||||
`hardwaremanagement.manager={nodename}-imm`
|
||||
|
||||
Additionally other attributes may be pulled in:
|
||||
|
||||
@@ -59,7 +47,7 @@ Additionally other attributes may be pulled in:
|
||||
|
||||
Multiple expressions are permissible within a single attribute:
|
||||
|
||||
`hardwaremanagement.manager={node}-{hardwaremanagement.method}`
|
||||
`hardwaremanagement.manager={nodename}-{hardwaremanagement.method}`
|
||||
|
||||
A note to developers: in general the API layer will automatically recognize a
|
||||
generic set attribute to string with expression syntax and import it as an
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
nodebmcreset(8) -- Reset management controller
|
||||
=========================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodebmcreset <noderange>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`nodebmcreset` allows you to reset the management controller of the specified noderange
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Number of nodes to affect before prompting for
|
||||
confirmation
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES:
|
||||
|
||||
* Reset the management controller for nodes n1 through n4:
|
||||
`# nodebmcreset n1-n4`
|
||||
`n1: BMC Reset Successful`
|
||||
`n2: BMC Reset Successful`
|
||||
`n3: BMC Reset Successful`
|
||||
`n4: BMC Reset Successful`
|
||||
@@ -3,51 +3,13 @@ nodeboot(8) -- Reboot a confluent node to a specific device
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodeboot [options] <noderange> [default|cd|network|setup|hd]`
|
||||
`nodeboot <noderange>`
|
||||
`nodeboot <noderange>` [net|setup]
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
**nodeboot** reboots nodes in a noderange. If an additional argument is given,
|
||||
it sets the node to specifically boot to that as the next boot. This
|
||||
performs an immediate reboot without waiting for the OS. To set the boot
|
||||
device without inducing a reboot, see the `nodesetboot` command.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-b`, `--bios`:
|
||||
For a system that supports both BIOS and UEFI style boot, request BIOS style
|
||||
boot if supported (some platforms will UEFI boot with this flag anyway).
|
||||
|
||||
* `-u`, `--uefi`:
|
||||
This flag does nothing, it is for command compatibility with xCAT's rsetboot
|
||||
|
||||
* `-p`, `--persist`:
|
||||
For a system that supports it, mark the boot override to persist rather than
|
||||
be a one time change. Many systems do not support this functionality.
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specify a maximum number of nodes to boot, prompting
|
||||
if over the threshold
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
* `default`:
|
||||
Request a normal default boot with no particular device override
|
||||
|
||||
* `cd`:
|
||||
Request boot from media. Note that this can include physical CD,
|
||||
remote media mounted as CD/DVD, and detachable hard disks drives such as usb
|
||||
key devices.
|
||||
|
||||
* `network`:
|
||||
Request boot to network
|
||||
|
||||
* `setup`:
|
||||
Request to enter the firmware configuration menu (e.g. F1 setup) on next boot.
|
||||
|
||||
* `hd`:
|
||||
Boot straight to hard disk drive
|
||||
it sets the node to specifically boot to that as the next boot.
|
||||
|
||||
## EXAMPLES
|
||||
* Booting n3 and n4 to the default boot behavior:
|
||||
|
||||
@@ -1,78 +0,0 @@
|
||||
nodeconfig(8) -- Show or change node configuration
|
||||
==================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodeconfig [options] <noderange> [setting|setting=value]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
**nodeconfig** manages the configuration of nodes managed by confluent.
|
||||
Rather than manipulating the confluent database, this actually modifies the
|
||||
running configuration on the node firmware. Calling without '=' will show the
|
||||
current value, and '=' will change the value. Network information can be
|
||||
given as a node expression, as documented in the man page for nodeattribexpressions(5).
|
||||
|
||||
Note that when using nodeconfig to submit changes, it will exit when the change
|
||||
is accepted, but the endpoint may not have fully processed it. Doing a show
|
||||
immediately after doing a set may reflect older information. Also, if changing
|
||||
BIOS/UEFI settings, the change may appear in output, but generally won't
|
||||
actually be in effect until a reboot.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-c`, `--comparedefault`:
|
||||
Take the given settings and compare against default value, if available. If
|
||||
no configuration values are specified, it will show only those that differ.
|
||||
If combined with `-x`, will show all differing values except those indicated
|
||||
by `-x`
|
||||
|
||||
* `-b settings.batch`, `--batch=settings.batch`:
|
||||
Provide arguments as lines of a file, rather than the command line.
|
||||
|
||||
* `-d`, `--detail`:
|
||||
Provide detailed data as available. This can include help text and valid
|
||||
values for a setting.
|
||||
|
||||
* `-e`, `--extra`:
|
||||
Read settings that are generally not needed, but may be slow to retrieve.
|
||||
Notably this includes the IMM category of Lenovo systems. The most popular
|
||||
IMM settings are available through faster 'bmc' attributes.
|
||||
|
||||
* `-x`, `--exclude`:
|
||||
Rather than listing only the specified configuration parameters, list all
|
||||
attributes except for the specified ones
|
||||
|
||||
* `-a`, `--advanced`:
|
||||
Include advanced settings, which are normally not intended to be used
|
||||
without direction from the relevant server vendor.
|
||||
|
||||
* `-r COMPONENT`, `--restoredefault=COMPONENT`:
|
||||
Request that the specified component of the targeted nodes will have its
|
||||
configuration reset to default. Currently the only component implemented
|
||||
is uefi.
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specify a maximum number of nodes to configure, prompting if over
|
||||
the threshold
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
* Showing the current IP configuration of noderange BMC/IMM/XCC:
|
||||
`# nodeconfig s3,s4 bmc`
|
||||
`s3: bmc.ipv4_address: 172.30.254.193/16`
|
||||
`s3: bmc.ipv4_method: DHCP`
|
||||
`s3: bmc.ipv4_gateway: 172.30.0.6`
|
||||
`s4: bmc.ipv4_address: 172.30.254.192/16`
|
||||
`s4: bmc.ipv4_method: DHCP`
|
||||
`s4: bmc.ipv4_gateway: 172.30.0.6`
|
||||
|
||||
* Changing nodes `s3` and `s4` to have the ip addressess 10.1.2.3 and 10.1.2.4 with a 16 bit subnet mask:
|
||||
`# nodeconfig s3,s4 bmc.ipv4_address=10.1.2.{n1}/16`
|
||||
|
||||
## SEE ALSO
|
||||
|
||||
nodeattribexpressions(5)
|
||||
|
||||
@@ -2,7 +2,7 @@ nodeconsole(8) -- Open a console to a confluent node
|
||||
=====================================================
|
||||
|
||||
## SYNOPSIS
|
||||
`nodeconsole [options] <noderange> [kill][-- [passthroughoptions]]`
|
||||
`nodeconsole <node>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -11,50 +11,6 @@ text or serial console of a system. Exiting is done by hitting `Ctrl-e`, then `
|
||||
then `.`. Note that console output by default is additionally logged to
|
||||
`/var/log/confluent/consoles/`**NODENAME**.
|
||||
|
||||
When the console connection to the target is broken, then confluent on backend
|
||||
will initiate an automatic retry interval that is randomized between 2 and 4 minutes.
|
||||
The reopen escape sequence below requests an immediate retry, as does connecting
|
||||
a new session.
|
||||
|
||||
When a windowed console is open the `nodeconsole <noderange> kill` command will kill the
|
||||
console process which will result in the console window closing.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-t`, `--tile`:
|
||||
Use tmux to arrange consoles of the given noderange into a tiled layout on
|
||||
the terminal screen
|
||||
|
||||
* `-l`, `--log`:
|
||||
Perform a log reply on the current, local log in /var/log/confluent/consoles.
|
||||
If in collective mode, this only makes sense to use on the current collective
|
||||
manager at this time.
|
||||
|
||||
* `-T`, `--Timestamp`:
|
||||
Dump the log with Timpstamps on the current, local log in /var/log/confluent/consoles.
|
||||
If in collective mode, this only makes sense to use on the current collective
|
||||
manager at this time.
|
||||
|
||||
* `-w`, `--windowed`:
|
||||
Open terminal windows for each node. The
|
||||
environment variable **NODECONSOLE_WINDOWED_COMMAND**
|
||||
should be set, which should be a text string corresponding
|
||||
to a command that can be used to open a windowed console,
|
||||
omitting the `nodeconsole <noderange>` part of the
|
||||
command, for example, to open a set of consoles for a
|
||||
range of nodes in separate xterm windows, set
|
||||
**NODECONSOLE_WINDOWED_COMMAND** to `xterm -e`. To open a
|
||||
set of consoles for a range of nodes in separate
|
||||
GNOME Terminal windows with a size of 100 columns and
|
||||
31 rows, set **NODECONSOLE_WINDOWED_COMMAND**
|
||||
to `gnome-terminal --geometry 100x31 --` or in a WSL
|
||||
environment, to open a set of consoles for a range of
|
||||
nodes in separate Windows Terminal windows, with the
|
||||
title set for each node, set **NODECONSOLE_WINDOWED_COMMAND**
|
||||
to `wt.exe wsl.exe -d AlmaLinux-8 --shell-type login. If the
|
||||
NODECONSOLE_WINDOWED_COMMAND environment variable isn't set,
|
||||
xterm will be used bydefault.
|
||||
|
||||
## ESCAPE SEQUENCE COMMANDS
|
||||
|
||||
While connected to a console, a number of commands may be performed through escape
|
||||
@@ -86,22 +42,7 @@ keystroke will be interpreted as a command. The following commands are availabl
|
||||
* `pbn`:
|
||||
[Power Boot Network]
|
||||
Request immediate boot to network
|
||||
* `r`:
|
||||
[send Resize]
|
||||
This queries the current terminal and sends stty commands to advertise the user termineal
|
||||
size to the remote console
|
||||
* `?`:
|
||||
Get a list of supported commands
|
||||
* `<ent>`:
|
||||
Hit enter to skip entering a command at the escape prompt.
|
||||
|
||||
|
||||
## PASSTHROUGH OPTIONS
|
||||
|
||||
While opening a windowed console with xterm or any other console of choice. The
|
||||
nodeconsole command gives capality to specify passthrough options targeted at
|
||||
the console. All options after the -- will be parsed the console program. For
|
||||
example, opening a windowed console using xterm with a black background.
|
||||
`nodeconconsole -w n1 -- -bg black`
|
||||
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
nodedefine(8) -- Define new confluent nodes
|
||||
===================================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodedefine <noderange> [nodeattribute1=value1> <nodeattribute2=value2> ...]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`nodedefine` allows the definition of new nodes for the confluent management
|
||||
system. It has the same syntax as `nodeattrib(8)`, and the commands differ in
|
||||
that `nodeattrib(8)` will error if a node does not exist.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
* Define two racks of nodes, named r{rack}u{u}:
|
||||
`# nodedefine r1u1-r2u4`
|
||||
`r1u4: created`
|
||||
`r1u1: created`
|
||||
`r1u2: created`
|
||||
`r1u3: created`
|
||||
`r2u4: created`
|
||||
`r2u3: created`
|
||||
`r2u2: created`
|
||||
`r2u1: created`
|
||||
|
||||
## SEE ALSO
|
||||
|
||||
noderange(5), nodeattribexpressions(8)
|
||||
@@ -1,46 +0,0 @@
|
||||
nodedeploy(8) -- Request preparation and/or initiating a node deployment
|
||||
=========================================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodedeploy [-h] [-c] [-n] [-p] [-m MAXNODES] <noderange> [profile]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
**nodedeploy** configures attributes and interacts with BMC devices as appropriate
|
||||
to change the operating system on the noderange to that indicated by `profile`. Tab completion
|
||||
is supported under bash to help list and select a profile name. `-n` indicates that it
|
||||
will be a network based deployment (either HTTP or PXE) and `-p` suppresses any activity
|
||||
that requires the BMC (e.g. setting the next boot device and rebooting the system). Currently
|
||||
**nodedeploy** only supports `-n` style deployment flows. Without options it shows the current
|
||||
deployment status.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-c`, `--clear`:
|
||||
Remove any pending deployment action
|
||||
|
||||
* `-n`, `--network`:
|
||||
Prepare for either an HTTP or PXE based deployment, setting boot device to network and rebooting unless `-p` is specified.
|
||||
|
||||
* `-p`, `--prepareonly`:
|
||||
Prepare the network services for deployment, but do not interact with BMCs. This is intended for scenarios where
|
||||
the boot device control and server restart will be handled outside of confluent.
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specifiy a maximum nodes to be deployed.
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
* Begin the instalalation of a profile of CentOS 8.2:
|
||||
`# nodedeploy d4 -n centos-8.2-x86_64-default`
|
||||
`d4: network`
|
||||
`d4: reset`
|
||||
|
||||
* Check current deployment state of nodes:
|
||||
`# nodedeploy d4`
|
||||
`d4: pending: centos-8.2-x86_64-default (node authentication armed)`
|
||||
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
nodediscover(8) -- List or manage confluent node discovery
|
||||
=========================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodediscover rescan`
|
||||
`nodediscover [options] list`
|
||||
`nodediscover [options] assign`
|
||||
`nodediscover [options] rescan`
|
||||
`nodediscover [options] clear`
|
||||
`nodediscover [options] subscribe [switch]`
|
||||
`nodediscover [options] unsubscribe [switch]`
|
||||
`nodediscover [options] register [Addresses, e.g. 192.168.1.2 or 192.168.1.0/24 or 192.168.1.1-192.168.1.28]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
**nodediscover** provides streamlined access to the confluent discovery data
|
||||
and assignment. Nodes are detected through either an active scan (as occurs
|
||||
at service startup and on request by nodediscover rescan) or through passive
|
||||
detection (as a target comes online, it may attempt to register with the
|
||||
network).
|
||||
|
||||
**nodediscover list** provides the currently known data in tabular format. The
|
||||
data may be filtered by various parameters, as denoted in the options below.
|
||||
|
||||
**nodediscover assign** performs manual discovery, assigning an entry to a node
|
||||
identity or, using `-i`, using a csv file to assign nodes all at once. For
|
||||
example, a spreadsheet of serial numbers to desired node names could be used.
|
||||
Note that if you see that the host is unreachable, it may be due to the IP
|
||||
address on the endpoint having changed since last detected. In such a case, it
|
||||
may help to **clear** and try **assign** again.
|
||||
|
||||
**nodediscover rescan** requests the server to do an active sweep for new
|
||||
devices. Generally every effort is made to passively detect devices as they
|
||||
become available (as they boot or are plugged in), however sometimes an active
|
||||
scan is the best approach to catch something that appears to be missing.
|
||||
|
||||
**nodedsicover clear** requests the server forget about a selection of
|
||||
detected device. It takes the same arguments as **nodediscover list**.
|
||||
|
||||
**nodediscover subscribe** and **unsubscribe** instructs confluent to subscribe to or
|
||||
unsubscribe from the designated switch running affluent with system discovery support.
|
||||
|
||||
**nodediscover register** instructs confluent to perform a remote probe of an address, subnet,
|
||||
or range of IP addresses.
|
||||
|
||||
## CSV FORMAT
|
||||
|
||||
The CSV format used by nodediscover consists of one header describing the
|
||||
columns followed by the data. The available columns are:
|
||||
|
||||
* node: The name desired for the node in confluent
|
||||
* groups: A comma delimited list of groups to put the node into (using normal CSV escape rules for the commas)
|
||||
* mac: The mac address of the node
|
||||
* serial: The serial number of the node
|
||||
* uuid: The uuid of the node
|
||||
* bmc: The name or ip address that should be assigned to the BMC, regardless of current address
|
||||
* bmc_gateway: IP address of gateway, if desired
|
||||
* bmcuser: The desired username for the BMC to have as administrator
|
||||
* bmcpass: The desired password for the BMC to require
|
||||
|
||||
Note that node is the only mandatory field. To identify the systems, one of
|
||||
mac, serial, or uuid should be specified, it is pointless to provide more than
|
||||
one of these columns. Other attributes if not provided may be defined through
|
||||
nodeattrib or group inherited. It is possible to define nodes without ever
|
||||
providing a BMC ip, in which case IPv6 will be used automatically if possible.
|
||||
|
||||
One example of a valid CSV file would be:
|
||||
node,serial,bmc,bmcuser,bmcpass
|
||||
n1,06DPMDF,172.30.204.1,admin,Passw0rd12
|
||||
n2,J30002HG,172.30.204.2,admin,Passw0rd12
|
||||
|
||||
Which would use the serial number to assign the name and other three values to
|
||||
the nodes.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-m MODEL`, `--model=MODEL`:
|
||||
Operate with nodes matching the specified model number
|
||||
* `-s SERIAL`, `--serial=SERIAL`:
|
||||
Operate against the system matching the specified
|
||||
serial number
|
||||
* `-u UUID`, `--uuid=UUID`:
|
||||
Operate against the system matching the specified UUID
|
||||
* `-n NODE`, `--node=NODE`:
|
||||
Operate with the given nodename
|
||||
* `-e MAC`, `--ethaddr=MAC`:
|
||||
Operate against the system with the specified MAC
|
||||
address
|
||||
* `-f FIELDS`, `--fields=FIELDS`:
|
||||
Request a custom set of fields. The available fields are:
|
||||
Node: The node name if a correlation has been identified
|
||||
Model: The model number
|
||||
Serial: The serial number
|
||||
UUID: The UUID as it should appear in DMI
|
||||
Type: Device type (e.g. lenovo-xcc, pxe-client, etc)
|
||||
IP: The confirmed working IP addresses associated with the record
|
||||
Mac: Mac address of the relevant network interface
|
||||
Switch: The nearest detected switch to the entry
|
||||
Port: Port of the switch that most closely connects to the network interface
|
||||
Advertised IP: IP addresses that may not have been confirmed, but are advertised
|
||||
* `-o ORDER`, `--order=ORDER`:
|
||||
Order output by given field. Field names are the same as documented in the -f argument.
|
||||
* `-t TYPE`, `--type=TYPE`:
|
||||
Operate against the system of the specified type
|
||||
* `-c`, `--csv`:
|
||||
Use CSV formatted output
|
||||
* `-i IMPORT.CSV`, `--import=IMPORT.CSV`:
|
||||
Import bulk assignment data from given CSV file
|
||||
* `-d STATE`, `--discoverystate=STATE`:
|
||||
Indicate devices with a particular state. The states are listed below
|
||||
* discovered: The device has been identified and has also had discovery
|
||||
activities performed, including any relevant certificate
|
||||
exchanges and deploying user and network configuration.
|
||||
* identified: The device has been identified as to what node it is
|
||||
supposed to be, however no active changes to the attributes
|
||||
or device configuration has been performed. This is
|
||||
generally discovery awaiting approval due to
|
||||
discovery.policy specifying a strict security model.
|
||||
* unidentified: A device has been sensed, but no node identity has been
|
||||
established at all. It provides data that can be used
|
||||
for nodediscover assign, as well as current IP addresses
|
||||
that can be used for manual efforts as possible.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
* Listing all detected Lenovo IMMv2 systems on a local network:
|
||||
`# nodediscover list -t lenovo-imm2`
|
||||
` Node| Model| Serial| UUID| Mac Address| Type| Current IP Addresses`
|
||||
`---------------|---------------|---------------|------------------------------------|-----------------|------------|------------------------------------------------`
|
||||
` r2| 5463AC1| 06DPMDF|5f7133b8-c8cb-11e4-99a9-40f2e9b91018|40:f2:e9:b9:10:1d| lenovo-imm2| 172.30.204.1,fe80::42f2:e9ff:feb9:101d%eth1`
|
||||
` | 7906AC1| 06PBX15|e98d483d-2759-11e1-8ffd-5cf3fc11249c|5c:f3:fc:11:24:9f| lenovo-imm2| 172.30.3.12,fe80::5ef3:fcff:fe11:249f%eth1`
|
||||
` n1| 8737AC1| 23XXH41|14dd3ba6-5c38-11e1-931a-5cf3fc6e4680|5c:f3:fc:6e:13:e1| lenovo-imm2| 172.30.3.1,fe80::5ef3:fcff:fe6e:13e1%eth1`
|
||||
` n7| 8737AC1| 23XXH32|79d2ce28-5cd5-11e1-8c86-5cf3fc6e46b0|5c:f3:fc:6e:13:f9| lenovo-imm2| 172.30.3.7,fe80::5ef3:fcff:fe6e:13f9%eth1`
|
||||
` n8| 8737AC1| 23XXH49|551a8438-5cd5-11e1-8d6c-5cf3fc6e4708|5c:f3:fc:6e:14:25| lenovo-imm2| 172.30.3.8,fe80::5ef3:fcff:fe6e:1425%eth1`
|
||||
` n3| 8737AC1| 23XXH30|1dd7f7b3-5da5-11e1-baf0-5cf3fc6e4738|5c:f3:fc:6e:14:3d| lenovo-imm2| 172.30.3.3,fe80::5ef3:fcff:fe6e:143d%eth1`
|
||||
` n4| 8737AC1| 23XXH35|45b81dae-5d9b-11e1-8337-5cf3fc6e4858|5c:f3:fc:6e:14:cd| lenovo-imm2| 172.30.3.4,fe80::5ef3:fcff:fe6e:14cd%eth1`
|
||||
` n11| 8737AC1| 23XXH12|31d90128-5c37-11e1-bdb7-5cf3fc6e4920|5c:f3:fc:6e:15:31| lenovo-imm2| 172.30.3.11,fe80::5ef3:fcff:fe6e:1531%eth1`
|
||||
` n13| 8737AC1| 23XXH44|e23a138a-5cd3-11e1-8f3d-5cf3fc6e4950|5c:f3:fc:6e:15:49| lenovo-imm2| 172.30.3.13,fe80::5ef3:fcff:fe6e:1549%eth1`
|
||||
` | 8737AC1| 23XXH29|5cd1216b-5c37-11e1-ba0c-5cf3fc6e49c8|5c:f3:fc:6e:15:85| lenovo-imm2| 172.30.3.9,fe80::5ef3:fcff:fe6e:1585%eth1`
|
||||
` | 8737AC1| 23ZYT44|f4bf48ca-71f0-11e1-b274-5cf3fc6e4f10|5c:f3:fc:6e:18:29| lenovo-imm2| 172.30.3.10,fe80::5ef3:fcff:fe6e:1829%eth1`
|
||||
` hpcedr| 7915AC1| 06DRHL5|a64e3014-d7e3-11e1-8d21-6cae8b1dff32|6c:ae:8b:1d:ff:36| lenovo-imm2| 172.30.254.250,fe80::6eae:8bff:fe1d:ff36%eth1`
|
||||
` | 8737AC1| 06YRWC3|3af85a51-7efd-11e3-8599-000af7482e00|6c:ae:8b:32:cb:c5| lenovo-imm2| 172.30.3.6,fe80::6eae:8bff:fe32:cbc5%eth1`
|
||||
` | 8737AC1| 06YRWB7|b230f62e-7efd-11e3-9773-000af7482980|6c:ae:8b:32:cd:01| lenovo-imm2| 172.30.3.5,fe80::6eae:8bff:fe32:cd01%eth1`
|
||||
` | 8737AC1| 06YRWC7|09586005-7efe-11e3-9f03-000af7482df0|6c:ae:8b:32:cd:a5| lenovo-imm2| 172.30.3.2,fe80::6eae:8bff:fe32:cda5%eth1`
|
||||
|
||||
* Manually assign a single node according to serial number:
|
||||
`[root@odin ~]# nodediscover assign -s 06PBX15 -n n12`
|
||||
`Assigned: n12`
|
||||
|
||||
* Bulk execute discovery based on spreadsheet:
|
||||
`[root@odin ~]# nodediscover assign -i import.csv`
|
||||
`Defined r2`
|
||||
`Discovered r2`
|
||||
`Defined c1`
|
||||
`Discovered c1`
|
||||
@@ -3,31 +3,14 @@ nodeeventlog(8) -- Pull eventlog from confluent nodes
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodeeventlog [options] <noderange> [clear]`
|
||||
`nodeeventlog <noderange>`
|
||||
`nodeeventlog <noderange> [clear]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`nodeeventlog` pulls and optionally clears the event log from the requested
|
||||
noderange.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specify a maximum number of nodes to clear if clearing log, prompting if
|
||||
over the threshold
|
||||
|
||||
* `-l LINES`, `--lines=LINES`:
|
||||
return the last <n> entries for each node in the eventlog.
|
||||
|
||||
* `-t TIMEFRAME`, `--timeframe=TIMEFRAME`:
|
||||
return entries within a specified timeframe for each node's event log.
|
||||
This will return entries from the last <n> hours or days. 1h would be
|
||||
entries from with the last one hour.
|
||||
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
* Pull the event log from n2 and n3:
|
||||
`# nodeeventlog n2,n3`
|
||||
|
||||
@@ -3,38 +3,11 @@ nodefirmware(8) -- Report firmware information on confluent nodes
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodefirmware <noderange> [list][update [--backup <file>]]|[<components>]`
|
||||
`nodefirmware <noderange>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`nodefirmware` reports and updates various firmware on nodes. By default it
|
||||
will retrieve all firmware, but can be directed to fetch specific firmware by
|
||||
calling out the name of the firmware (e.g. uefi or xcc) or request reading only
|
||||
core firmware firmware by using the word 'core', which is generally a quicker
|
||||
operation. Different hardwaremanagement.method indicated plugins may have
|
||||
different capabilities available. For example, the 'core' distinction may
|
||||
not be relevant to redfish. Additionally, the Lenovo XCC makes certain
|
||||
information available over IPMI that is not otherwise available (for example
|
||||
the FPGA version where applicable).
|
||||
|
||||
In the update form, it accepts a single file and attempts to update it using
|
||||
the out of band facilities. Firmware updates can end in one of three states:
|
||||
|
||||
* `error`: The attempted update encountered an error that prevented successful install. Nodes experiencing this state will be reported at the end and more detail on the error will be given
|
||||
* `pending`: The firmware update process has completed, but the firmware will not be active until the relevant component next resets. Generally speaking, for UEFI update the system will need a reboot, and for BMC updates, the `nodebmcreset` command will begin the process to activate the firmware.
|
||||
* `complete`: The firmware update process has completed and activation has proceeded. Note that while the activation process has commenced, the component may still be in the process of rebooting when nodefirmware exits.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-b`, `--backup`:
|
||||
Target a backup bank rather than primary
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
When updating, prompt if more than the specified number of servers will
|
||||
be affected
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
`nodefirmware` reports various pieces of firmware on confluent nodes.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
|
||||
-15
@@ -7,7 +7,6 @@ nodegroupattrib(8) -- List or change confluent nodegroup attributes
|
||||
`nodegroupattrib <group> [<nodeattribute>...]`
|
||||
`nodegroupattrib <group> [<nodeattribute1=value1> <nodeattribute2=value2> ...]`
|
||||
`nodegroupattrib <group> [-c] [<nodeattribute1> <nodeattribute2=value2> ...]`
|
||||
`nodeattrib -p <noderange> <nodeattribute1> <nodeattribute2> ...`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -25,20 +24,8 @@ the attributes are set on the node versus a group to which a node belongs.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-b`, `--blame`:
|
||||
Show information about how attributes inherited
|
||||
|
||||
* `-e`, `--environment`:
|
||||
Set attributes, but from environment variable of same name
|
||||
|
||||
* `-c`, `--clear`:
|
||||
Clear specified nodeattributes.
|
||||
|
||||
* `-p`, `--prompt`:
|
||||
Prompt for attribute values interactively
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
@@ -53,5 +40,3 @@ the attributes are set on the node versus a group to which a node belongs.
|
||||
## SEE ALSO
|
||||
|
||||
nodeattrib(8), nodeattribexpressions(5)
|
||||
|
||||
## ATTRIBUTES
|
||||
@@ -1,24 +0,0 @@
|
||||
nodegroupdefine(8) -- Define new confluent node group
|
||||
===================================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodegroupdefine <groupname> [nodeattribute1=value1> <nodeattribute2=value2> ...]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`nodegroupdefine` allows the definition of a new nodegroup for the confluent management
|
||||
service. It may only define a single group name at a time.
|
||||
It has the same syntax as `nodegroupattrib(8)`, and the commands differ in
|
||||
that `nodegroupattrib(8)` will error if a node group does not exist.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
* Create a group called `compute`:
|
||||
`# nodegroupdefine compute`
|
||||
`compute: created`
|
||||
|
||||
|
||||
## SEE ALSO
|
||||
|
||||
nodeattribexpressions(8), nodegroupattrib(8), nodegroupremove(8)
|
||||
@@ -1,14 +0,0 @@
|
||||
nodegrouplist(8) -- List the defined confluent nodegroups
|
||||
===================================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodegrouplist`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`nodegrouplist` lists the currently defined groups in confluent.
|
||||
|
||||
## SEE ALSO
|
||||
|
||||
nodeattrib(8), nodeattribexpressions(5), nodegroupattrib(8)
|
||||
@@ -1,18 +0,0 @@
|
||||
nodegroupremove(8) -- Remove a nodegroup from the confluent database
|
||||
====================================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodegroupremove <noderange>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`nodegroupremove` simply removes the given single nodegroup from the confluent database.
|
||||
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
* Remove group called testgroup
|
||||
`# nodegroupremove testgroup`
|
||||
`testgroup: deleted`
|
||||
|
||||
@@ -3,7 +3,7 @@ nodeidentify(8) -- Control the identify LED of confluent nodes
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodidentify <noderange> [on|off|blink]`
|
||||
`nodidentify <noderange> [on|off]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -13,7 +13,6 @@ options are supported:
|
||||
|
||||
* `on`: Turn on the identify LED
|
||||
* `off`: Turn off the identify LED
|
||||
* `blink`: Set the identify LED to blink (when supported by the system)
|
||||
|
||||
## EXAMPLES:
|
||||
|
||||
|
||||
@@ -3,15 +3,13 @@ nodeinventory(8) -- Get hardware inventory of confluent node
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodeinventory <noderange> [serial|model|uuid|mac]`
|
||||
`nodeinventory <noderange>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`nodeinventory` pulls information about hardware of a node. This includes
|
||||
information such as adapters, serial numbers, processors, and memory modules,
|
||||
as supported by the platforms hardware management implementation. It accepts
|
||||
arguments such as serial or model or others as listed above to filter
|
||||
output to specific data.
|
||||
as supported by the platforms hardware management implementation.
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
@@ -106,16 +104,3 @@ output to specific data.
|
||||
`r1: Broadcom NetXtreme Gigabit Ethernet Controller MAC Address 4: 40:f2:e9:af:45:a3`
|
||||
`r1: Broadcom NetXtreme Gigabit Ethernet Controller PCI slot: 1b:00`
|
||||
`r1: Broadcom NetXtreme Gigabit Ethernet Controller location: Onboard`
|
||||
|
||||
* Reporting just the mac addresses of a node named r2:
|
||||
`# nodeinventory r2 mac`
|
||||
`r2: System MAC Address 1: 40:f2:e9:b9:10:18`
|
||||
`r2: System MAC Address 2: 40:f2:e9:b9:10:19`
|
||||
`r2: System MAC Address 3: 40:f2:e9:b9:10:1a`
|
||||
`r2: System MAC Address 4: 40:f2:e9:b9:10:1b`
|
||||
|
||||
* Getting the model number and serial number of a node named s2:
|
||||
`# nodeinventory stark2 serial model`
|
||||
`s2: System Product name: LENOVO THINKSYSTEM SD530 SERVER`
|
||||
`s2: System Serial Number: DEV0000002`
|
||||
`s2: System Model: 7X2104Z028`
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
nodelicense(8) -- Manage license keys on BMC
|
||||
=================================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodelicense <noderange> [list][install <license_filename>|save <directory>|delete <license_feature_name>]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`nodelicense` manages license keys on supported BMCs. Without an argument, the command
|
||||
lists currently installed license. Using `delete` will remove the specified license name
|
||||
from the BMC. The `save` subcommand will take the passed directory (which may be in the form
|
||||
of /path/to/{node}/ to have the node name substituted for each node) and back up installed licenses
|
||||
to that directory. The `install` command will take the specified filename and install. The filename
|
||||
argument may be of the form xcc_fod_0034_7X21{id.serial}.key to have the serial number substituted
|
||||
to allow unique licenses to be specified in a single command.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specify a maximum number of nodes to delete licenses from, prompting if over the threshold
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
@@ -4,7 +4,7 @@ nodelist(8) -- List confluent nodes and their attributes
|
||||
## SYNOPSIS
|
||||
|
||||
`nodelist <noderange>`
|
||||
`nodelist <noderange> [-b] [-d] {string} <nodeattribute>...`
|
||||
`nodelist <noderange> [-b] <nodeattribute>...`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -17,15 +17,11 @@ displayed. If `-b` is specified, it will also display information on
|
||||
how inherited and expression based attributes are defined. There is more
|
||||
information on node attributes in nodeattributes(5) man page.
|
||||
|
||||
Attributes may be specified by wildcard, for example `net.*switch` will report
|
||||
all attributes that begin with `net.` and end with `switch`.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-b`, `--blame`:
|
||||
Annotate inherited and expression based attributes to show their base value.
|
||||
* `-d`, `--delim`:
|
||||
Choose a delimiter to separat the values. Default - ENTER.
|
||||
|
||||
## EXAMPLES
|
||||
* Listing matching nodes of a simple noderange:
|
||||
`# nodelist n1-n4`
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
nodemedia(8) -- Manage server remote media
|
||||
=========================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodemedia <noderange> [attach|detachall|list|upload] [options] <media>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
**nodemedia** manages the remote media functionality of supported BMCs.
|
||||
|
||||
`list` shows all the current remote media the BMCs of the noderange are
|
||||
providing to the host platform. The string (insecure) is appended to URLs that
|
||||
are mounted in an insecure fashion. http is insecure, and https is also
|
||||
insecure when no meaningful certificate validation is performed. Currently
|
||||
there is no action that can change this, and this is purely informational. A
|
||||
future version of software may provide a means to increase security of attached
|
||||
remote media. If no media is mounted, this will provide no output, error
|
||||
conditions will result in output to standard error.
|
||||
|
||||
`detachall` removes all the currently provided media to the host. This unlinks
|
||||
remote media from urls and deletes uploaded media from the BMC.
|
||||
|
||||
`upload` takes the given media image and uploads it to the BMC. This causes
|
||||
the remote media to reside internally to the system without having to go
|
||||
to the network after the upload. This is more constrained, for example the
|
||||
Lenovo xClarity Controller has a limit of 50 megabytes, but it has zero ongoing
|
||||
load on the media source.
|
||||
|
||||
`attach` takes a URL to a remote media as an argument, and has the given
|
||||
BMCs map a virtual USB device to that url. Content is loaded on demand, and
|
||||
as such that URL is referenced potentially once for every IO operation that
|
||||
the host platform attempts.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
* Listing currently mounted media:
|
||||
`# nodemedia s1-s4 list`
|
||||
`s1: boot.img`
|
||||
`s2: boot.img`
|
||||
`s4: boot.img`
|
||||
|
||||
* Uploading a small boot image to the BMC:
|
||||
`# nodemedia s1-s4 upload boot.img`
|
||||
`s1:complete: 100% s2:complete: 100% s3:complete: 100% s4:complete: 100%`
|
||||
`s1: boot.img`
|
||||
`s4: boot.img`
|
||||
`s2: boot.img`
|
||||
|
||||
* Attaching a larger ISO for on-demand access:
|
||||
`# nodemedia s1,s4 attach http://172.30.0.6/install/rhel74.iso`
|
||||
`s4: http://172.30.0.6/install/rhel74.iso (insecure)`
|
||||
`s1: http://172.30.0.6/install/rhel74.iso (insecure)`
|
||||
@@ -1,47 +0,0 @@
|
||||
nodeping(8) -- Pings a node or a noderange.
|
||||
==============================
|
||||
## SYNOPSIS
|
||||
`nodeping [options] noderange`
|
||||
|
||||
## DESCRIPTION
|
||||
**nodeping** is a command that pings the default NIC on a node.
|
||||
It can also be used with the `-s` flag to change the ping location to something that is 'non primary'
|
||||
|
||||
|
||||
## OPTIONS
|
||||
* ` -f` COUNT, `-c` COUNT, --count=COUNT
|
||||
Number of commands to run at a time
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
* `-s` SUBSTITUTENAME, --substitutename=SUBSTITUTENAME
|
||||
Use a different name other than the nodename for ping. This may be a
|
||||
expression, such as {bmc} or, if no { character is present, it is treated as a suffix. -s -eth1 would make n1 become n1-eth1, for example.
|
||||
|
||||
|
||||
## EXAMPLES
|
||||
* Pinging a node :
|
||||
`# nodeping <node>`
|
||||
`node : ping`
|
||||
|
||||
* Pinging a group:
|
||||
`# nodeping <groupname>`
|
||||
`Node1 : ping
|
||||
Node2 : ping
|
||||
Node3 : ping`
|
||||
|
||||
* Pinging BMC on a node:
|
||||
`# nodeping -s {bmc} <noderange>`
|
||||
` Node-bmc : ping`
|
||||
|
||||
* Pinging by specifying a suffix:
|
||||
`# nodeping d1-d4 -s -eth1`
|
||||
`d2-eth1: no_ping`
|
||||
`d1-eth1: no_ping`
|
||||
`d3-eth1: no_ping`
|
||||
`d4-eth1: no_ping`
|
||||
|
||||
* Fail to ping node:
|
||||
`# nodeping <node>`
|
||||
`node : no_ping`
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@ nodepower(8) -- Check or change power state of confluent nodes
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodepower [options] <noderange> ([status|on|off|shutdown|boot|reset])`
|
||||
`nodepower <noderange>`
|
||||
`nodepower <noderange> [on|off|boot|shutdown|reset|status]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -24,21 +25,6 @@ respond.
|
||||
* `reset`: Request immediate reset of nodes of the noderange. Nodes that are
|
||||
off will not react to this request.
|
||||
* `status`: Behave identically to having no argument passed at all.
|
||||
* `pdu_status`: Query state of associated PDU outlets, if configured.
|
||||
* `pdu_on`: Energize all PDU outlets associated with the noderange.
|
||||
* `pdu_off`: De-energize all PDU outlets associated with the noderange.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-p`, `--showprevious`:
|
||||
Show previous power state for all directives that may change power state.
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specify a maximum number of nodes to change power state, prompting if
|
||||
over the threshold
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
* Get power state of nodes n1 through n4:
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
noderange(5) -- Indicate a target set of nodes in confluent
|
||||
=========================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`<noderange>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
<noderange> is a syntax that can be used in most Confluent commands to conveniently specify a list of nodes. The result is that the command will be applied to a range of nodes, often in parallel.
|
||||
|
||||
## EXAMPLES:
|
||||
|
||||
The simplest noderange is a single node name:
|
||||
`n1`
|
||||
|
||||
Nodes and groups may be used interchangeably. The following may be used in any context where n1 would be accepted as a noderange:
|
||||
`rack1`
|
||||
|
||||
Commonly, there is a desire to target a range of elements. There are a few identically behaving syntaxes for the purpose.
|
||||
`n1:n20`
|
||||
`n1-n20`
|
||||
`n[1-20]`
|
||||
|
||||
Note that numbers may be zero padded or not, it will automatically detect the padding amount and adjust members of the range accordingly. Ranges also can understand multiple numeric values changing:
|
||||
`r[1-3]u[01-10]`
|
||||
`r1u01-r3u10`
|
||||
|
||||
Ranges can also be applied to group names, and all above syntaxes are compatible:
|
||||
`rack1-rack10`
|
||||
`rack[1-10]`
|
||||
|
||||
Also, regular expressions may be used to indicate nodes with names matching certain patterns:
|
||||
`~r1u..`
|
||||
|
||||
The other major noderange primitive is indicating nodes by some attribute value:
|
||||
`location.rack=7`
|
||||
|
||||
The attribute name may use a wildcard:
|
||||
`net.*switch=switch1`
|
||||
|
||||
Commas can be used to indicate multiple nodes, and can mix and match any of the above primitives. The following can be a valid single noderange, combining any and all members of each comma separated component
|
||||
`n1,n2,rack1,storage,location.rack=9,~s1..,n20-n30`
|
||||
|
||||
Exclusions can be done by prepending a '-' before a portion of a noderange:
|
||||
`rack1,-n2`
|
||||
`compute,-rack1`
|
||||
`compute,-location.row=12`
|
||||
|
||||
To indicate nodes that match multiple selections at once (set intersection), the @ symbol may be used:
|
||||
`compute@rack1`
|
||||
`location.rack=10@compute`
|
||||
|
||||
For complex expressions, () may be used to indicate order of expanding the noderange to be explicit
|
||||
`rack1,-(console.logging=full@compute)`
|
||||
|
||||
Noderange syntax can also indicate 'pagination', or separating the nodes into well defined chunks. > is used to indicate how many nodes to display at a time, and < is used to indicate how many nodes to skip into a noderange:
|
||||
`rack1>3<6`
|
||||
|
||||
The above would show the seventh through ninth nodes of the rack1 group. Like all other noderange operations, this may be combined with any of the above, but must appear as the very last operation. Ordering is done with a natural sort.
|
||||
@@ -1,32 +0,0 @@
|
||||
noderemove(8) -- Remove nodes from the confluent management service
|
||||
===================================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`noderemove <noderange>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`noderemove` simply removes the given noderange from the confluent database.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specify a maximum number of nodes to delete, prompting if over the
|
||||
threshold
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
* Remove two racks each with 4 nodes:
|
||||
`# noderemove r1u1-r2u4`
|
||||
`r1u4: deleted`
|
||||
`r1u1: deleted`
|
||||
`r1u2: deleted`
|
||||
`r1u3: deleted`
|
||||
`r2u4: deleted`
|
||||
`r2u3: deleted`
|
||||
`r2u2: deleted`
|
||||
`r2u1: deleted`
|
||||
@@ -1,26 +0,0 @@
|
||||
nodereseat(8) -- Request a reseat of a node
|
||||
============================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodereseat <noderange>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`nodereseat` requests the enclosure manager of the current node to reseat that
|
||||
node's slot. This should be equivalent to removing the system entirely from
|
||||
the chassis and putting it back in, but without actually having to do so.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specify a maximum number of nodes to reseat, prompting if over the threshold
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
* Reseating the node `s1`:
|
||||
`# nodereseat s1`
|
||||
`s1: Reseat successful`
|
||||
@@ -1,28 +0,0 @@
|
||||
nodersync(8) -- Run rsync in parallel against a noderange
|
||||
=========================================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodersync <file/directorylist> <noderange>:<destination>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
Supervises execution of rsync to push files or a directory tree to the specified
|
||||
noderange. This will present progress as percentage for all nodes.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-f COUNT`, `-c COUNT`, `--count=COUNT`:
|
||||
Specify how many rsync executions to do concurrently. If noderange
|
||||
exceeds the count, then excess nodes will wait until one of the
|
||||
active count completes.
|
||||
|
||||
* `-s`, `--substitutename`:
|
||||
'Use a different name other than the nodename for rsync'
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specify a maximum number of nodes to run rsync to, prompting if over the
|
||||
threshold
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
@@ -3,7 +3,7 @@ noderun(8) -- Run arbitrary commands per node in a noderange
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`noderun [options] <noderange> <command expression>`
|
||||
`noderun <noderange> <command string>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -13,21 +13,6 @@ nodeattribexpressions(5) are expanded prior to execution of the command. For
|
||||
noderun, the commands are locally executed. To execute commands on the nodes
|
||||
themselves, see nodeshell(8).
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-f COUNT`, `-c COUNT`, `--count=COUNT`:
|
||||
Number of commands to run at a time
|
||||
|
||||
* `-n`, `--nonodeprefix`:
|
||||
Do not prefix output with node names
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specify a maximum number of nodes to run the command with, prompting if over
|
||||
the threshold
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
* Run ping against nodes n1 through n4:
|
||||
@@ -63,10 +48,6 @@ themselves, see nodeshell(8).
|
||||
`n4: 01 10 00`
|
||||
`n2: 01 10 00`
|
||||
|
||||
|
||||
* If wanting to use literal {} in the command, they must be escaped by doubling:
|
||||
`# noderun n1-n4 "echo {node} | awk '{{print $1}}'"`
|
||||
|
||||
## SEE ALSO
|
||||
|
||||
nodeshell(8)
|
||||
|
||||
@@ -29,10 +29,6 @@ interval of 1 second is used.
|
||||
Perform the specified number of readings, waiting `-i` indicated interval or 1 second if not
|
||||
otherwise indicated.
|
||||
|
||||
* `-s`, `--skipnumberless`:
|
||||
Return only sensors that provide numeric data (e.g. temperature, fanspeed),
|
||||
ignoring sensors that do not provide numeric data.
|
||||
|
||||
## EXAMPLES
|
||||
* Retrieving all temperature related sensors from one system
|
||||
`# nodesensors n1 temperature`
|
||||
|
||||
@@ -2,8 +2,9 @@ nodesetboot(8) -- Check or set next boot device for noderange
|
||||
====================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodesetboot [options] <noderange> [default|cd|network|setup|hd|usb|floppy]`
|
||||
|
||||
`nodesetboot <noderange>`
|
||||
`nodesetboot [options] <noderang> [default|cd|network|setup|hd]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
@@ -20,8 +21,6 @@ to use `nodesetboot <noderange> setup` and then initiate a reboot from within
|
||||
the operating system with ssh or similar rather than using the remote hardware
|
||||
control.
|
||||
|
||||
Running the command with no target queries the current setting.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-b`, `--bios`:
|
||||
@@ -31,17 +30,7 @@ Running the command with no target queries the current setting.
|
||||
* `-p`, `--persist`:
|
||||
For a system that supports it, mark the boot override to persist rather than
|
||||
be a one time change. Many systems do not support this functionality.
|
||||
|
||||
* `-u`, `--uefi`:
|
||||
This flag does nothing, it is for command compatibility with xCAT's rsetboot
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specify a maximum number of nodes to modify next boot device, prompting if
|
||||
over the threshold
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
* `default`:
|
||||
Request a normal default boot with no particular device override
|
||||
|
||||
@@ -50,14 +39,6 @@ Running the command with no target queries the current setting.
|
||||
remote media mounted as CD/DVD, and detachable hard disks drives such as usb
|
||||
key devices.
|
||||
|
||||
* `floppy`:
|
||||
Request boot from floppy. Generally speaking firmware uses this to mean a USB
|
||||
flash drive or similar (whether virtual or physical).
|
||||
|
||||
* `usb`:
|
||||
Request boot from usb. Generally speaking firmware uses this to mean a USB
|
||||
flash drive or similar (whether virtual or physical).
|
||||
|
||||
* `network`:
|
||||
Request boot to network
|
||||
|
||||
|
||||
@@ -3,39 +3,13 @@ nodeshell(8) -- Execute command on many nodes in a noderange through ssh
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodeshell [options] <noderange> <command to execute on each node>`
|
||||
`nodeshell <noderange> <command to execute on each node>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
Allows execution of a command on many nodes in parallel. Like noderun(8), it
|
||||
accepts and interpolates confluent attribute expressions as documented in
|
||||
nodeattribexpressions(5). `nodeshell` provides stdout as stdout and stderr
|
||||
as stderr, unlike psh which combines all stdout and stderr into stdout.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-c COUNT`, `-f COUNT`, `--count=COUNT`
|
||||
Specify the maximum number of instances to run concurrently
|
||||
|
||||
* `-l LOGINNAME`, `--loginname=LOGINNAME`
|
||||
Username to use when connecting, defaults to current user.
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`
|
||||
Specify a maximum number of nodes to run remote ssh command to, prompting
|
||||
if over the threshold
|
||||
|
||||
* `-n`, `--nonodeprefix`
|
||||
Do not prefix output with node names
|
||||
|
||||
* `-p PORT`, `--port=PORT`
|
||||
Specify a custom port for ssh
|
||||
|
||||
* `-s SUBSTITUTION`, `--substitutename=SUBSTITITUTION`
|
||||
Specify a substitution name instead of the nodename. If no {} are in the substitution,
|
||||
it is considered to be an append. For example, '-s -ib' would produce 'node1-ib' from 'node1'.
|
||||
Full expression syntax is supported, in which case the substitution is considered to be the entire
|
||||
new name. {node}-ib would be equivalent to -ib. For example, nodeshell -s {bmc} node1
|
||||
would ssh to the BMC instead of the node.
|
||||
nodeattribexpressions(5).
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
@@ -47,10 +21,7 @@ as stderr, unlike psh which combines all stdout and stderr into stdout.
|
||||
`n4: hi`
|
||||
|
||||
* Setting a new static ip address temporarily on secondary interface of four nodes:
|
||||
`# nodeshell n1-n4 ifconfig eth1 172.30.93.{n1}`
|
||||
|
||||
* If wanting to use literal {} in the command, they must be escaped by doubling:
|
||||
`# nodeshell n1-n4 "ps | awk '{{print $1}}'"`
|
||||
`# nodeshell n1-n4 ifconfig eth1 172.30.93.{n1}`
|
||||
|
||||
## SEE ALSO
|
||||
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
nodestorage(8) -- Examine/Modify storage configuration of a node
|
||||
============================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodestorage <noderange> [show|create|delete|diskset] [hotspare|jbod|unconfigured] [options]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`nodestorage` provides access to the remote storage configuration of
|
||||
the noderange. The `show` subcommand will show current storage configuration,
|
||||
`create` can be used to create new arrays or volumes, `delete` can be used to
|
||||
remove volumes and arrays, and `diskset` can modify the usage of disks
|
||||
indicated by `-d` to either be `unconfigured`, `jbod`, or `hotspare`.
|
||||
|
||||
## OPTIONS
|
||||
* `-r` **RAIDLEVEL**, `--raidlevel`=**RAIDLEVEL**:
|
||||
RAID level to use when creating an array
|
||||
|
||||
* `-d` **DISKS**, `--disks`=**DISKS**:
|
||||
Comma separated list of disks to use, or the word "rest" to
|
||||
indicate use of all available disks
|
||||
|
||||
* `-s` **SIZE**, `--size`=**SIZE**:
|
||||
Comma separated list of sizes to use when creating
|
||||
volumes. The sizes may be absolute size (e.g. 16gb),
|
||||
percentage (10%) or the word "rest" to use remaining
|
||||
capacity, default behavior is to use all capacity to
|
||||
make a volume
|
||||
* `-n` **NAME**, `--name`=**NAME**:
|
||||
Comma separated list of names to use when naming
|
||||
volumes, or selecting a volume for delete. Default
|
||||
behavior is to use implementation provided default
|
||||
|
||||
* `-z` **STRIPSIZES**, `--stripsizes`=**STRIPSIZES**:
|
||||
Comma separated list of stripsizes to use when creating volumes.
|
||||
This value is in kilobytes. The default behavior is to allow the
|
||||
storage controller to decide
|
||||
|
||||
* `-m` **MAXNODES**, `--maxnodes`=**MAXNODES**:
|
||||
Specify a maximum number of nodes to configure storage on, prompting
|
||||
if over the threshold
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
* Deleting the volume `somedata`:
|
||||
`$ nodestorage d5 delete somedata`
|
||||
`Deleted: somedata`
|
||||
|
||||
* Creating a raid5 of 4 disks and a volume named `somedata`:
|
||||
`$ nodestorage d5 create -r 5 -d drive0,drive_1,drive_2,drive_3 -n somedata`
|
||||
`d5: Volume somedata: Size: 1.905 TB`
|
||||
`d5: Volume somedata: State: Optimal`
|
||||
`d5: Volume somedata: Array 1-2`
|
||||
|
||||
* Showing current storage configuration of `d3`:
|
||||
`$ nodestorage d3`
|
||||
`d3: Disk m.2-0 Description: 128GB M.2 SATA SSD`
|
||||
`d3: Disk m.2-0 State: online`
|
||||
`d3: Disk m.2-0 FRU: 00LF428`
|
||||
`d3: Disk m.2-0 Serial Number: H6B80054`
|
||||
`d3: Disk m.2-0 Array: 0-0`
|
||||
`d3: Disk m.2-1 Description: 128GB M.2 SATA SSD`
|
||||
`d3: Disk m.2-1 State: online`
|
||||
`d3: Disk m.2-1 FRU: 00LF428`
|
||||
`d3: Disk m.2-1 Serial Number: H6B80059`
|
||||
`d3: Disk m.2-1 Array: 0-0`
|
||||
`d3: Array 0-0 Available Capacity: 0.000 MB`
|
||||
`d3: Array 0-0 Total Capacity: 131.072 GB`
|
||||
`d3: Array 0-0 RAID: RAID 1`
|
||||
`d3: Array 0-0 Disks: m.2-0,m.2-1`
|
||||
`d3: Array 0-0 Volumes: new_vd`
|
||||
`d3: Volume new_vd: Size: 122.040 GB`
|
||||
`d3: Volume new_vd: State: Optimal`
|
||||
`d3: Volume new_vd: Array 0-0`
|
||||
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
nodesupport(8) -- Utilities for interacting with vendor support
|
||||
=================================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`nodesupport <noderange> servicedata <directory or filename>`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`nodesupport` provides capabilities associated with interacting with support.
|
||||
Currently it only has the `servicedata` subcommand. `servicedata` takes
|
||||
an argument that is either a directory name (that can be used for a single node
|
||||
or multiple nodes) or a file name (only to be used with single node noderange).
|
||||
Note that the file will be downloaded to the confluent server that actually
|
||||
connects to the managed system, so it will download to the remote system if running
|
||||
remotely and will download to the collective.manager indicated system if
|
||||
running in collective mode.
|
||||
|
||||
Note that due to vendor filename requirements, any filename may have vendor
|
||||
specific suffixes added to any file produced.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specify a maximum number of nodes to download diagnostic data from, prompting
|
||||
if over the threshold
|
||||
|
||||
* `-h`, `--help`:
|
||||
Show help message and exit
|
||||
|
||||
## EXAMPLES
|
||||
|
||||
* Download support data from a single node to a specific filename
|
||||
`# nodesupport d1 servicedata svcdata.out`
|
||||
`d1:initializing: 15%`
|
||||
|
||||
* Download support data from multiple nodes to a directory
|
||||
`# nodesupport d1-d4 servicedata service/`
|
||||
`d1:initializing: 0% d2:initializing: 0% d3:initializing: 0% d4:initializing: 0%`
|
||||
`# ls service/`
|
||||
`d1.svcdata d2.svcdata d3.svcdata d4.svcdata`
|
||||
@@ -1,19 +0,0 @@
|
||||
osdeploy(8) --- Configure general OS deployment facilities of confluent
|
||||
==========================================================================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`osdeploy import <iso>`
|
||||
`osdeploy updateboot <profile>`
|
||||
`osdeploy rebase <profile>`
|
||||
`osdeploy initialize [-h] [-g] [-u] [-s] [-k] [-t] [-p] [-i] [-l] [-a]`
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
**osdeploy** manages the facilities and os deployment content of a confluent server. The
|
||||
`import` subcommand will generate profiles from an iso image. `updateboot` will take any
|
||||
changes in the specified update that need to be pushed into boot configuration and/or images
|
||||
and ensure those needed changes are performed. `initialize` provides assistance in setting
|
||||
up the most commonly required facilities. `rebase` has confluent attempt to update profile
|
||||
content that came from /opt/confluent in an rpm update, if profile supports it.
|
||||
Run `osdeploy initialize -h` for more detail on the options offered by `osdeploy initialize`.
|
||||
@@ -1,49 +0,0 @@
|
||||
stats(8) -- Common basic statistics on typical numeric data in output
|
||||
==============================
|
||||
|
||||
## SYNOPSIS
|
||||
|
||||
`<other command> | stats [-c N] [-d D] [-x|-g|-t|-o image.png] [-s N] [-v] [-b N]
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
The **stats** command helps analyze common numerical data such as performance numbers
|
||||
or temperatures or any other numerical value.
|
||||
|
||||
By default it looks for the last numerical output on the first line to identify the numerical column
|
||||
and analyze that number. This can be overriden by **-c COLUMN** to indicate a column. By default,
|
||||
whitespace and commas are treated to delimit columns, and **-d DELIMITER** can be used to override.
|
||||
|
||||
By default it outputs basic statistics, but a histogram is available either text or through X11 output
|
||||
or sixel or output to an image file depending on whether **-x**, **-g**, **-t*, or **-o image.png** is
|
||||
used.
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-c N`:
|
||||
Select column number. Defaults to last column that appears numeric
|
||||
|
||||
* `-d D`:
|
||||
Specify a custom column delimiter
|
||||
|
||||
* `-x`:
|
||||
Output in Sixel format (supported by mlterm and PuTTY, among others)
|
||||
|
||||
* `-g`:
|
||||
Try to open histogram as an X window
|
||||
|
||||
* `-t`:
|
||||
Output histogram as bars rendered by =
|
||||
|
||||
* `-o image.png`:
|
||||
Write graphical histogram to image.png.
|
||||
|
||||
* `-s N`:
|
||||
Ignore specified number of lines as header content before processing numbers
|
||||
|
||||
* `-v`:
|
||||
Treat value before : on each line as a label, and show which labels belong to which histogram buckets.
|
||||
|
||||
* `-b N`:
|
||||
Specify a custom number of buckets for histogram. The default is 10.
|
||||
|
||||
@@ -1,12 +1,8 @@
|
||||
#!/bin/sh
|
||||
cd `dirname $0`
|
||||
python3 addattribs.py || python2 addattribs.py
|
||||
cd `dirname $0`/doc/man
|
||||
mkdir -p ../../man/man1
|
||||
mkdir -p ../../man/man5
|
||||
mkdir -p ../../man/man8
|
||||
ronn -r *.ronn
|
||||
mv *.1 ../../man/man1/
|
||||
mv *.5 ../../man/man5/
|
||||
mv *.8 ../../man/man8/
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
#!/usr/bin/python2
|
||||
|
||||
# This is a sample python script for going through all observed mac addresses
|
||||
# and assuming they are BMC related and printing nodeattrib commands
|
||||
# for each node to access the bmc using the interface specified on the command
|
||||
# line
|
||||
|
||||
# Not necessarily as useful if there may be mistakes in the
|
||||
# net.switch/net.switchport attributes, but a handy utility in a pinch when
|
||||
# you really know
|
||||
|
||||
|
||||
import confluent.client as cl
|
||||
import socket
|
||||
import struct
|
||||
c = cl.Command()
|
||||
macs = []
|
||||
interface = sys.argv[1]
|
||||
for mac in c.read('/networking/macs/by-mac/'):
|
||||
macs.append(mac['item']['href'])
|
||||
for mac in macs:
|
||||
macinfo = list(c.read('/networking/macs/by-mac/{0}'.format(mac)))[0]
|
||||
if 'possiblenode' in macinfo and macinfo['possiblenode']:
|
||||
if macinfo['macsonport'] > 1:
|
||||
print('#Ambiguous set of macs on port for ' + macinfo[
|
||||
'possiblenode'])
|
||||
prefix = int(mac.replace('-', '')[:6], 16) ^ 0b100000000000000000
|
||||
prefix = prefix << 8
|
||||
prefix |= 0xff
|
||||
suffix = int(mac.replace('-', '')[6:], 16)
|
||||
suffix |= 0xfe000000
|
||||
rawn = struct.pack('!QLL', 0xfe80000000000000, prefix, suffix)
|
||||
bmc = socket.inet_ntop(socket.AF_INET6, rawn)
|
||||
print('nodeattrib {0} bmc={1}%{2}'.format(macinfo['possiblenode'],
|
||||
bmc, interface))
|
||||
@@ -1,12 +1,9 @@
|
||||
from setuptools import setup
|
||||
import os
|
||||
|
||||
data_files = [('/etc/profile.d', ['confluent_env.sh', 'confluent_env.csh']),
|
||||
('/opt/confluent/share/licenses/confluent_client/', ['LICENSE', 'COPYRIGHT'])
|
||||
]
|
||||
data_files = [('/etc/profile.d', ['confluent_env.sh', 'confluent_env.csh'])]
|
||||
try:
|
||||
scriptlist = ['bin/{0}'.format(d) for d in os.listdir('bin/')]
|
||||
data_files.append(('/opt/confluent/share/man/man1', ['man/man1/' + x for x in os.listdir('man/man1')]))
|
||||
data_files.append(('/opt/confluent/share/man/man5', ['man/man5/' + x for x in os.listdir('man/man5')]))
|
||||
data_files.append(('/opt/confluent/share/man/man8', ['man/man8/' + x for x in os.listdir('man/man8')]))
|
||||
except OSError:
|
||||
@@ -15,14 +12,10 @@ except OSError:
|
||||
setup(
|
||||
name='confluent_client',
|
||||
version='#VERSION#',
|
||||
author='Lenovo',
|
||||
author='Jarrod Johnson',
|
||||
author_email='jjohnson2@lenovo.com',
|
||||
url='http://github.com/lenovo/confluent/',
|
||||
license='Apache-2.0',
|
||||
description='Command line client and libraries for confluent management server',
|
||||
long_description='Command line client and libraries for confluent management server',
|
||||
url='http://xcat.sf.net/',
|
||||
packages=['confluent'],
|
||||
platforms=['Linux'],
|
||||
scripts=scriptlist,
|
||||
data_files=data_files,
|
||||
)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user