mirror of
https://github.com/xcat2/confluent.git
synced 2026-09-29 08:41:00 +00:00
Compare commits
402 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ec1efecdae | |||
| c54ac530e1 | |||
| 8db76b92ee | |||
| 2a32fc85a6 | |||
| 2bd13c397d | |||
| 5250a3a67a | |||
| 038faaab74 | |||
| a8f4c437bb | |||
| 9d17102f60 | |||
| f2ce13253f | |||
| 65944a4507 | |||
| 7c8cee2480 | |||
| 31a56f9fdc | |||
| 8990622470 | |||
| 93a35d7e77 | |||
| 131fa052e0 | |||
| f5a1c0d1b4 | |||
| c49b2fd8ab | |||
| 3ce2a5bc26 | |||
| 9dad93fd26 | |||
| f125ff0fb6 | |||
| ec6cdd6c21 | |||
| c53206331c | |||
| 7bbc451047 | |||
| 5ccbc37aa6 | |||
| 69d984b9dc | |||
| dade4239aa | |||
| 6d17d9f0f8 | |||
| 62e9ee8dac | |||
| 326b659d28 | |||
| a123165712 | |||
| 8c9d55d469 | |||
| a1e7cb1e9d | |||
| 7f4da79679 | |||
| fecf766183 | |||
| f842a0a6e3 | |||
| 2462e07832 | |||
| 939d5c59ea | |||
| 36d3cdbe41 | |||
| b91b10552c | |||
| 779b07d2c2 | |||
| d44e7f0955 | |||
| cd68225672 | |||
| f18cc981e2 | |||
| 8c482d9078 | |||
| 78c708424d | |||
| 9b6ead31a7 | |||
| 8e508ee858 | |||
| 7f80a4d5aa | |||
| 315fb0ced8 | |||
| 6dc57abe28 | |||
| 36bf03d65c | |||
| 53c2ace620 | |||
| bec5363877 | |||
| df73c14475 | |||
| d8c7e1fc2a | |||
| 7adef74fe9 | |||
| 40da956a06 | |||
| 07a6eb32ed | |||
| f78b301143 | |||
| 57fe186a10 | |||
| b4c4ac0861 | |||
| 2ab85bb687 | |||
| e1a6a1c9bf | |||
| 9b00fe5521 | |||
| 13a6444541 | |||
| a6e7d016ea | |||
| 52db46be93 | |||
| 2c8cce74ab | |||
| fed83841bb | |||
| 550dfbf6a0 | |||
| 89cc70260f | |||
| e0951b11a6 | |||
| 794502eb6a | |||
| 1a87701fee | |||
| e185f2224f | |||
| a4510ae58d | |||
| a7c5b2478c | |||
| 14035fce88 | |||
| 42bfde3f86 | |||
| 2c75571a84 | |||
| a5e7fe93e4 | |||
| bfb41de43b | |||
| c44cdc21ea | |||
| c806bf2234 | |||
| 61ada4d3d4 | |||
| f10a173e62 | |||
| 27a3a446fe | |||
| 9abe1f98b7 | |||
| 1fd986cc9a | |||
| f97c481c62 | |||
| d50fa2b587 | |||
| a31532d8e4 | |||
| c8fcb716fb | |||
| 289a6a6af8 | |||
| 6379b03051 | |||
| 580dd283cc | |||
| 9d85a3c993 | |||
| 3636e14628 | |||
| 84f3614f0a | |||
| 97f1545f97 | |||
| e6bcf3cf9a | |||
| 61c063adf4 | |||
| 36898ba570 | |||
| 2f2afd970a | |||
| 69beaad3c9 | |||
| 74dda48513 | |||
| f2de24015b | |||
| 9b2eaa90b5 | |||
| 16ff57dcfc | |||
| 7adea90169 | |||
| f28e6d32f3 | |||
| 09089befc8 | |||
| 4173356a70 | |||
| ca0c89aa07 | |||
| ce2487dcb8 | |||
| 5b6bd4fbe1 | |||
| 18d06409d4 | |||
| 08b2e1d008 | |||
| 582842aec8 | |||
| e0f00d80ed | |||
| 63307c331e | |||
| 318608cde3 | |||
| 7efbec7ea2 | |||
| ef7d2414ad | |||
| 21867a60d2 | |||
| 722a0b874a | |||
| f77c1e9333 | |||
| 1deb76989e | |||
| 9e5c69c286 | |||
| 480d399f44 | |||
| 07369667f7 | |||
| 25ea00d6d1 | |||
| e1d4b72f32 | |||
| 137c3a0688 | |||
| 00136f61fe | |||
| f9e898a46a | |||
| 06c2299b10 | |||
| a003ad6e2c | |||
| d5c85cdff9 | |||
| cdc668d717 | |||
| 9ea971d9df | |||
| d2a13f93f3 | |||
| 850793a73f | |||
| 86783a2f12 | |||
| 29ec8e2b54 | |||
| 99063eb049 | |||
| c83ac717fe | |||
| 69e149f9c5 | |||
| b496f2c324 | |||
| e75a1dc7ad | |||
| 5c6fb7f7ef | |||
| 2dcbf76738 | |||
| 04d2a5affc | |||
| 134f339050 | |||
| b4b9a1d1ce | |||
| 0975bd9e62 | |||
| 291363c582 | |||
| 3058dd4141 | |||
| c29494bcf6 | |||
| 50ec0bbca6 | |||
| 52bb240aff | |||
| 667e44983d | |||
| b5771023c3 | |||
| 76efea7c44 | |||
| b0647275df | |||
| c4616745c4 | |||
| 60c3d5400a | |||
| 6bc9282698 | |||
| b06ffb293a | |||
| b548002a8d | |||
| 218ecce63f | |||
| 1c679727ad | |||
| 50e530ebde | |||
| 7984c02042 | |||
| d338f8d586 | |||
| c0d53ba986 | |||
| 68097428a5 | |||
| 7fedbc1810 | |||
| b2f1b8da79 | |||
| 21c9158491 | |||
| 54735e9857 | |||
| 0dabccaec8 | |||
| d89305ca42 | |||
| e6c19388a2 | |||
| 048780e16d | |||
| 61d7a49163 | |||
| f8b8ce3847 | |||
| a0a5887214 | |||
| ccaf22f44f | |||
| 72c4868073 | |||
| afb6356f9d | |||
| 6e6ac67b3d | |||
| 99d10896e8 | |||
| 488f23e3ed | |||
| 6ca62cbb35 | |||
| 45bc9788b4 | |||
| 289c31e7ac | |||
| 1a684f2012 | |||
| a4229fc58d | |||
| 31c1a865dc | |||
| ff84fcf6e9 | |||
| 56dfb6dc6b | |||
| d7577a04a7 | |||
| b72d6c9cfc | |||
| 523c93dfc3 | |||
| 04e983a2d3 | |||
| 2464e0ff4f | |||
| c196bf9d55 | |||
| 12d886a4f6 | |||
| 6a26ece782 | |||
| 3cbac38d57 | |||
| 224f349053 | |||
| 9d361d376d | |||
| ec39de3df0 | |||
| a3b768c70f | |||
| 4f75d4942b | |||
| 4d2f36917c | |||
| a2a50d34d1 | |||
| 041008a524 | |||
| 5923feaa18 | |||
| 73216fc062 | |||
| 100944490c | |||
| 61b07e0af4 | |||
| 53760ab5dd | |||
| d3e7a49f92 | |||
| 1f688ead28 | |||
| d20c5ac6eb | |||
| 4484216198 | |||
| e1efd6a9c5 | |||
| 58d5209595 | |||
| 53c918042a | |||
| 9148a841b5 | |||
| 6ebb6de107 | |||
| 20292cdfd0 | |||
| b07da455c2 | |||
| cc9a81103b | |||
| 21155d2091 | |||
| 6c0d7ea60e | |||
| 174d204607 | |||
| 2826abb7ab | |||
| 5adb5fa780 | |||
| 5de063212f | |||
| 073f6d1389 | |||
| f755ba9f91 | |||
| cf8c01ef13 | |||
| 8b12047ae0 | |||
| f0a779764d | |||
| 0ad7e99efe | |||
| 24a76612ae | |||
| 6c9c58f464 | |||
| 3125f4171b | |||
| d66df7ee4b | |||
| feaa3bb7b4 | |||
| dac383af59 | |||
| f50db78c8c | |||
| 8cb34b20bc | |||
| 75ae623b70 | |||
| 55cdfae437 | |||
| 4edc2a6412 | |||
| b46aecbeed | |||
| 69afc013f2 | |||
| 9c3126e9f7 | |||
| 0b401e8276 | |||
| b609a0039f | |||
| fe0a15faf2 | |||
| 10faac8835 | |||
| 19439463b1 | |||
| 1d861e60bb | |||
| 52f172ef57 | |||
| a0ab71f7bb | |||
| 4ef24351aa | |||
| c9e428bb1b | |||
| 53d0d09ae1 | |||
| 30b8979e2c | |||
| 6f776a657c | |||
| 2f415caead | |||
| 708170b06a | |||
| 5eaf998391 | |||
| c43a667299 | |||
| bf56d40fb5 | |||
| fab6a5a757 | |||
| a076472718 | |||
| d1659cef97 | |||
| c0018840c7 | |||
| 511fdfe6c1 | |||
| 4945c1f473 | |||
| 90b893bc28 | |||
| ac4092ec4b | |||
| 556e40787c | |||
| 45187e0c54 | |||
| 2cc61a1810 | |||
| 21f68bb212 | |||
| 45b17ba855 | |||
| db670b695f | |||
| 0d8173cbcb | |||
| 8b70213c0d | |||
| e0fa642496 | |||
| 42e5a556c1 | |||
| d7d89dd233 | |||
| 536aa7f212 | |||
| 67a61c5012 | |||
| 73cd6d52da | |||
| 5be422958b | |||
| c754dc2641 | |||
| 3741db740f | |||
| e446aa9277 | |||
| 1edfeba076 | |||
| 2c2fe08d66 | |||
| 362f6ae6d5 | |||
| 8fbb495ee9 | |||
| 879fb9c7ab | |||
| 9b8ec1e493 | |||
| 9394e83c81 | |||
| f42812b836 | |||
| b6a0250e5c | |||
| bdb7f064d6 | |||
| 85c8268ad8 | |||
| 00eff4a002 | |||
| cbb52739d3 | |||
| 4ba82b7ef4 | |||
| c5405f832c | |||
| 23d0bbd047 | |||
| 4c3f93765f | |||
| 4a2349d9ad | |||
| 1a9395fc5f | |||
| b4ae6012c5 | |||
| 782991aea3 | |||
| 6e751c811e | |||
| c03aa728cc | |||
| fbdb35e33d | |||
| 207cc3471e | |||
| 5a9f608451 | |||
| 100810788c | |||
| 90b90ade9c | |||
| f6fc539df9 | |||
| 2e30f7fb86 | |||
| e1e3244af6 | |||
| 5fd0cf2b0b | |||
| bd2f08d3ad | |||
| b9a2c9a3ae | |||
| 42b7cbe421 | |||
| 96a43013b5 | |||
| a3506cf0bf | |||
| 25d4d13a96 | |||
| 23658680a5 | |||
| 2089f5e7e6 | |||
| b3e0117944 | |||
| 056a41c985 | |||
| 6704f23218 | |||
| 222bdee851 | |||
| 5e222041bf | |||
| ee6f869cea | |||
| b967c552fd | |||
| 553916340e | |||
| 0be60b1ce2 | |||
| a5dc10debf | |||
| d2edcb62c6 | |||
| afa0c0df5a | |||
| 560ec60c12 | |||
| e890276bf6 | |||
| c24da59216 | |||
| e8110551db | |||
| bfe7529d21 | |||
| c3cafd9bf8 | |||
| fb8ac158cb | |||
| 9d828f0998 | |||
| e8fed28a21 | |||
| 7b2e32009f | |||
| 587ccd13cc | |||
| 198ffb8be6 | |||
| c1d680d8d8 | |||
| 1fbaee6149 | |||
| 81428727d3 | |||
| 668c5af261 | |||
| b1cd7bcd98 | |||
| 4fe9e1e80b | |||
| 508adc8d03 | |||
| 46edd8a49a | |||
| 0570996c36 | |||
| ce3d4d7256 | |||
| 94cb1aebc3 | |||
| da63543a70 | |||
| 887207b9fc | |||
| 142f97c94e | |||
| 4ca82948ba | |||
| 399c1467c1 | |||
| dcb6a1c759 | |||
| 91dc37d45e | |||
| 500a955d79 | |||
| f4f5fcdb6d | |||
| eed2e74bd0 | |||
| 4f92e3413a | |||
| d42e8e0921 | |||
| 635ef6073c | |||
| 496e7b4ef3 | |||
| 3d33e33ea2 | |||
| 0a8ec96cdf | |||
| 25f2698ae6 | |||
| d6bff637db | |||
| 91e0aa938c | |||
| ed54bfa11a |
@@ -0,0 +1,8 @@
|
||||
FROM almalinux:10
|
||||
RUN ["yum", "-y","update"]
|
||||
RUN ["yum", "-y","install","gcc","make","rpm-build","python3-devel","python3-setuptools","createrepo","python3", "perl", "perl-DBI", "perl-JSON", "perl-XML-LibXML", "pinentry-tty", "rpm-sign", "git", "golang"]
|
||||
ADD rpmmacro /root/.rpmmacros
|
||||
ADD buildpackages.sh /bin/
|
||||
#VOLUME ["/rpms", "/srpms"]
|
||||
CMD ["/bin/bash","/bin/buildpackages.sh"]
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
for package in /srpms/*; do
|
||||
rpmbuild --rebuild $package
|
||||
done
|
||||
find ~/rpmbuild/RPMS -type f -exec cp {} /rpms/ \;
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
%_gpg_digest_algo sha256
|
||||
%_gpg_name Lenovo Scalable Infrastructure
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
FROM almalinux:8
|
||||
RUN ["yum", "-y","update"]
|
||||
RUN ["yum", "-y","install","gcc","make","rpm-build","python3-devel","python3-setuptools","createrepo","python3", "perl", "perl-DBI", "perl-JSON", "perl-Net-DNS", "perl-DB_File", "perl-XML-LibXML", "rpm-sign", "git", "fuse-devel","libcurl-devel"]
|
||||
ADD rpmmacro /root/.rpmmacros
|
||||
ADD buildpackages.sh /bin/
|
||||
#VOLUME ["/rpms", "/srpms"]
|
||||
CMD ["/bin/bash","/bin/buildpackages.sh"]
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
for package in /srpms/*; do
|
||||
rpmbuild --rebuild $package
|
||||
done
|
||||
find ~/rpmbuild/RPMS -type f -exec cp {} /rpms/ \;
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
%_gpg_digest_algo sha256
|
||||
%_gpg_name Lenovo Scalable Infrastructure
|
||||
@@ -0,0 +1,10 @@
|
||||
FROM almalinux:9
|
||||
RUN ["yum", "-y","update"]
|
||||
RUN ["yum", "-y","install","gcc","make","rpm-build","python3-devel","python3-setuptools","createrepo","python3", "perl", "perl-DBI", "perl-JSON", "perl-Net-DNS", "perl-DB_File", "perl-XML-LibXML", "pinentry-tty", "rpm-sign", "epel-release", "git"]
|
||||
RUN ["crb", "enable"]
|
||||
RUN ["yum", "-y","install","fuse-devel","libcurl-devel"]
|
||||
ADD rpmmacro /root/.rpmmacros
|
||||
ADD buildpackages.sh /bin/
|
||||
#VOLUME ["/rpms", "/srpms"]
|
||||
CMD ["/bin/bash","/bin/buildpackages.sh"]
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
for package in /srpms/*; do
|
||||
rpmbuild --rebuild $package
|
||||
done
|
||||
find ~/rpmbuild/RPMS -type f -exec cp {} /rpms/ \;
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
%_gpg_digest_algo sha256
|
||||
%_gpg_name Lenovo Scalable Infrastructure
|
||||
@@ -0,0 +1,12 @@
|
||||
FROM ubuntu:noble
|
||||
ADD stdeb.patch /tmp/
|
||||
ADD buildapt.sh /bin/
|
||||
ADD distributions.tmpl /bin/
|
||||
RUN ["apt-get", "update"]
|
||||
RUN ["apt-get", "install", "-y", "reprepro", "python3-stdeb", "gnupg-agent", "devscripts", "debhelper", "libsoap-lite-perl", "libdbi-perl", "quilt", "git", "python3-pyparsing", "python3-dnspython", "python3-eventlet", "python3-netifaces", "python3-paramiko", "dh-python", "libjson-perl", "ronn", "alien", "gcc", "make"]
|
||||
RUN ["mkdir", "-p", "/sources/git/"]
|
||||
RUN ["mkdir", "-p", "/debs/"]
|
||||
RUN ["mkdir", "-p", "/apt/"]
|
||||
RUN ["bash", "-c", "patch -p1 < /tmp/stdeb.patch"]
|
||||
CMD ["/bin/bash", "/bin/buildapt.sh"]
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
#cp -a /sources/git /tmp
|
||||
for builder in $(find /sources/git -name builddeb); do
|
||||
cd $(dirname $builder)
|
||||
./builddeb /debs/
|
||||
done
|
||||
cp /prebuilt/* /debs/
|
||||
cp /osd/*.deb /debs/
|
||||
mkdir -p /apt/conf/
|
||||
CODENAME=$(grep VERSION_CODENAME= /etc/os-release | sed -e 's/.*=//')
|
||||
if [ -z "$CODENAME" ]; then
|
||||
CODENAME=$(grep VERSION= /etc/os-release | sed -e 's/.*(//' -e 's/).*//')
|
||||
fi
|
||||
if ! grep $CODENAME /apt/conf/distributions; then
|
||||
sed -e s/#CODENAME#/$CODENAME/ /bin/distributions.tmpl >> /apt/conf/distributions
|
||||
fi
|
||||
cd /apt/
|
||||
reprepro includedeb $CODENAME /debs/*.deb
|
||||
for dsc in /debs/*.dsc; do
|
||||
reprepro includedsc $CODENAME $dsc
|
||||
done
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
Origin: Lenovo HPC Packages
|
||||
Label: Lenovo HPC Packages
|
||||
Codename: #CODENAME#
|
||||
Architectures: amd64 source
|
||||
Components: main
|
||||
Description: Lenovo HPC Packages
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
diff -urN t/usr/lib/python3/dist-packages/stdeb/cli_runner.py t.patch/usr/lib/python3/dist-packages/stdeb/cli_runner.py
|
||||
--- t/usr/lib/python3/dist-packages/stdeb/cli_runner.py 2024-06-11 18:30:13.930328999 +0000
|
||||
+++ t.patch/usr/lib/python3/dist-packages/stdeb/cli_runner.py 2024-06-11 18:32:05.392731405 +0000
|
||||
@@ -8,7 +8,7 @@
|
||||
from ConfigParser import SafeConfigParser # noqa: F401
|
||||
except ImportError:
|
||||
# python 3.x
|
||||
- from configparser import SafeConfigParser # noqa: F401
|
||||
+ from configparser import ConfigParser # noqa: F401
|
||||
from distutils.util import strtobool
|
||||
from distutils.fancy_getopt import FancyGetopt, translate_longopt
|
||||
from stdeb.util import stdeb_cmdline_opts, stdeb_cmd_bool_opts
|
||||
diff -urN t/usr/lib/python3/dist-packages/stdeb/util.py t.patch/usr/lib/python3/dist-packages/stdeb/util.py
|
||||
--- t/usr/lib/python3/dist-packages/stdeb/util.py 2024-06-11 18:32:53.864776149 +0000
|
||||
+++ t.patch/usr/lib/python3/dist-packages/stdeb/util.py 2024-06-11 18:33:02.063952870 +0000
|
||||
@@ -730,7 +730,7 @@
|
||||
example.
|
||||
"""
|
||||
|
||||
- cfg = ConfigParser.SafeConfigParser()
|
||||
+ cfg = ConfigParser.ConfigParser()
|
||||
cfg.read(cfg_files)
|
||||
if cfg.has_section(module_name):
|
||||
section_items = cfg.items(module_name)
|
||||
@@ -801,7 +801,7 @@
|
||||
if len(cfg_files):
|
||||
check_cfg_files(cfg_files, module_name)
|
||||
|
||||
- cfg = ConfigParser.SafeConfigParser(cfg_defaults)
|
||||
+ cfg = ConfigParser.ConfigParser(cfg_defaults)
|
||||
for cfg_file in cfg_files:
|
||||
with codecs.open(cfg_file, mode='r', encoding='utf-8') as fd:
|
||||
cfg.readfp(fd)
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
cd ~/confluent
|
||||
git pull
|
||||
rm ~/rpmbuild/RPMS/noarch/*osdeploy*
|
||||
rm ~/rpmbuild/SRPMS/*osdeploy*
|
||||
sh confluent_osdeploy/buildrpm-aarch64
|
||||
mkdir -p $HOME/el9/
|
||||
mkdir -p $HOME/el10/
|
||||
podman run --rm -it -v $HOME:/build el9build bash /build/confluent/confluent_vtbufferd/buildrpm /build/el9/
|
||||
|
||||
@@ -131,6 +131,15 @@ def print_help():
|
||||
#common with the api document
|
||||
|
||||
|
||||
def writeout(data):
|
||||
while True:
|
||||
try:
|
||||
select.select((), (sys.stdout,), ())
|
||||
sys.stdout.write(data)
|
||||
break
|
||||
except BlockingIOError:
|
||||
continue
|
||||
|
||||
def updatestatus(stateinfo={}):
|
||||
global powerstate, powertime, clearpowermessage
|
||||
status = consolename
|
||||
@@ -150,10 +159,10 @@ def updatestatus(stateinfo={}):
|
||||
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")
|
||||
writeout("\x1b[2J\x1b[;H[powered off]\r\n")
|
||||
clearpowermessage = True
|
||||
if newpowerstate == 'on' and clearpowermessage:
|
||||
sys.stdout.write("\x1b[2J\x1b[;H")
|
||||
writeout("\x1b[2J\x1b[;H")
|
||||
clearpowermessage = False
|
||||
powerstate = newpowerstate
|
||||
if 'clientcount' in laststate and laststate['clientcount'] != 1:
|
||||
@@ -171,7 +180,7 @@ def updatestatus(stateinfo={}):
|
||||
if info:
|
||||
status += ' [' + ','.join(info) + ']'
|
||||
if os.environ.get('TERM', '') not in ('linux'):
|
||||
sys.stdout.write('\x1b]0;console: %s\x07' % status)
|
||||
writeout('\x1b]0;console: %s\x07' % status)
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
@@ -451,7 +460,7 @@ def do_command(command, server):
|
||||
print_result(res)
|
||||
elif argv[0] == 'start':
|
||||
targpath = fullpath_target(argv[1])
|
||||
nodename = targpath.split('/')[-3]
|
||||
nodename = targpath.split('/')[2]
|
||||
currconsole = targpath
|
||||
startrequest = {'operation': 'start', 'path': targpath,
|
||||
'parameters': {}}
|
||||
@@ -659,9 +668,10 @@ def get_session_node(shellargs):
|
||||
return targ, shellargs[0]
|
||||
if len(shellargs) == 2 and shellargs[0] == 'start':
|
||||
args = [s for s in shellargs[1].split('/') if s]
|
||||
if len(args) == 4 and args[0] == 'nodes' and args[2] == 'console' and \
|
||||
args[3] == 'session':
|
||||
return shellargs[1], args[1]
|
||||
if len(args) == 4 and args[0] == 'nodes':
|
||||
if args[2] == 'console' and \
|
||||
args[3] == 'session':
|
||||
return shellargs[1], args[1]
|
||||
if len(args) == 5 and args[0] == 'nodes' and args[2] == 'shell' and \
|
||||
args[3] == 'sessions':
|
||||
return shellargs[1], args[1]
|
||||
@@ -1045,14 +1055,21 @@ def consume_termdata(fh, bufferonly=False):
|
||||
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)
|
||||
data = data.encode('utf8')
|
||||
written = False
|
||||
while not written:
|
||||
select.select((), (sys.stdout,), ())
|
||||
try:
|
||||
sys.stdout.buffer.write(data)
|
||||
written = True
|
||||
except BlockingIOError:
|
||||
continue
|
||||
except IOError: # Some times circumstances are bad
|
||||
# resort to byte at a time...
|
||||
raise
|
||||
for d in data:
|
||||
sys.stdout.write(d)
|
||||
written = True
|
||||
now = time.time()
|
||||
if ('showtime' not in laststate or
|
||||
(now // 60) != laststate['showtime'] // 60):
|
||||
|
||||
@@ -36,6 +36,36 @@ import confluent.client as client
|
||||
import confluent.sortutil as sortutil
|
||||
devnull = None
|
||||
|
||||
def run_automation(noderange, category, c):
|
||||
automationbynode = {}
|
||||
for res in c.update('/noderange/{0}/deployment/remote_config/run'.format(noderange), {
|
||||
'category': category,
|
||||
}):
|
||||
if 'error' in res:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
exitcode |= res.get('errorcode', 1)
|
||||
if 'created' in res:
|
||||
nodename = res['created'].split('/')[2]
|
||||
automationbynode[nodename] = res['created']
|
||||
while automationbynode:
|
||||
for node in list(automationbynode):
|
||||
for res in c.read(automationbynode[node]):
|
||||
if 'error' in res:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
exitcode |= res.get('errorcode', 1)
|
||||
for result in res.get('results', []):
|
||||
sys.stdout.write('{0}: Task [{1}] {2}\n'.format(
|
||||
node, result['task_name'], result['state']))
|
||||
for warning in result.get('warnings', []):
|
||||
sys.stderr.write('{0}: [WARNING] {1}\n'.format(node, warning))
|
||||
if 'errorinfo' in result:
|
||||
for errorline in result['errorinfo'].splitlines():
|
||||
sys.stderr.write('{0}: [ERROR] {1}\n'.format(node, errorline))
|
||||
if res.get('complete', False):
|
||||
del automationbynode[node]
|
||||
sys.stdout.write('{0}: Automation complete\n'.format(node))
|
||||
|
||||
|
||||
def run():
|
||||
global devnull
|
||||
devnull = open(os.devnull, 'rb')
|
||||
@@ -51,6 +81,8 @@ def run():
|
||||
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('-A', '--automation',
|
||||
help='Run the automation scripts associated with the current OS profile on the noderange, specifying category (onboot.d/firstboot.d/post.d)')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to run remote ssh command to, '
|
||||
@@ -74,16 +106,13 @@ def run():
|
||||
exitcode = 0
|
||||
|
||||
c.stop_if_noderange_over(args[0], options.maxnodes)
|
||||
if options.automation:
|
||||
run_automation(args[0], options.automation, c)
|
||||
|
||||
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:
|
||||
@@ -94,8 +123,17 @@ def run():
|
||||
for script in options.scripts.split(','):
|
||||
cmdstorun.append(['run_remote', script])
|
||||
if not cmdstorun:
|
||||
if options.automation:
|
||||
sys.exit(0)
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
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)
|
||||
idxbynode = {}
|
||||
cmdvbase = ['bash', '/etc/confluent/functions']
|
||||
for sshnode in nodes:
|
||||
@@ -145,7 +183,7 @@ def run():
|
||||
run_cmdv(node, cmdv, all, poller, pipedesc)
|
||||
elif pendingexecs:
|
||||
node, cmdv = pendingexecs.popleft()
|
||||
run_cmdv(node, cmdv, all, poller. pipedesc)
|
||||
run_cmdv(node, cmdv, all, poller, pipedesc)
|
||||
singlepoller.close()
|
||||
for node in sortutil.natural_sort(pernodeout):
|
||||
for line in pernodeout[node]:
|
||||
|
||||
+118
-113
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python2
|
||||
#!/usr/bin/python3
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
__author__ = 'alin37'
|
||||
|
||||
import asyncio
|
||||
from getpass import getpass
|
||||
import optparse
|
||||
import os
|
||||
@@ -34,126 +35,130 @@ 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.asynclient as client
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage='''\n %prog [-b] noderange [list of attributes or 'all'] \
|
||||
\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()
|
||||
async def main():
|
||||
argparser = optparse.OptionParser(
|
||||
usage='''\n %prog [-b] noderange [list of attributes or 'all'] \
|
||||
\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()
|
||||
|
||||
|
||||
#setting minimal output to only output current information
|
||||
showtype = 'current'
|
||||
requestargs=None
|
||||
try:
|
||||
noderange = args[0]
|
||||
nodelist = '/noderange/{0}/nodes/'.format(noderange)
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
|
||||
#Sets attributes
|
||||
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)
|
||||
#setting minimal output to only output current information
|
||||
showtype = 'current'
|
||||
requestargs=None
|
||||
try:
|
||||
# setting user output to what the user inputs
|
||||
if args[1] == 'all':
|
||||
showtype = 'all'
|
||||
requestargs=args[2:]
|
||||
elif args[1] == 'current':
|
||||
showtype = 'current'
|
||||
requestargs=args[2:]
|
||||
else:
|
||||
showtype = 'all'
|
||||
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
|
||||
noderange = args[0]
|
||||
nodelist = '/noderange/{0}/nodes/'.format(noderange)
|
||||
except IndexError:
|
||||
argparser.print_help()
|
||||
sys.exit(1)
|
||||
client.check_globbing(noderange)
|
||||
session = client.Command()
|
||||
exitcode = 0
|
||||
|
||||
elif options.set:
|
||||
arglist = [noderange]
|
||||
showtype='current'
|
||||
argfile = open(options.set, 'r')
|
||||
argset = argfile.readline()
|
||||
while argset:
|
||||
#Sets attributes
|
||||
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
|
||||
await session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
exitcode = await client.updateattrib(session,args,nodetype, noderange, options, argassign)
|
||||
try:
|
||||
argset = argset[:argset.index('#')]
|
||||
except ValueError:
|
||||
# setting user output to what the user inputs
|
||||
if args[1] == 'all':
|
||||
showtype = 'all'
|
||||
requestargs=args[2:]
|
||||
elif args[1] == 'current':
|
||||
showtype = 'current'
|
||||
requestargs=args[2:]
|
||||
else:
|
||||
showtype = 'all'
|
||||
requestargs=args[1:]
|
||||
except:
|
||||
pass
|
||||
argset = argset.strip()
|
||||
if argset:
|
||||
arglist += shlex.split(argset)
|
||||
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()
|
||||
session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
exitcode=client.updateattrib(session,arglist,nodetype, noderange, options, None)
|
||||
if exitcode != 0:
|
||||
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)
|
||||
|
||||
# Lists all attributes
|
||||
if len(args) > 0:
|
||||
# setting output to all so it can search since if we do have something to search, we want to show all outputs even if it is blank.
|
||||
if requestargs is None:
|
||||
showtype = 'current'
|
||||
elif requestargs == []:
|
||||
#showtype already set
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
requestargs.remove('all')
|
||||
requestargs.remove('current')
|
||||
except ValueError:
|
||||
pass
|
||||
exitcode = await client.printattributes(session, requestargs, showtype,nodetype, noderange, options)
|
||||
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)
|
||||
|
||||
# Lists all attributes
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
||||
|
||||
if len(args) > 0:
|
||||
# setting output to all so it can search since if we do have something to search, we want to show all outputs even if it is blank.
|
||||
if requestargs is None:
|
||||
showtype = 'current'
|
||||
elif requestargs == []:
|
||||
#showtype already set
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
requestargs.remove('all')
|
||||
requestargs.remove('current')
|
||||
except ValueError:
|
||||
pass
|
||||
exitcode = client.printattributes(session, requestargs, showtype,nodetype, noderange, options)
|
||||
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)
|
||||
|
||||
@@ -76,6 +76,10 @@ if __name__ == '__main__':
|
||||
|
||||
list_parser = subparsers.add_parser('listbmccacerts', help='List BMC CA certificates')
|
||||
|
||||
sign_bmc_parser = subparsers.add_parser('signbmccert', help='Sign BMC certificate')
|
||||
sign_bmc_parser.add_argument('--days', type=int, help='Number of days the certificate is valid for')
|
||||
sign_bmc_parser.add_argument('--added-names', type=str, help='Additional names to include in the certificate')
|
||||
|
||||
args = parser.parse_args()
|
||||
c = client.Command()
|
||||
if args.command == 'installbmccacert':
|
||||
@@ -84,6 +88,17 @@ if __name__ == '__main__':
|
||||
removebmccacert(args.noderange, args.id, c)
|
||||
elif args.command == 'listbmccacerts':
|
||||
listbmccacerts(args.noderange, c)
|
||||
elif args.command == 'signbmccert':
|
||||
payload = {}
|
||||
if args.days is not None:
|
||||
payload['days'] = args.days
|
||||
else:
|
||||
print("Error: --days is required for signbmccert", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if args.added_names:
|
||||
payload['added_names'] = args.added_names
|
||||
for res in c.update(f'/noderange/{args.noderange}/configuration/management_controller/certificate/sign', payload):
|
||||
print(repr(res))
|
||||
else:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
+111
-106
@@ -15,7 +15,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import signal
|
||||
import optparse
|
||||
@@ -31,7 +31,7 @@ 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.asynclient as client
|
||||
|
||||
class NullOpt(object):
|
||||
blame = None
|
||||
@@ -62,7 +62,7 @@ argparser.add_option('-e', '--extra', dest='extra',
|
||||
'to be extra configuration')
|
||||
argparser.add_option('-x', '--exclude', dest='exclude',
|
||||
action='store_true', default=False,
|
||||
help='Treat positional arguments as items to not '
|
||||
help='Treat named settings as items to not '
|
||||
'examine, compare, or restore default')
|
||||
argparser.add_option('-a', '--advanced', dest='advanced',
|
||||
action='store_true', default=False,
|
||||
@@ -73,7 +73,7 @@ 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')
|
||||
'Currently "uefi" and "bmc" are supported components')
|
||||
argparser.add_option('-m', '--maxnodes', type='int',
|
||||
help='Specify a maximum number of '
|
||||
'nodes to configure, '
|
||||
@@ -222,109 +222,114 @@ def parse_config_line(arguments, single=False):
|
||||
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)
|
||||
async def main():
|
||||
global printsys
|
||||
if options.batch:
|
||||
printsys = []
|
||||
argfile = open(options.batch, 'r')
|
||||
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)
|
||||
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:
|
||||
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
|
||||
parse_config_line(args[1:])
|
||||
session = client.Command()
|
||||
rcode = 0
|
||||
if options.restoredefault:
|
||||
await session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
if options.restoredefault.lower() in (
|
||||
'sys', 'system', 'uefi', 'bios'):
|
||||
async 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'):
|
||||
async for fr in session.update(
|
||||
'/noderange/{0}/configuration/management_controller/clear'.format(noderange),
|
||||
{'clear': True}):
|
||||
rcode |= client.printerror(fr)
|
||||
sys.exit(rcode)
|
||||
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:
|
||||
if options.advanced:
|
||||
rcode |= client.print_attrib_path(
|
||||
'/noderange/{0}/configuration/management_controller/extended/extra_advanced'.format(noderange),
|
||||
session, printextbmc, options)
|
||||
sys.stderr.write(
|
||||
'Unrecognized component to restore defaults: {0}\n'.format(
|
||||
options.restoredefault))
|
||||
sys.exit(1)
|
||||
if setmode:
|
||||
await 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:
|
||||
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)
|
||||
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:
|
||||
async 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 |= await 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 |= await client.print_attrib_path(
|
||||
'/noderange/{0}/configuration/management_controller/extended/all'.format(noderange),
|
||||
session, printbmc, options, attrprefix='bmc.')
|
||||
if options.extra:
|
||||
if options.advanced:
|
||||
rcode |= await client.print_attrib_path(
|
||||
'/noderange/{0}/configuration/management_controller/extended/extra_advanced'.format(noderange),
|
||||
session, printextbmc, options)
|
||||
else:
|
||||
rcode |= await 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 = await client.print_attrib_path(path, session, printsys,
|
||||
options)
|
||||
sys.exit(rcode)
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
||||
|
||||
@@ -134,7 +134,9 @@ def determine_tile_size(numnodes):
|
||||
# from kitty by omitting, but:
|
||||
# then we don't know how much to move the cursor left after draw_image
|
||||
# Konsole won't scale at all with only partial scaling specified
|
||||
cheight, cwidth, pixwidth, pixheight = sq.get_screengeom()
|
||||
direct_console()
|
||||
cheight, cwidth, pixwidth, pixheight = sq.get_screengeom(escfallback=True)
|
||||
indirect_console()
|
||||
# 16:12 is to roughly account for the 'titles' of the tiles
|
||||
ratio = (pixwidth / 16) / (pixheight / 12)
|
||||
bestdeviation = None
|
||||
@@ -214,7 +216,9 @@ def cursor_show():
|
||||
sys.stdout.write('\x1b[?25h')
|
||||
|
||||
def get_pix_dimensions(width, height):
|
||||
cheight, cwidth, pixwidth, pixheight = sq.get_screengeom()
|
||||
direct_console()
|
||||
cheight, cwidth, pixwidth, pixheight = sq.get_screengeom(escfallback=True)
|
||||
indirect_console()
|
||||
imgwidth = int(pixwidth / cwidth * width)
|
||||
imgheight = int(pixheight / cheight * height)
|
||||
return imgwidth, imgheight
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python2
|
||||
#!/usr/bin/python3
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
@@ -15,6 +15,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import asyncio
|
||||
import optparse
|
||||
import os
|
||||
import signal
|
||||
@@ -30,29 +31,33 @@ 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.asynclient 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)
|
||||
async def main():
|
||||
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
|
||||
async 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)
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python2
|
||||
#!/usr/bin/python3
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2017 Lenovo
|
||||
@@ -15,6 +15,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import asyncio
|
||||
import csv
|
||||
import optparse
|
||||
import os
|
||||
@@ -26,7 +27,7 @@ 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.asynclient as client
|
||||
import confluent.sortutil as sortutil
|
||||
|
||||
defcolumns = ['Node', 'Model', 'Serial', 'UUID', 'Mac Address', 'Type',
|
||||
@@ -81,16 +82,15 @@ def subscribe_discovery(options, session, subscribe, targ):
|
||||
if 'status' in rsp:
|
||||
print(rsp['status'])
|
||||
|
||||
def print_disco(options, session, currmac, outhandler, columns):
|
||||
async def print_disco(options, session, currmac, outhandler, columns):
|
||||
procinfo = {}
|
||||
for tmpinfo in session.read('/discovery/by-mac/{0}'.format(currmac)):
|
||||
|
||||
async 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(
|
||||
async 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
|
||||
@@ -160,12 +160,12 @@ def datum_complete(datum):
|
||||
searchkeys = set(['mac', 'serial', 'uuid'])
|
||||
|
||||
|
||||
def search_record(datum, options, session):
|
||||
async 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))
|
||||
return [x async for x in list_matching_macs(options, session)]
|
||||
|
||||
|
||||
def datum_to_attrib(datum):
|
||||
@@ -180,7 +180,7 @@ def datum_to_attrib(datum):
|
||||
|
||||
unique_fields = frozenset(['serial', 'mac', 'uuid'])
|
||||
|
||||
def import_csv(options, session):
|
||||
async def import_csv(options, session):
|
||||
nodedata = []
|
||||
unique_data = {}
|
||||
exitcode = 0
|
||||
@@ -213,7 +213,7 @@ def import_csv(options, session):
|
||||
for nodedatum in alldata:
|
||||
if not search_record(nodedatum, options, session) and not broken:
|
||||
allthere = False
|
||||
blocking_scan(session)
|
||||
await blocking_scan(session)
|
||||
break
|
||||
for nodedatum in alldata:
|
||||
if not allthere and not search_record(nodedatum, options, session):
|
||||
@@ -262,7 +262,7 @@ def import_csv(options, session):
|
||||
sys.exit(exitcode)
|
||||
|
||||
|
||||
def list_discovery(options, session):
|
||||
async def list_discovery(options, session):
|
||||
orderby = None
|
||||
if options.fields:
|
||||
columns = []
|
||||
@@ -278,23 +278,24 @@ def list_discovery(options, session):
|
||||
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)
|
||||
for mac in [x async for x in list_matching_macs(options, session)]:
|
||||
await 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)):
|
||||
async def clear_discovery(options, session):
|
||||
allmacs = [x async for x in list_matching_macs(options, session)]
|
||||
for mac in allmacs:
|
||||
async 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):
|
||||
async def list_matching_macs(options, session, node=None, checknode=True):
|
||||
path = '/discovery/'
|
||||
if node:
|
||||
path += 'by-node/{0}/'.format(node)
|
||||
@@ -314,22 +315,21 @@ def list_matching_macs(options, session, node=None, checknode=True):
|
||||
path += 'by-state/{0}/'.format(options.state).lower()
|
||||
if options.mac:
|
||||
path += 'by-mac/{0}'.format(options.mac)
|
||||
result = list(session.read(path))[0]
|
||||
result = list([x async for x in session.read(path)])[0]
|
||||
if 'error' in result:
|
||||
return []
|
||||
return [options.mac.replace(':', '-')]
|
||||
return
|
||||
yield options.mac.replace(':', '-')
|
||||
return
|
||||
else:
|
||||
path += 'by-mac/'
|
||||
ret = []
|
||||
for x in session.read(path):
|
||||
async for x in session.read(path):
|
||||
if 'item' in x and 'href' in x['item']:
|
||||
ret.append(x['item']['href'])
|
||||
return ret
|
||||
yield x['item']['href']
|
||||
|
||||
def assign_discovery(options, session, needid=True):
|
||||
async def assign_discovery(options, session, needid=True):
|
||||
abort = False
|
||||
if options.importfile:
|
||||
return import_csv(options, session)
|
||||
return await import_csv(options, session)
|
||||
if not options.node:
|
||||
sys.stderr.write("Node (-n) must be specified for assignment\n")
|
||||
abort = True
|
||||
@@ -340,16 +340,16 @@ def assign_discovery(options, session, needid=True):
|
||||
abort = True
|
||||
if abort:
|
||||
sys.exit(1)
|
||||
matches = list_matching_macs(options, session, None if needid else options.node, False)
|
||||
matches = [x async for x in 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)
|
||||
await blocking_scan(session)
|
||||
matches = [x async for x in 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]),
|
||||
async 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']))
|
||||
@@ -361,14 +361,14 @@ def assign_discovery(options, session, needid=True):
|
||||
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)):
|
||||
async def blocking_scan(session):
|
||||
list([x async for x in session.update('/discovery/rescan', {'rescan': 'start'})])
|
||||
while(list([x async for x in session.read('/discovery/rescan')])[0].get('scanning', False)):
|
||||
time.sleep(0.5)
|
||||
list(session.update('/networking/macs/rescan', {'rescan': 'start'}))
|
||||
list([x async for x in session.update('/networking/macs/rescan', {'rescan': 'start'})])
|
||||
|
||||
|
||||
def main():
|
||||
async def main():
|
||||
parser = optparse.OptionParser(
|
||||
usage='Usage: %prog [list|assign|rescan|clear|subscribe|unsubscribe|register] [options]')
|
||||
# -a for 'address' maybe?
|
||||
@@ -419,13 +419,13 @@ def main():
|
||||
sys.exit(1)
|
||||
session = client.Command()
|
||||
if args[0] == 'list':
|
||||
list_discovery(options, session)
|
||||
await list_discovery(options, session)
|
||||
if args[0] == 'clear':
|
||||
clear_discovery(options, session)
|
||||
await clear_discovery(options, session)
|
||||
if args[0] == 'assign':
|
||||
assign_discovery(options, session)
|
||||
await assign_discovery(options, session)
|
||||
if args[0] == 'reassign':
|
||||
assign_discovery(options, session, False)
|
||||
await assign_discovery(options, session, False)
|
||||
if args[0] == 'register':
|
||||
register_endpoint(options, session, args[1])
|
||||
if args[0] == 'subscribe':
|
||||
@@ -433,9 +433,9 @@ def main():
|
||||
if args[0] == 'unsubscribe':
|
||||
subscribe_discovery(options, session, False, args[1])
|
||||
if args[0] == 'rescan':
|
||||
blocking_scan(session)
|
||||
await blocking_scan(session)
|
||||
print("Rescan complete")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
asyncio.run(main())
|
||||
|
||||
@@ -84,13 +84,13 @@ def main():
|
||||
healthexplanations[node] = []
|
||||
for sensor in health[node]['sensors']:
|
||||
explanation = sensor['name'] + ':'
|
||||
if sensor['value'] is not None:
|
||||
if sensor.get('value', None) is not None:
|
||||
explanation += str(sensor['value'])
|
||||
if sensor['units'] is not None:
|
||||
if sensor.get('units', None) is not None:
|
||||
explanation += sensor['units']
|
||||
if sensor['states']:
|
||||
if sensor.get('states', None):
|
||||
explanation += ','
|
||||
if sensor['states']:
|
||||
if sensor.get('states', None):
|
||||
explanation += ','.join(sensor['states'])
|
||||
healthexplanations[node].append(explanation)
|
||||
if node in healthbynode and node in healthexplanations:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/libexec/platform-python
|
||||
#!/usr/bin/python3
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015-2017 Lenovo
|
||||
@@ -21,6 +21,7 @@ import optparse
|
||||
import os
|
||||
import signal
|
||||
import sys
|
||||
import asyncio
|
||||
|
||||
|
||||
|
||||
@@ -33,9 +34,9 @@ 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.asynclient as client
|
||||
|
||||
def main():
|
||||
async def main():
|
||||
argparser = optparse.OptionParser(
|
||||
usage="Usage: %prog noderange\n"
|
||||
" or: %prog [options] noderange <nodeattribute>...")
|
||||
@@ -59,9 +60,9 @@ def main():
|
||||
requestargs=args[1:]
|
||||
nodetype='noderange'
|
||||
if len(args) > 1:
|
||||
exitcode=client.printattributes(session,requestargs,showtype,nodetype,noderange,options)
|
||||
exitcode=await client.printattributes(session,requestargs,showtype,nodetype,noderange,options)
|
||||
else:
|
||||
for res in session.read(nodelist):
|
||||
async for res in session.read(nodelist):
|
||||
if 'error' in res:
|
||||
sys.stderr.write(res['error'] + '\n')
|
||||
exitcode = 1
|
||||
@@ -73,4 +74,4 @@ def main():
|
||||
sys.exit(exitcode)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
asyncio.run(main())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python2
|
||||
#!/usr/bin/python3
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2015-2017 Lenovo
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import asyncio
|
||||
import optparse
|
||||
import os
|
||||
import signal
|
||||
@@ -32,26 +33,32 @@ if path.startswith('/opt'):
|
||||
|
||||
import confluent.client as client
|
||||
|
||||
argparser = optparse.OptionParser(
|
||||
usage='''\n %prog <noderange>
|
||||
|
||||
async def main():
|
||||
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)
|
||||
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
|
||||
await session.stop_if_noderange_over(noderange, options.maxnodes)
|
||||
async 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)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.get_event_loop().run_until_complete(main())
|
||||
|
||||
@@ -123,7 +123,7 @@ def sensorpass(showout=True, appendtime=False):
|
||||
if 'sensors' not in reading[node]:
|
||||
continue
|
||||
for sensedata in reading[node]['sensors']:
|
||||
if sensedata['value'] is None and options.skipnumberless:
|
||||
if sensedata.get('value', None) is None and options.skipnumberless:
|
||||
continue
|
||||
for redundant_state in ('Non-Critical', 'Critical'):
|
||||
try:
|
||||
@@ -134,17 +134,17 @@ def sensorpass(showout=True, appendtime=False):
|
||||
resultdata[node][sensedata['name']] = sensedata
|
||||
sensorname = sensedata['name']
|
||||
sensorheaders[sensorname] = sensorname
|
||||
if sensedata['units'] not in (None, u''):
|
||||
if sensedata.get('units', None) not in (None, u''):
|
||||
sensorheaders[sensorname] += u' ({0})'.format(
|
||||
sensedata['units'])
|
||||
if showout:
|
||||
if sensedata['value'] is None:
|
||||
if sensedata.get('value', None) 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 = u' {0} '.format(sensedata.get('value', ''))
|
||||
if sensedata.get('units', None) not in (None, u''):
|
||||
showval += sensedata['units']
|
||||
if sensedata.get('health', 'ok') != 'ok':
|
||||
datadescription = [sensedata['health']]
|
||||
|
||||
@@ -0,0 +1,827 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2014 IBM Corporation
|
||||
# Copyright 2015-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 asyncio
|
||||
import ctypes
|
||||
import ctypes.util
|
||||
import dbm
|
||||
import csv
|
||||
import errno
|
||||
import fnmatch
|
||||
import hashlib
|
||||
import os
|
||||
import shlex
|
||||
import socket
|
||||
import ssl
|
||||
import sys
|
||||
import confluent.asynctlvdata as tlvdata
|
||||
import confluent.sortutil as sortutil
|
||||
libssl = ctypes.CDLL(ctypes.util.find_library('ssl'))
|
||||
libssl.SSL_CTX_set_cert_verify_callback.argtypes = [
|
||||
ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
|
||||
|
||||
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 PyObject_HEAD(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("ob_refcnt", ctypes.c_ssize_t),
|
||||
("ob_type", ctypes.c_void_p),
|
||||
]
|
||||
|
||||
|
||||
# see main/Modules/_ssl.c, only caring about the SSL_CTX pointer
|
||||
class PySSLContext(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("ob_base", PyObject_HEAD),
|
||||
("ctx", ctypes.c_void_p),
|
||||
]
|
||||
|
||||
|
||||
@ctypes.CFUNCTYPE(ctypes.c_int, ctypes.c_void_p, ctypes.c_void_p)
|
||||
def verify_stub(store, misc):
|
||||
return 1
|
||||
|
||||
|
||||
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', errors='replace')
|
||||
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:
|
||||
server, port = string[1:].split(']:')
|
||||
elif string[0] == '[':
|
||||
server = string[1:-1]
|
||||
port = '13001'
|
||||
elif ':' in string:
|
||||
server, port = string.split(':')
|
||||
else:
|
||||
server = string
|
||||
port = '13001'
|
||||
return server, port
|
||||
|
||||
|
||||
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']
|
||||
else:
|
||||
self.serverloc = '/var/run/confluent/api.sock'
|
||||
else:
|
||||
self.serverloc = server
|
||||
self.connected = False
|
||||
|
||||
async def ensure_connected(self):
|
||||
if self.connected:
|
||||
return True
|
||||
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:
|
||||
await self._connect_tls()
|
||||
self.protversion = int((await tlvdata.recv(self.connection)).split(
|
||||
b'--')[1].strip()[1:])
|
||||
authdata = await tlvdata.recv(self.connection)
|
||||
if authdata['authpassed'] == 1:
|
||||
self.authenticated = True
|
||||
else:
|
||||
self.authenticated = False
|
||||
if not self.authenticated and 'CONFLUENT_USER' in os.environ:
|
||||
username = os.environ['CONFLUENT_USER']
|
||||
passphrase = os.environ['CONFLUENT_PASSPHRASE']
|
||||
await self.authenticate(username, passphrase)
|
||||
self.connected = True
|
||||
|
||||
async def add_file(self, name, handle, mode):
|
||||
await self.ensure_connected()
|
||||
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)
|
||||
|
||||
async def authenticate(self, username, password):
|
||||
await tlvdata.send(self.connection,
|
||||
{'username': username, 'password': password})
|
||||
authdata = await tlvdata.recv(self.connection)
|
||||
if authdata['authpassed'] == 1:
|
||||
self.authenticated = True
|
||||
|
||||
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):
|
||||
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']
|
||||
else:
|
||||
return 1
|
||||
if 'databynode' not in res:
|
||||
return 0
|
||||
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]:
|
||||
rc |= res[node]['errorcode']
|
||||
else:
|
||||
rc |= 1
|
||||
elif ikey in res[node]:
|
||||
if 'value' in res[node][ikey]:
|
||||
val = res[node][ikey]['value']
|
||||
elif 'isset' in res[node][ikey]:
|
||||
val = '********' if res[node][ikey] else ''
|
||||
else:
|
||||
val = repr(res[node][ikey])
|
||||
if self._prevkeyname and self._prevkeyname in res[node]:
|
||||
cprint('{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)
|
||||
return rc
|
||||
|
||||
async def simple_noderange_command(self, noderange, resource, input=None,
|
||||
key=None, errnodes=None, promptover=None, outhandler=None, **kwargs):
|
||||
try:
|
||||
self._currnoderange = noderange
|
||||
rc = 0
|
||||
if resource[0] == '/':
|
||||
resource = resource[1:]
|
||||
# The implicit key is the resource basename
|
||||
if key is None:
|
||||
ikey = resource.rpartition('/')[-1]
|
||||
else:
|
||||
ikey = key
|
||||
if input is None:
|
||||
async for res in self.read('/noderange/{0}/{1}'.format(
|
||||
noderange, resource)):
|
||||
rc = self.handle_results(ikey, rc, res, errnodes, outhandler)
|
||||
else:
|
||||
await self.stop_if_noderange_over(noderange, promptover)
|
||||
kwargs[ikey] = input
|
||||
async for res in self.update('/noderange/{0}/{1}'.format(
|
||||
noderange, resource), kwargs):
|
||||
rc = self.handle_results(ikey, rc, res, errnodes, outhandler)
|
||||
self._currnoderange = None
|
||||
return rc
|
||||
except KeyboardInterrupt:
|
||||
cprint('')
|
||||
return 0
|
||||
|
||||
async def stop_if_noderange_over(self, noderange, maxnodes):
|
||||
if maxnodes is None:
|
||||
return
|
||||
nsize = await self.get_noderange_size(noderange)
|
||||
if nsize > maxnodes:
|
||||
if nsize == 1:
|
||||
nodename = [x async for x in 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")
|
||||
|
||||
|
||||
async def get_noderange_size(self, noderange):
|
||||
numnodes = 0
|
||||
async 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
|
||||
|
||||
async def simple_nodegroups_command(self, noderange, resource, input=None, key=None, **kwargs):
|
||||
try:
|
||||
rc = 0
|
||||
if resource[0] == '/':
|
||||
resource = resource[1:]
|
||||
# The implicit key is the resource basename
|
||||
if key is None:
|
||||
ikey = resource.rpartition('/')[-1]
|
||||
else:
|
||||
ikey = key
|
||||
if input is None:
|
||||
for res in await self.read('/nodegroups/{0}/{1}'.format(
|
||||
noderange, resource)):
|
||||
rc = self.handle_results(ikey, rc, res)
|
||||
else:
|
||||
kwargs[ikey] = input
|
||||
for res in await self.update('/nodegroups/{0}/{1}'.format(
|
||||
noderange, resource), kwargs):
|
||||
rc = self.handle_results(ikey, rc, res)
|
||||
return rc
|
||||
except KeyboardInterrupt:
|
||||
cprint('')
|
||||
return 0
|
||||
|
||||
async def read(self, path, parameters=None):
|
||||
await self.ensure_connected()
|
||||
if not self.authenticated:
|
||||
raise Exception('Unauthenticated')
|
||||
async for rsp in send_request(
|
||||
'retrieve', path, self.connection, parameters):
|
||||
yield rsp
|
||||
|
||||
async def update(self, path, parameters=None):
|
||||
await self.ensure_connected()
|
||||
if not self.authenticated:
|
||||
raise Exception('Unauthenticated')
|
||||
async for rsp in send_request(
|
||||
'update', path, self.connection, parameters):
|
||||
yield rsp
|
||||
|
||||
async def create(self, path, parameters=None):
|
||||
await self.ensure_connected()
|
||||
if not self.authenticated:
|
||||
raise Exception('Unauthenticated')
|
||||
async for rsp in send_request(
|
||||
'create', path, self.connection, parameters):
|
||||
yield rsp
|
||||
|
||||
async def delete(self, path, parameters=None):
|
||||
await self.ensure_connected()
|
||||
if not self.authenticated:
|
||||
raise Exception('Unauthenticated')
|
||||
async for rsp in send_request(
|
||||
'delete', path, self.connection, parameters):
|
||||
yield rsp
|
||||
|
||||
def _connect_unix(self):
|
||||
self.connection = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
self.connection.setsockopt(socket.SOL_SOCKET, SO_PASSCRED, 1)
|
||||
self.connection.connect(self.serverloc)
|
||||
|
||||
async def _connect_tls(self):
|
||||
server, port = _parseserver(self.serverloc)
|
||||
for res in socket.getaddrinfo(server, port, socket.AF_UNSPEC,
|
||||
socket.SOCK_STREAM):
|
||||
af, socktype, proto, canonname, sa = res
|
||||
try:
|
||||
self.connection = socket.socket(af, socktype, proto)
|
||||
self.connection.setsockopt(
|
||||
socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
|
||||
except:
|
||||
self.connection = None
|
||||
continue
|
||||
try:
|
||||
self.connection.settimeout(5)
|
||||
self.connection.connect(sa)
|
||||
self.connection.settimeout(0)
|
||||
except:
|
||||
raise
|
||||
self.connection.close()
|
||||
self.connection = None
|
||||
continue
|
||||
break
|
||||
if self.connection is None:
|
||||
raise Exception("Failed to connect to %s" % self.serverloc)
|
||||
#TODO(jbjohnso): server certificate validation
|
||||
clientcfgdir = os.path.join(os.path.expanduser("~"), ".confluent")
|
||||
try:
|
||||
os.makedirs(clientcfgdir)
|
||||
except OSError as exc:
|
||||
if not (exc.errno == errno.EEXIST and os.path.isdir(clientcfgdir)):
|
||||
raise
|
||||
cacert = os.path.join(clientcfgdir, "ca.pem")
|
||||
certreqs = ssl.CERT_REQUIRED
|
||||
knownhosts = False
|
||||
if not os.path.exists(cacert):
|
||||
cacert = None
|
||||
certreqs = ssl.CERT_NONE
|
||||
knownhosts = True
|
||||
ctx = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
|
||||
ssl_ctx = PySSLContext.from_address(id(ctx)).ctx
|
||||
libssl.SSL_CTX_set_cert_verify_callback(ssl_ctx, verify_stub, 0)
|
||||
sreader = asyncio.StreamReader()
|
||||
sreaderprot = asyncio.StreamReaderProtocol(sreader)
|
||||
cloop = asyncio.get_event_loop()
|
||||
tport, _ = await cloop.create_connection(
|
||||
lambda: sreaderprot, sock=self.connection, ssl=ctx, server_hostname='x')
|
||||
swriter = asyncio.StreamWriter(tport, sreaderprot, sreader, cloop)
|
||||
self.connection = (sreader, swriter)
|
||||
#self.connection = ssl.wrap_socket(self.connection, ca_certs=cacert,
|
||||
# cert_reqs=certreqs)
|
||||
if knownhosts:
|
||||
certdata = tport.get_extra_info('ssl_object').getpeercert(binary_form=True)
|
||||
# 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(
|
||||
"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))
|
||||
khf[hostid] = fingerprint
|
||||
|
||||
|
||||
async def send_request(operation, path, server, parameters=None):
|
||||
"""This function iterates over all the responses
|
||||
received from the server.
|
||||
|
||||
:param operation: The operation to request, retrieve, update, delete,
|
||||
create, start, stop
|
||||
:param path: The URI path to the resource to operate on
|
||||
:param server: The socket to send data over
|
||||
:param parameters: Parameters if any to send along with the request
|
||||
"""
|
||||
payload = {'operation': operation, 'path': path}
|
||||
if parameters is not None:
|
||||
payload['parameters'] = parameters
|
||||
await tlvdata.send(server, payload)
|
||||
result = await tlvdata.recv(server)
|
||||
while '_requestdone' not in result:
|
||||
try:
|
||||
yield result
|
||||
except GeneratorExit:
|
||||
while '_requestdone' not in result:
|
||||
result = await tlvdata.recv(server)
|
||||
raise
|
||||
result = await tlvdata.recv(server)
|
||||
|
||||
|
||||
def attrrequested(attr, attrlist, seenattributes, node=None):
|
||||
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
|
||||
return True
|
||||
elif attr.lower().startswith(candidate.lower() + '.'):
|
||||
if node is None:
|
||||
seenattributes.add(truename)
|
||||
else:
|
||||
seenattributes[node][truename] = 1
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
async def printattributes(session, requestargs, showtype, nodetype, noderange, options):
|
||||
path = '/{0}/{1}/attributes/{2}'.format(nodetype, noderange, showtype)
|
||||
return await 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 sortutil.naturalize_string('{}'.format(k[1]['sortid']))
|
||||
return sortutil.naturalize_string(k[0])
|
||||
|
||||
async def print_attrib_path(path, session, requestargs, options, rename=None, attrprefix=None):
|
||||
exitcode = 0
|
||||
seenattributes = NestedDict()
|
||||
allnodes = set([])
|
||||
async for res in session.read(path):
|
||||
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
|
||||
currattr = res['databynode'][node][attr]
|
||||
if show_attr(attr, requestargs, seenattributes, options, node):
|
||||
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()
|
||||
else:
|
||||
attrout = '{0}: {1}:'.format(node, printattr)
|
||||
elif 'isset' in currattr:
|
||||
if currattr['isset']:
|
||||
attrout = '{0}: {1}: ********'.format(node,
|
||||
printattr)
|
||||
else:
|
||||
attrout = '{0}: {1}:'.format(node, printattr)
|
||||
elif isinstance(currattr, dict) and 'broken' in currattr:
|
||||
attrout = '{0}: {1}: *ERROR* BROKEN EXPRESSION: ' \
|
||||
'{2}'.format(node, printattr,
|
||||
currattr['broken'])
|
||||
elif isinstance(currattr, list) or isinstance(currattr, tuple):
|
||||
attrout = '{0}: {1}: {2}'.format(node, attr, ','.join(map(str, currattr)))
|
||||
elif isinstance(currattr, dict):
|
||||
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)))
|
||||
else:
|
||||
cprint("CODE ERROR" + repr(attr))
|
||||
try:
|
||||
blame = options.blame
|
||||
except AttributeError:
|
||||
blame = False
|
||||
if blame or (isinstance(currattr, dict) and 'broken' in currattr):
|
||||
blamedata = []
|
||||
if 'inheritedfrom' in currattr:
|
||||
blamedata.append('inherited from group {0}'.format(
|
||||
currattr['inheritedfrom']
|
||||
))
|
||||
if 'expression' in currattr:
|
||||
blamedata.append(
|
||||
'derived from expression "{0}"'.format(
|
||||
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()
|
||||
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(missing))
|
||||
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([])
|
||||
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 attr in res:
|
||||
seenattributes.add(attr)
|
||||
currattr = res[attr]
|
||||
if (requestargs is None or requestargs == [] or attrrequested(attr, requestargs, seenattributes)):
|
||||
if 'value' in currattr:
|
||||
if currattr['value'] is not None:
|
||||
attrout = '{0}: {1}: {2}'.format(
|
||||
noderange, attr, currattr['value'])
|
||||
else:
|
||||
attrout = '{0}: {1}:'.format(noderange, attr)
|
||||
elif 'isset' in currattr:
|
||||
if currattr['isset']:
|
||||
attrout = '{0}: {1}: ********'.format(noderange, attr)
|
||||
else:
|
||||
attrout = '{0}: {1}:'.format(noderange, attr)
|
||||
elif isinstance(currattr, dict) and 'broken' in currattr:
|
||||
attrout = '{0}: {1}: *ERROR* BROKEN EXPRESSION: ' \
|
||||
'{2}'.format(noderange, attr,
|
||||
currattr['broken'])
|
||||
elif 'expression' in currattr:
|
||||
attrout = '{0}: {1}: (will derive from expression {2})'.format(noderange, attr, currattr['expression'])
|
||||
elif isinstance(currattr, list) or isinstance(currattr, tuple):
|
||||
attrout = '{0}: {1}: {2}'.format(noderange, attr, ','.join(map(str, currattr)))
|
||||
elif isinstance(currattr, dict):
|
||||
dictout = []
|
||||
for k, v in currattr.items:
|
||||
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)
|
||||
if not exitcode:
|
||||
if requestargs:
|
||||
for attr in requestargs:
|
||||
if attr not in seenattributes:
|
||||
sys.stderr.write('Error: {0} not a valid attribute\n'.format(attr))
|
||||
exitcode = 1
|
||||
return exitcode
|
||||
|
||||
async def updateattrib(session, updateargs, nodetype, noderange, options, dictassign=None):
|
||||
# update attribute
|
||||
exitcode = 0
|
||||
if options.clear:
|
||||
targpath = '/{0}/{1}/attributes/all'.format(nodetype, noderange)
|
||||
keydata = {}
|
||||
for attrib in updateargs[1:]:
|
||||
keydata[attrib] = None
|
||||
async for res in session.update(targpath, keydata):
|
||||
for node in res.get('databynode', {}):
|
||||
for warnmsg in res['databynode'][node].get('_warnings', []):
|
||||
sys.stderr.write('Warning: ' + warnmsg + '\n')
|
||||
if 'error' in res:
|
||||
if 'errorcode' in res:
|
||||
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 = await session.simple_nodegroups_command(noderange,
|
||||
'attributes/all',
|
||||
value, key)
|
||||
else:
|
||||
exitcode = await session.simple_noderange_command(noderange,
|
||||
'attributes/all',
|
||||
value, key)
|
||||
sys.exit(exitcode)
|
||||
elif dictassign:
|
||||
for key in dictassign:
|
||||
if nodetype == 'nodegroups':
|
||||
exitcode = await session.simple_nodegroups_command(
|
||||
noderange, 'attributes/all', dictassign[key], key)
|
||||
else:
|
||||
exitcode = await session.simple_noderange_command(
|
||||
noderange, 'attributes/all', dictassign[key], key)
|
||||
else:
|
||||
if "=" in updateargs[1]:
|
||||
update_ready = True
|
||||
for arg in updateargs[1:]:
|
||||
if not '=' in arg:
|
||||
update_ready = False
|
||||
exitcode = 1
|
||||
if not update_ready:
|
||||
sys.stderr.write('Error: {0} Can not set and read at the same time!\n'.format(str(updateargs[1:])))
|
||||
sys.exit(exitcode)
|
||||
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]}
|
||||
else:
|
||||
key = val[0]
|
||||
value = val[1]
|
||||
if (nodetype == "nodegroups"):
|
||||
exitcode = await session.simple_nodegroups_command(noderange, 'attributes/all',
|
||||
value, key)
|
||||
else:
|
||||
exitcode = await session.simple_noderange_command(noderange, 'attributes/all',
|
||||
value, key)
|
||||
except Exception:
|
||||
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)
|
||||
@@ -0,0 +1,323 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2014 IBM Corporation
|
||||
# Copyright 2015 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 array
|
||||
import asyncio
|
||||
import ctypes
|
||||
import ctypes.util
|
||||
import confluent.tlv as tlv
|
||||
import socket
|
||||
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
|
||||
|
||||
|
||||
|
||||
|
||||
def _sendmsg(loop, fut, sock, msg, fds, rfd):
|
||||
if rfd is not None:
|
||||
loop.remove_reader(rfd)
|
||||
if fut.cancelled():
|
||||
return
|
||||
try:
|
||||
retdata = sock.sendmsg(
|
||||
[msg],
|
||||
[(socket.SOL_SOCKET, socket.SCM_RIGHTS, array.array("i", fds))])
|
||||
except (BlockingIOError, InterruptedError):
|
||||
fd = sock.fileno()
|
||||
loop.add_reader(fd, _sendmsg, loop, fut, sock, fd)
|
||||
except Exception as exc:
|
||||
fut.set_exception(exc)
|
||||
else:
|
||||
fut.set_result(retdata)
|
||||
|
||||
|
||||
def send_fds(sock, msg, fds):
|
||||
cloop = asyncio.get_event_loop()
|
||||
fut = cloop.create_future()
|
||||
_sendmsg(cloop, fut, sock, msg, fds, None)
|
||||
return fut
|
||||
|
||||
|
||||
def _recvmsg(loop, fut, sock, msglen, maxfds, rfd):
|
||||
if rfd is not None:
|
||||
loop.remove_reader(rfd)
|
||||
fds = array.array("i") # Array of ints
|
||||
try:
|
||||
msg, ancdata, flags, addr = sock.recvmsg(
|
||||
msglen, socket.CMSG_LEN(maxfds * fds.itemsize))
|
||||
except (BlockingIOError, InterruptedError):
|
||||
fd = sock.fileno()
|
||||
loop.add_reader(fd, _recvmsg, loop, fut, sock, msglen, maxfds, fd)
|
||||
except Exception as exc:
|
||||
fut.set_exception(exc)
|
||||
else:
|
||||
for cmsg_level, cmsg_type, cmsg_data in ancdata:
|
||||
if (cmsg_level == socket.SOL_SOCKET
|
||||
and cmsg_type == socket.SCM_RIGHTS):
|
||||
# Append data, ignoring any truncated integers at the end.
|
||||
fds.frombytes(
|
||||
cmsg_data[
|
||||
:len(cmsg_data) - (len(cmsg_data) % fds.itemsize)])
|
||||
fut.set_result((msg, list(fds)))
|
||||
|
||||
|
||||
def recv_fds(sock, msglen, maxfds):
|
||||
cloop = asyncio.get_event_loop()
|
||||
fut = cloop.create_future()
|
||||
_recvmsg(cloop, fut, sock, msglen, maxfds, None)
|
||||
return fut
|
||||
|
||||
|
||||
def decodestr(value):
|
||||
ret = None
|
||||
try:
|
||||
ret = value.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
try:
|
||||
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):
|
||||
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])
|
||||
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])
|
||||
|
||||
|
||||
async def sendall(handle, data):
|
||||
if isinstance(handle, tuple):
|
||||
handle[1].write(data)
|
||||
return await handle[1].drain()
|
||||
else:
|
||||
cloop = asyncio.get_event_loop()
|
||||
return await cloop.sock_sendall(handle, data)
|
||||
|
||||
async def close(handle):
|
||||
if isinstance(handle, tuple):
|
||||
handle[1].close()
|
||||
await handle[1].wait_closed()
|
||||
else:
|
||||
handle.close()
|
||||
|
||||
async def send(handle, data, filehandle=None):
|
||||
cloop = asyncio.get_event_loop()
|
||||
if isinstance(data, unicode):
|
||||
try:
|
||||
data = data.encode('utf-8')
|
||||
except AttributeError:
|
||||
pass
|
||||
if isinstance(data, bytes) or isinstance(data, unicode):
|
||||
# plain text, e.g. console data
|
||||
tl = len(data)
|
||||
if tl == 0:
|
||||
# if you don't have anything to say, don't say anything at all
|
||||
return
|
||||
if tl < 16777216:
|
||||
# type for string is '0', so we don't need
|
||||
# to xor anything in
|
||||
await sendall(handle, struct.pack("!I", tl))
|
||||
else:
|
||||
raise Exception("String data length exceeds protocol")
|
||||
await sendall(handle, data)
|
||||
elif isinstance(data, dict): # JSON currently only goes to 4 bytes
|
||||
# Some structured message, like what would be seen in http responses
|
||||
unicode_dictvalues(data) # make everything unicode, assuming UTF-8
|
||||
sdata = json.dumps(data, ensure_ascii=False, separators=(',', ':'))
|
||||
sdata = sdata.encode('utf-8')
|
||||
tl = len(sdata)
|
||||
if tl > 16777215:
|
||||
raise Exception("JSON data exceeds protocol limits")
|
||||
# xor in the type (0b1 << 24)
|
||||
if filehandle is None:
|
||||
tl |= 16777216
|
||||
await sendall(handle, struct.pack("!I", tl))
|
||||
await sendall(handle, sdata)
|
||||
elif isinstance(handle, tuple):
|
||||
raise Exception("Cannot send filehandle over network socket")
|
||||
else:
|
||||
tl |= (2 << 24)
|
||||
await cloop.sock_sendall(handle, struct.pack("!I", tl))
|
||||
await send_fds(handle, b'', [filehandle])
|
||||
|
||||
|
||||
async def _grabhdl(handle, size):
|
||||
if isinstance(handle, tuple):
|
||||
return await handle[0].read(size)
|
||||
else:
|
||||
cloop = asyncio.get_event_loop()
|
||||
return await cloop.sock_recv(handle, size)
|
||||
|
||||
|
||||
async def recvall(handle, size):
|
||||
rd = await _grabhdl(handle, size)
|
||||
while len(rd) < size:
|
||||
nd = await _grabhdl(handle, size - len(rd))
|
||||
if not nd:
|
||||
raise Exception("Error reading data")
|
||||
rd += nd
|
||||
return rd
|
||||
|
||||
|
||||
async def recv(handle):
|
||||
tl = await _grabhdl(handle, 4)
|
||||
if not tl:
|
||||
return None
|
||||
while len(tl) < 4:
|
||||
ndata = await _grabhdl(handle, 4 - len(tl))
|
||||
if not ndata:
|
||||
raise Exception("Error reading data")
|
||||
tl += ndata
|
||||
if len(tl) == 0:
|
||||
return None
|
||||
tl = struct.unpack("!I", tl)[0]
|
||||
if tl & 0b10000000000000000000000000000000:
|
||||
raise Exception("Protocol Violation, reserved bit set")
|
||||
# 4 byte tlv
|
||||
dlen = tl & 16777215 # grab lower 24 bits
|
||||
datatype = (tl & 2130706432) >> 24 # grab 7 bits from near beginning
|
||||
if dlen == 0:
|
||||
return None
|
||||
if datatype == tlv.Types.filehandle:
|
||||
if isinstance(handle, tuple):
|
||||
raise Exception('Filehandle not supported over TLS socket')
|
||||
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)
|
||||
i = await recv_fds(handle, 2048, 4)
|
||||
data = i[0]
|
||||
filehandles = i[1]
|
||||
data = json.loads(bytes(data))
|
||||
return ClientFile(data['filename'], data['mode'], filehandles[0])
|
||||
else:
|
||||
data = await _grabhdl(handle, dlen)
|
||||
while len(data) < dlen:
|
||||
ndata = await _grabhdl(handle, 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:
|
||||
return json.loads(data)
|
||||
@@ -16,11 +16,55 @@ import fcntl
|
||||
import sys
|
||||
import struct
|
||||
import termios
|
||||
import select
|
||||
|
||||
def get_screengeom():
|
||||
def get_screengeom(escfallback=False):
|
||||
# returns height in cells, width in cells, width in pixels, height in pixels
|
||||
return struct.unpack('hhhh', fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ,
|
||||
b'........'))
|
||||
geom = list(struct.unpack('hhhh', fcntl.ioctl(sys.stdout, termios.TIOCGWINSZ,
|
||||
b'........')))
|
||||
if escfallback and (geom[0] == 0 or geom[1] == 0):
|
||||
cellgeom = get_cell_geometry_using_esc_18t()
|
||||
geom[0], geom[1] = cellgeom
|
||||
if escfallback and (geom[2] == 0 or geom[3] == 0):
|
||||
pixgeom = get_pixel_geometry_using_esc_14t()
|
||||
geom[2], geom[3] = pixgeom
|
||||
return geom
|
||||
|
||||
def get_pixel_geometry_using_esc_14t():
|
||||
sys.stdout.write('\x1b[14t')
|
||||
sys.stdout.flush()
|
||||
rlist, _, _ = select.select([sys.stdin], [], [], 1)
|
||||
if not rlist:
|
||||
return 0, 0
|
||||
response = ''
|
||||
while True:
|
||||
c = sys.stdin.read(1)
|
||||
if c == 't':
|
||||
break
|
||||
response += c
|
||||
if not response.startswith('\x1b[4;'):
|
||||
return 0, 0
|
||||
pixgeom = response[4:].split(';')
|
||||
return int(pixgeom[1]), int(pixgeom[0])
|
||||
|
||||
def get_cell_geometry_using_esc_18t():
|
||||
sys.stdout.write('\x1b[18t')
|
||||
sys.stdout.flush()
|
||||
rlist, _, _ = select.select([sys.stdin], [], [], 1)
|
||||
if not rlist:
|
||||
return 0, 0
|
||||
response = ''
|
||||
while True:
|
||||
c = sys.stdin.read(1)
|
||||
if c == 't':
|
||||
break
|
||||
response += c
|
||||
if not response.startswith('\x1b[8;'):
|
||||
return 0, 0
|
||||
cellgeom = response[4:].split(';')
|
||||
return int(cellgeom[0]), int(cellgeom[1])
|
||||
|
||||
|
||||
class ScreenPrinter(object):
|
||||
|
||||
def __init__(self, noderange, client, textlen=4):
|
||||
|
||||
@@ -19,12 +19,8 @@ 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
|
||||
import socket
|
||||
import select
|
||||
from datetime import datetime
|
||||
import json
|
||||
import os
|
||||
|
||||
@@ -24,6 +24,8 @@ 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.
|
||||
This is different from setting the value to an empty string.
|
||||
|
||||
Arbitrary custom attributes can also be created with the `custom.` prefix.
|
||||
|
||||
Attributes may be specified by wildcard, for example `net.*switch` will report
|
||||
all attributes that begin with `net.` and end with `switch`.
|
||||
|
||||
|
||||
@@ -49,8 +49,7 @@ actually be in effect until a reboot.
|
||||
|
||||
* `-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.
|
||||
configuration reset to default. The component may be "uefi" or "bmc".
|
||||
|
||||
* `-m MAXNODES`, `--maxnodes=MAXNODES`:
|
||||
Specify a maximum number of nodes to configure, prompting if over
|
||||
|
||||
@@ -11,7 +11,7 @@ nodegroupattrib(8) -- List or change confluent nodegroup attributes
|
||||
|
||||
## DESCRIPTION
|
||||
|
||||
`nodegroupattrip` queries the confluent server to get information about nodes.
|
||||
`nodegroupattrib` queries the confluent server to get information about nodes.
|
||||
In the simplest form, it simply takes the given group and lists the attributes of that group.
|
||||
|
||||
Contrasted with nodeattrib(8), settings managed by nodegroupattrib will be added
|
||||
|
||||
@@ -32,6 +32,13 @@ 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.
|
||||
|
||||
## NOTES
|
||||
|
||||
When doing an attach of an https:// url, you may hit an error if you have not enrolled your certificate authority.
|
||||
In a general confluent environment, you can usually address it by:
|
||||
`# for cert in /var/lib/confluent/public/site/tls/*.pem; do nodecertutil s1-s4 installbmccacert $cert; done`
|
||||
|
||||
|
||||
## OPTIONS
|
||||
|
||||
* `-h`, `--help`:
|
||||
|
||||
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/python3
|
||||
import glob
|
||||
import gzip
|
||||
import base64
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
def collect_certificates(tmpdir):
|
||||
certdata = ''
|
||||
for cacert in glob.glob(f'{tmpdir}/*.pem'):
|
||||
with open(cacert, 'r') as f:
|
||||
certdata += f.read()
|
||||
return certdata
|
||||
|
||||
def embed_certificates(incfg, certdata):
|
||||
if not certdata:
|
||||
raise Exception('No certificates found to embed')
|
||||
incfg = incfg.replace('%CONFLUENTCERTCOLL%', certdata)
|
||||
return incfg
|
||||
|
||||
def embed_identity(incfg, identityjson):
|
||||
incfg = incfg.replace('%IDENTJSON%', identityjson)
|
||||
return incfg
|
||||
|
||||
def embed_apiclient(incfg, apiclient):
|
||||
with open(apiclient, 'r') as f:
|
||||
apiclientdata = f.read()
|
||||
compressed = gzip.compress(apiclientdata.encode())
|
||||
encoded = base64.b64encode(compressed).decode()
|
||||
incfg = incfg.replace('%APICLIENTZ64%', encoded)
|
||||
return incfg
|
||||
|
||||
def embed_data(tmpdir, outfile):
|
||||
templatefile = f'{tmpdir}/bfb.cfg.template'
|
||||
with open(templatefile, 'r') as f:
|
||||
incfg = f.read()
|
||||
|
||||
certdata = collect_certificates(tmpdir)
|
||||
incfg = embed_certificates(incfg, certdata)
|
||||
|
||||
with open(f'{tmpdir}/identity.json', 'r') as f:
|
||||
identityjson = f.read()
|
||||
|
||||
incfg = embed_identity(incfg, identityjson)
|
||||
|
||||
incfg = embed_apiclient(incfg, f'{tmpdir}/../apiclient')
|
||||
|
||||
with open(outfile, 'w') as f:
|
||||
f.write(incfg)
|
||||
|
||||
def get_identity_json(node):
|
||||
identity_file = f'/var/lib/confluent/private/site/identity_files/{node}.json'
|
||||
try:
|
||||
with open(identity_file, 'r') as f:
|
||||
return f.read()
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) != 4:
|
||||
print("Usage: bfb-autoinstall <node> <bfbfile> <rshim>")
|
||||
sys.exit(1)
|
||||
|
||||
node = sys.argv[1]
|
||||
bfbfile = sys.argv[2]
|
||||
rshim = sys.argv[3]
|
||||
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
currdir = os.getcwd()
|
||||
tempdir = tempfile.mkdtemp(prefix=f'bfb-autoinstall-{node}-')
|
||||
embed_data(f'{currdir}/{node}', f'{tempdir}/bfb.cfg')
|
||||
subprocess.check_call(['bfb-install', '-b', bfbfile, '-c', f'{tempdir}/bfb.cfg', '-r', rshim])
|
||||
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/python3
|
||||
import glob
|
||||
import gzip
|
||||
import base64
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
def collect_certificates(tmpdir):
|
||||
certdata = ''
|
||||
for cacert in glob.glob(f'{tmpdir}/*.pem'):
|
||||
with open(cacert, 'r') as f:
|
||||
certdata += f.read()
|
||||
return certdata
|
||||
|
||||
def embed_certificates(incfg, certdata):
|
||||
if not certdata:
|
||||
raise Exception('No certificates found to embed')
|
||||
incfg = incfg.replace('%CONFLUENTCERTCOLL%', certdata)
|
||||
return incfg
|
||||
|
||||
def embed_identity(incfg, identityjson):
|
||||
incfg = incfg.replace('%IDENTJSON%', identityjson)
|
||||
return incfg
|
||||
|
||||
def embed_apiclient(incfg, apiclient):
|
||||
with open(apiclient, 'r') as f:
|
||||
apiclientdata = f.read()
|
||||
compressed = gzip.compress(apiclientdata.encode())
|
||||
encoded = base64.b64encode(compressed).decode()
|
||||
incfg = incfg.replace('%APICLIENTZ64%', encoded)
|
||||
return incfg
|
||||
|
||||
def embed_data(tmpdir, outfile):
|
||||
templatefile = f'{tmpdir}/bfb.cfg.template'
|
||||
with open(templatefile, 'r') as f:
|
||||
incfg = f.read()
|
||||
|
||||
certdata = collect_certificates(tmpdir)
|
||||
incfg = embed_certificates(incfg, certdata)
|
||||
|
||||
with open(f'{tmpdir}/identity.json', 'r') as f:
|
||||
identityjson = f.read()
|
||||
|
||||
incfg = embed_identity(incfg, identityjson)
|
||||
|
||||
incfg = embed_apiclient(incfg, f'{tmpdir}/../apiclient')
|
||||
|
||||
with open(outfile, 'w') as f:
|
||||
f.write(incfg)
|
||||
|
||||
def get_identity_json(node):
|
||||
identity_file = f'/var/lib/confluent/private/site/identity_files/{node}.json'
|
||||
try:
|
||||
with open(identity_file, 'r') as f:
|
||||
return f.read()
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) != 4:
|
||||
print("Usage: bfb-autoinstall <node> <bfbfile> <rshim>")
|
||||
sys.exit(1)
|
||||
|
||||
node = sys.argv[1]
|
||||
bfbfile = sys.argv[2]
|
||||
rshim = sys.argv[3]
|
||||
|
||||
os.chdir(os.path.dirname(os.path.abspath(__file__)))
|
||||
currdir = os.getcwd()
|
||||
tempdir = tempfile.mkdtemp(prefix=f'bfb-autoinstall-{node}-')
|
||||
embed_data(f'{currdir}/{node}', f'{tempdir}/bfb.cfg')
|
||||
subprocess.check_call(['bfb-install', '-b', bfbfile, '-c', f'{tempdir}/bfb.cfg', '-r', rshim])
|
||||
@@ -0,0 +1,76 @@
|
||||
function bfb_modify_os() {
|
||||
echo 'ubuntu:!' | chpasswd -e
|
||||
mkdir -p /mnt/opt/confluent/bin/
|
||||
cat > /mnt/opt/confluent/bin/confluentbootstrap.sh << 'END_OF_EMBED'
|
||||
#!/bin/bash
|
||||
cat > /usr/local/share/ca-certificates/confluent.crt << 'END_OF_CERTS'
|
||||
%CONFLUENTCERTCOLL%
|
||||
END_OF_CERTS
|
||||
update-ca-certificates
|
||||
mkdir -p /opt/confluent/bin /etc/confluent/
|
||||
cp /usr/local/share/ca-certificates/confluent.crt /etc/confluent/ca.pem
|
||||
cat > /opt/confluent/bin/apiclient.gz.b64 << 'END_OF_CLIENT'
|
||||
%APICLIENTZ64%
|
||||
END_OF_CLIENT
|
||||
base64 -d /opt/confluent/bin/apiclient.gz.b64 | gunzip > /opt/confluent/bin/apiclient
|
||||
cat > /etc/confluent/ident.json << 'END_OF_IDENT'
|
||||
%IDENTJSON%
|
||||
END_OF_IDENT
|
||||
python3 /opt/confluent/bin/apiclient -i /etc/confluent/ident.json /confluent-api/self/deploycfg2 > /etc/confluent/confluent.deploycfg
|
||||
PROFILE=$(grep ^profile: /etc/confluent/confluent.deploycfg |awk '{print $2}')
|
||||
ROOTPASS=$(grep ^rootpassword: /etc/confluent/confluent.deploycfg | awk '{print $2}'|grep -v null)
|
||||
if [ -n "$ROOTPASS" ]; then
|
||||
echo root:$ROOTPASS | chpasswd -e
|
||||
echo "ubuntu:$ROOTPASS" | chpasswd -e
|
||||
else
|
||||
echo 'ubuntu:!' | chpasswd -e
|
||||
fi
|
||||
cntmp=$(mktemp -d)
|
||||
cd "$cntmp" || { echo "Failed to cd to temporary directory $cntmp"; exit 1; }
|
||||
touch /etc/confluent/confluent.deploycfg
|
||||
python3 /opt/confluent/bin/apiclient /confluent-public/os/$PROFILE/scripts/confignet > confignet
|
||||
python3 confignet
|
||||
cd -
|
||||
rm -rf "$cntmp"
|
||||
python3 /opt/confluent/bin/apiclient /confluent-public/os/$PROFILE/scripts/functions > /etc/confluent/functions
|
||||
bash /etc/confluent/functions run_remote setupssh
|
||||
for cert in /etc/ssh/ssh*-cert.pub; do
|
||||
if [ -s $cert ]; then
|
||||
echo HostCertificate $cert >> /etc/ssh/sshd_config.d/90-confluent.conf
|
||||
fi
|
||||
done
|
||||
mkdir -p /var/log/confluent
|
||||
chmod 700 /var/log/confluent
|
||||
touch /var/log/confluent/confluent-firstboot.log
|
||||
touch /var/log/confluent/confluent-post.log
|
||||
chmod 600 /var/log/confluent/confluent-post.log
|
||||
chmod 600 /var/log/confluent/confluent-firstboot.log
|
||||
exec >> /var/log/confluent/confluent-post.log
|
||||
exec 2>> /var/log/confluent/confluent-post.log
|
||||
bash /etc/confluent/functions run_remote_python syncfileclient
|
||||
bash /etc/confluent/functions run_remote_parts post.d
|
||||
bash /etc/confluent/functions run_remote_config post.d
|
||||
exec >> /var/log/confluent/confluent-firstboot.log
|
||||
exec 2>> /var/log/confluent/confluent-firstboot.log
|
||||
bash /etc/confluent/functions run_remote_parts firstboot.d
|
||||
bash /etc/confluent/functions run_remote_config firstboot.d
|
||||
python3 /opt/confluent/bin/apiclient /confluent-api/self/updatestatus -d 'status: staged'
|
||||
python3 /opt/confluent/bin/apiclient /confluent-api/self/updatestatus -d 'status: complete'
|
||||
systemctl disable confluentbootstrap
|
||||
rm /etc/systemd/system/confluentbootstrap.service
|
||||
END_OF_EMBED
|
||||
chmod +x /mnt/opt/confluent/bin/confluentbootstrap.sh
|
||||
cat > /mnt/etc/systemd/system/confluentbootstrap.service << EOS
|
||||
[Unit]
|
||||
Description=First Boot Process
|
||||
Requires=network-online.target
|
||||
After=network-online.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/opt/confluent/bin/confluentbootstrap.sh
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOS
|
||||
chroot /mnt systemctl enable confluentbootstrap
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import glob
|
||||
import shutil
|
||||
import shlex
|
||||
import subprocess
|
||||
import select
|
||||
|
||||
sys.path.append('/opt/lib/confluent/python')
|
||||
|
||||
import confluent.sortutil as sortutil
|
||||
import confluent.client as client
|
||||
|
||||
|
||||
def prep_outdir(node):
|
||||
tmpdir = tempfile.mkdtemp()
|
||||
for certfile in glob.glob('/var/lib/confluent/public/site/tls/*.pem'):
|
||||
basename = os.path.basename(certfile)
|
||||
destfile = os.path.join(tmpdir, basename)
|
||||
shutil.copy2(certfile, destfile)
|
||||
subprocess.check_call(shlex.split(f'confetty set /nodes/{node}/deployment/ident_image=create'))
|
||||
shutil.copy2(f'/var/lib/confluent/private/identity_files/{node}.json', os.path.join(tmpdir, 'identity.json'))
|
||||
return tmpdir
|
||||
|
||||
def exec_bfb_install(host, nodetorshim, bfbfile, installprocs, pipedesc, all, poller):
|
||||
remotedir = subprocess.check_output(shlex.split(f'ssh {host} mktemp -d /tmp/bfb.XXXXXX')).decode().strip()
|
||||
bfbbasename = os.path.basename(bfbfile)
|
||||
subprocess.check_call(shlex.split(f'rsync -avz --info=progress2 {bfbfile} {host}:{remotedir}/{bfbbasename}'))
|
||||
subprocess.check_call(shlex.split(f'rsync -avc --info=progress2 /opt/lib/confluent/osdeploy/bluefield/hostscripts/ {host}:{remotedir}/'))
|
||||
for node in nodetorshim:
|
||||
rshim = nodetorshim[node]
|
||||
nodeoutdir = prep_outdir(node)
|
||||
nodeprofile = subprocess.check_output(shlex.split(f'nodeattrib {node} deployment.pendingprofile')).decode().strip().split(':', 2)[2].strip()
|
||||
shutil.copy2(f'/var/lib/confluent/public/os/{nodeprofile}/bfb.cfg.template', os.path.join(nodeoutdir, 'bfb.cfg.template'))
|
||||
subprocess.check_call(shlex.split(f'rsync -avz {nodeoutdir}/ {host}:{remotedir}/{node}/'))
|
||||
shutil.rmtree(nodeoutdir)
|
||||
run_cmdv(node, shlex.split(f'ssh {host} sh /etc/confluent/functions confluentpython {remotedir}/bfb-autoinstall {node} {remotedir}/{bfbbasename} {rshim}'), all, poller, pipedesc)
|
||||
|
||||
|
||||
def run_cmdv(node, cmdv, all, poller, pipedesc):
|
||||
try:
|
||||
nopen = subprocess.Popen(
|
||||
cmdv, stdin=subprocess.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}"\n'.format(node, cmdv[0]))
|
||||
return
|
||||
raise
|
||||
pipedesc[nopen.stdout.fileno()] = {'node': node, 'popen': nopen,
|
||||
'type': 'stdout', 'file': nopen.stdout}
|
||||
pipedesc[nopen.stderr.fileno()] = {'node': node, 'popen': nopen,
|
||||
'type': 'stderr', 'file': nopen.stderr}
|
||||
all.add(nopen.stdout)
|
||||
poller.register(nopen.stdout, select.EPOLLIN)
|
||||
all.add(nopen.stderr)
|
||||
poller.register(nopen.stderr, select.EPOLLIN)
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
|
||||
if len(sys.argv) < 3:
|
||||
print(f'Usage: {sys.argv[0]} <host> <bfbfile> <node1:rshim1> [<node2:rshim2> ...]')
|
||||
sys.exit(1)
|
||||
|
||||
host = sys.argv[1]
|
||||
bfbfile = sys.argv[2]
|
||||
nodetorshim = {}
|
||||
for arg in sys.argv[3:]:
|
||||
node, rshim = arg.split(':')
|
||||
nodetorshim[node] = rshim
|
||||
|
||||
installprocs = {}
|
||||
pipedesc = {}
|
||||
all = set()
|
||||
poller = select.epoll()
|
||||
|
||||
exec_bfb_install(host, nodetorshim, bfbfile, installprocs, pipedesc, all, poller)
|
||||
rdy = poller.poll(10)
|
||||
pendingexecs = []
|
||||
exitcode = 0
|
||||
while all:
|
||||
pernodeout = {}
|
||||
for r in rdy:
|
||||
r = r[0]
|
||||
desc = pipedesc[r]
|
||||
r = desc['file']
|
||||
node = desc['node']
|
||||
data = True
|
||||
singlepoller = select.epoll()
|
||||
singlepoller.register(r, select.EPOLLIN)
|
||||
while data and singlepoller.poll(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)
|
||||
poller.unregister(r)
|
||||
r.close()
|
||||
if desc['type'] == 'stdout' and pendingexecs:
|
||||
node, cmdv = pendingexecs.popleft()
|
||||
run_cmdv(node, cmdv, all, poller, pipedesc)
|
||||
singlepoller.close()
|
||||
for node in sortutil.natural_sort(pernodeout):
|
||||
for line in pernodeout[node]:
|
||||
line = client.stringify(line)
|
||||
sys.stdout.write('{0}: {1}'.format(node, line))
|
||||
sys.stdout.flush()
|
||||
if all:
|
||||
rdy = poller.poll(10)
|
||||
|
||||
|
||||
@@ -47,38 +47,113 @@ c_crypt.restype = ctypes.c_char_p
|
||||
|
||||
|
||||
def get_my_addresses():
|
||||
nlhdrsz = struct.calcsize('IHHII')
|
||||
ifaddrsz = struct.calcsize('BBBBI')
|
||||
# RTM_GETADDR = 22
|
||||
# nlmsghdr struct: u32 len, u16 type, u16 flags, u32 seq, u32 pid
|
||||
nlhdr = struct.pack('IHHII', nlhdrsz + ifaddrsz, 22, 0x301, 0, 0)
|
||||
# ifaddrmsg struct: u8 family, u8 prefixlen, u8 flags, u8 scope, u32 index
|
||||
ifaddrmsg = struct.pack('BBBBI', 0, 0, 0, 0, 0)
|
||||
s = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, socket.NETLINK_ROUTE)
|
||||
s.bind((0, 0))
|
||||
s.sendall(nlhdr + ifaddrmsg)
|
||||
addrs = []
|
||||
while True:
|
||||
pdata = s.recv(65536)
|
||||
v = memoryview(pdata)
|
||||
if struct.unpack('H', v[4:6])[0] == 3: # netlink done message
|
||||
break
|
||||
while len(v):
|
||||
length, typ = struct.unpack('IH', v[:6])
|
||||
if typ == 20:
|
||||
fam, plen, _, scope, ridx = struct.unpack('BBBBI', v[nlhdrsz:nlhdrsz+ifaddrsz])
|
||||
if scope in (253, 0):
|
||||
rta = v[nlhdrsz+ifaddrsz:length]
|
||||
while len(rta):
|
||||
rtalen, rtatyp = struct.unpack('HH', rta[:4])
|
||||
if rtalen < 4:
|
||||
break
|
||||
if rtatyp == 1:
|
||||
addrs.append((fam, rta[4:rtalen], plen, ridx))
|
||||
rta = rta[msg_align(rtalen):]
|
||||
v = v[msg_align(length):]
|
||||
for ifa in get_ifaddrs():
|
||||
if ifa[0] == 'ip':
|
||||
addrs.append((ifa[1], ifa[2], ifa[3]))
|
||||
return addrs
|
||||
|
||||
def get_mac_addresses():
|
||||
macs = []
|
||||
for ifa in get_ifaddrs():
|
||||
if ifa[0] == 'ETHER':
|
||||
macs.append((ifa[1], ifa[2]))
|
||||
return macs
|
||||
|
||||
def get_ifaddrs():
|
||||
class sockaddr(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('sa_family', ctypes.c_uint16),
|
||||
('sa_data', ctypes.c_ubyte * 14),
|
||||
]
|
||||
|
||||
class sockaddr_in(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('sin_family', ctypes.c_uint16),
|
||||
('sin_port', ctypes.c_uint16),
|
||||
('sin_addr', ctypes.c_ubyte * 4),
|
||||
('sin_zero', ctypes.c_ubyte * 8),
|
||||
]
|
||||
|
||||
class sockaddr_in6(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('sin6_family', ctypes.c_uint16),
|
||||
('sin6_port', ctypes.c_uint16),
|
||||
('sin6_flowinfo', ctypes.c_uint32),
|
||||
('sin6_addr', ctypes.c_ubyte * 16),
|
||||
('sin6_scope_id', ctypes.c_uint32),
|
||||
]
|
||||
|
||||
class sockaddr_ll(ctypes.Structure):
|
||||
_fields_ = [
|
||||
('sll_family', ctypes.c_uint16),
|
||||
('sll_protocol', ctypes.c_uint16),
|
||||
('sll_ifindex', ctypes.c_int32),
|
||||
('sll_hatype', ctypes.c_uint16),
|
||||
('sll_pkttype', ctypes.c_uint8),
|
||||
('sll_halen', ctypes.c_uint8),
|
||||
('sll_addr', ctypes.c_ubyte * 8),
|
||||
]
|
||||
|
||||
class ifaddrs(ctypes.Structure):
|
||||
pass
|
||||
|
||||
ifaddrs._fields_ = [
|
||||
('ifa_next', ctypes.POINTER(ifaddrs)),
|
||||
('ifa_name', ctypes.c_char_p),
|
||||
('ifa_flags', ctypes.c_uint),
|
||||
('ifa_addr', ctypes.POINTER(sockaddr)),
|
||||
('ifa_netmask', ctypes.POINTER(sockaddr)),
|
||||
('ifa_ifu', ctypes.POINTER(sockaddr)),
|
||||
('ifa_data', ctypes.c_void_p),
|
||||
]
|
||||
|
||||
libc = ctypes.CDLL(ctypes.util.find_library('c'))
|
||||
libc.getifaddrs.argtypes = [ctypes.POINTER(ctypes.POINTER(ifaddrs))]
|
||||
libc.getifaddrs.restype = ctypes.c_int
|
||||
libc.freeifaddrs.argtypes = [ctypes.POINTER(ifaddrs)]
|
||||
libc.freeifaddrs.restype = None
|
||||
ifap = ctypes.POINTER(ifaddrs)()
|
||||
result = libc.getifaddrs(ctypes.pointer(ifap))
|
||||
if result != 0:
|
||||
return []
|
||||
addresses = []
|
||||
ifa = ifap
|
||||
try:
|
||||
while ifa:
|
||||
if ifa.contents.ifa_addr:
|
||||
family = ifa.contents.ifa_addr.contents.sa_family
|
||||
name = ifa.contents.ifa_name.decode('utf-8') if ifa.contents.ifa_name else None
|
||||
if family in (socket.AF_INET, socket.AF_INET6):
|
||||
# skip loopback and non-multicast interfaces
|
||||
if ifa.contents.ifa_flags & 8 or not ifa.contents.ifa_flags & 0x1000:
|
||||
ifa = ifa.contents.ifa_next
|
||||
continue
|
||||
if family == socket.AF_INET:
|
||||
addr_ptr = ctypes.cast(ifa.contents.ifa_addr, ctypes.POINTER(sockaddr_in))
|
||||
addr_bytes = bytes(addr_ptr.contents.sin_addr)
|
||||
if_index = socket.if_nametoindex(name) if name else 0
|
||||
addresses.append(('ip', family, addr_bytes, if_index))
|
||||
elif family == socket.AF_INET6:
|
||||
addr_ptr = ctypes.cast(ifa.contents.ifa_addr, ctypes.POINTER(sockaddr_in6))
|
||||
addr_bytes = bytes(addr_ptr.contents.sin6_addr)
|
||||
scope_id = addr_ptr.contents.sin6_scope_id
|
||||
addresses.append(('ip', family, addr_bytes, scope_id))
|
||||
elif family == socket.AF_PACKET:
|
||||
addr_ptr = ctypes.cast(ifa.contents.ifa_addr, ctypes.POINTER(sockaddr_ll))
|
||||
halen = addr_ptr.contents.sll_halen
|
||||
if addr_ptr.contents.sll_hatype in (1, 32) and halen > 0: # ARPHRD_ETHER or ARPHRD_INFINIBAND
|
||||
if addr_ptr.contents.sll_hatype == 1 and addr_ptr.contents.sll_addr[0] & 2: # skip locally administered MACs
|
||||
ifa = ifa.contents.ifa_next
|
||||
continue
|
||||
mac_bytes = bytes(addr_ptr.contents.sll_addr[:halen])
|
||||
macaddr = ':'.join('{:02x}'.format(b) for b in mac_bytes)
|
||||
addresses.append(('ETHER', name, macaddr))
|
||||
ifa = ifa.contents.ifa_next
|
||||
finally:
|
||||
libc.freeifaddrs(ifap)
|
||||
|
||||
return addresses
|
||||
|
||||
def scan_confluents(confuuid=None):
|
||||
srvs = {}
|
||||
@@ -92,22 +167,24 @@ def scan_confluents(confuuid=None):
|
||||
s4.bind(('0.0.0.0', 1900))
|
||||
doneidxs = set([])
|
||||
msg = 'M-SEARCH * HTTP/1.1\r\nST: urn:xcat.org:service:confluent:'
|
||||
if not confuuid:
|
||||
if not confuuid and os.path.exists('/etc/confluent/confluent.deploycfg'):
|
||||
with open('/etc/confluent/confluent.deploycfg') as dcfg:
|
||||
for line in dcfg.read().split('\n'):
|
||||
if line.startswith('confluent_uuid:'):
|
||||
confluentuuid = line.split(': ')[1]
|
||||
msg += '/confluentuuid=' + confluentuuid
|
||||
break
|
||||
if not confuuid and os.path.exists('/confluent_uuid'):
|
||||
with open('/confluent_uuid') as cuuidin:
|
||||
confluentuuid = cuuidin.read().strip()
|
||||
msg += '/confluentuuid=' + confluentuuid
|
||||
try:
|
||||
with open('/sys/devices/virtual/dmi/id/product_uuid') as uuidin:
|
||||
msg += '/uuid=' + uuidin.read().strip()
|
||||
except Exception:
|
||||
pass
|
||||
for addrf in glob.glob('/sys/class/net/*/address'):
|
||||
with open(addrf) as addrin:
|
||||
hwaddr = addrin.read().strip()
|
||||
msg += '/mac=' + hwaddr
|
||||
for iface, hwaddr in get_mac_addresses():
|
||||
msg += '/mac=' + hwaddr
|
||||
msg = msg.encode('utf8')
|
||||
for addr in get_my_addresses():
|
||||
if addr[0] == socket.AF_INET6:
|
||||
@@ -155,7 +232,8 @@ def scan_confluents(confuuid=None):
|
||||
if currip.startswith('fe80::') and '%' not in currip:
|
||||
currip = '{0}%{1}'.format(currip, peer[-1])
|
||||
srvs[currip] = current
|
||||
srvlist.append(currip)
|
||||
if currip not in srvlist:
|
||||
srvlist.append(currip)
|
||||
r = select.select((s4, s6), (), (), 2)
|
||||
if r:
|
||||
r = r[0]
|
||||
|
||||
@@ -356,7 +356,7 @@ class NetworkManager(object):
|
||||
currteam = deats.get('connection.master', None)
|
||||
if currteam == team:
|
||||
return
|
||||
for stg in ('ipv4.dhcp-hostname', 'ipv4.dns', 'ipv6.dns', 'ipv6.dhcp-hostname'):
|
||||
for stg in ('ipv4.dhcp-hostname', 'ipv4.dns', 'ipv6.dns', 'ipv6.dhcp-hostname', 'ipv4.dns-search', 'ipv6.dns-search'):
|
||||
if deats.get(stg, None):
|
||||
bondcfg[stg] = deats[stg]
|
||||
if member in self.uuidbyname:
|
||||
@@ -488,15 +488,16 @@ if __name__ == '__main__':
|
||||
continue
|
||||
myname = s.getsockname()
|
||||
s.close()
|
||||
curridx = None
|
||||
if len(myname) == 4:
|
||||
curridx = myname[-1]
|
||||
else:
|
||||
myname = myname[0]
|
||||
myname = socket.inet_pton(socket.AF_INET, myname)
|
||||
for addr in myaddrs:
|
||||
if myname == addr[1].tobytes():
|
||||
if myname == addr[1]:
|
||||
curridx = addr[-1]
|
||||
if curridx in doneidxs:
|
||||
if curridx is not None and curridx in doneidxs:
|
||||
continue
|
||||
for tries in (1, 2, 3):
|
||||
try:
|
||||
|
||||
@@ -27,7 +27,6 @@ mkdir -p stateless-bin
|
||||
cp -a el8bin/* .
|
||||
ln -s el8 el9
|
||||
ln -s el8 el10
|
||||
mv el10/initramfs/usr el10/initramfs/var
|
||||
cp -a debian debian13
|
||||
mkdir -p debian13/initramfs/usr
|
||||
mv debian13/initramfs/lib debian13/initramfs/usr/
|
||||
@@ -86,6 +85,9 @@ cp -a esxi7 esxi8
|
||||
%install
|
||||
mkdir -p %{buildroot}/opt/confluent/share/licenses/confluent_osdeploy/
|
||||
#cp LICENSE %{buildroot}/opt/confluent/share/licenses/confluent_osdeploy/
|
||||
mkdir -p %{buildroot}/opt/confluent/lib/osdeploy/common
|
||||
cp common/initramfs/opt/confluent/bin/apiclient %{buildroot}/opt/confluent/lib/osdeploy/common
|
||||
|
||||
for os in rhvh4 el7 el8 el9 el10 debian debian13 genesis suse15 ubuntu20.04 ubuntu22.04 ubuntu24.04 esxi6 esxi7 esxi8 coreos; do
|
||||
mkdir -p %{buildroot}/opt/confluent/lib/osdeploy/$os/initramfs/aarch64/
|
||||
cp ${os}out/addons.* %{buildroot}/opt/confluent/lib/osdeploy/$os/initramfs/aarch64/
|
||||
|
||||
@@ -3,8 +3,8 @@ confluent_whost=$confluent_mgr
|
||||
if [[ "$confluent_whost" == *:* ]] && [[ "$confluent_whost" != "["* ]]; then
|
||||
confluent_whost="[$confluent_mgr]"
|
||||
fi
|
||||
mkdir -p /mnt/remoteimg /mnt/remote /mnt/overlay
|
||||
if [ "untethered" = "$(getarg confluent_imagemethod)" ]; then
|
||||
mkdir -p /mnt/remoteimg /mnt/remote /mnt/overlay /sysroot
|
||||
if [ "untethered" = "$(getarg confluent_imagemethod)" -o "uncompressed" = "$(getarg confluent_imagemethod)" ]; then
|
||||
mount -t tmpfs untethered /mnt/remoteimg
|
||||
curl https://$confluent_whost/confluent-public/os/$confluent_profile/rootimg.sfs -o /mnt/remoteimg/rootimg.sfs
|
||||
else
|
||||
@@ -40,20 +40,53 @@ fi
|
||||
|
||||
|
||||
#mount -t tmpfs overlay /mnt/overlay
|
||||
modprobe zram
|
||||
memtot=$(grep ^MemTotal: /proc/meminfo|awk '{print $2}')
|
||||
memtot=$((memtot/2))$(grep ^MemTotal: /proc/meminfo | awk '{print $3'})
|
||||
echo $memtot > /sys/block/zram0/disksize
|
||||
mkfs.xfs /dev/zram0 > /dev/null
|
||||
mount -o discard /dev/zram0 /mnt/overlay
|
||||
if [ ! -f /tmp/mountparts.sh ]; then
|
||||
mkdir -p /mnt/overlay/upper /mnt/overlay/work
|
||||
mount -t overlay -o upperdir=/mnt/overlay/upper,workdir=/mnt/overlay/work,lowerdir=/mnt/remote disklessroot /sysroot
|
||||
if [ ! "uncompressed" = "$(getarg confluent_imagemethod)" ]; then
|
||||
modprobe zram
|
||||
memtot=$(grep ^MemTotal: /proc/meminfo|awk '{print $2}')
|
||||
memtot=$((memtot/2))$(grep ^MemTotal: /proc/meminfo | awk '{print $3'})
|
||||
echo $memtot > /sys/block/zram0/disksize
|
||||
mkfs.xfs /dev/zram0 > /dev/null
|
||||
fi
|
||||
TETHERED=0
|
||||
if [ "untethered" = "$(getarg confluent_imagemethod)" -o "uncompressed" = "$(getarg confluent_imagemethod)" ]; then
|
||||
if [ "untethered" = "$(getarg confluent_imagemethod)" ]; then
|
||||
mount -o discard /dev/zram0 /sysroot
|
||||
else
|
||||
mount -t tmpfs disklessroot /sysroot
|
||||
fi
|
||||
echo -en "Decrypting and extracting root filesystem: 0%\r"
|
||||
srcsz=$(du -sk /mnt/remote | awk '{print $1}')
|
||||
while [ -f /mnt/remoteimg/rootimg.sfs ]; do
|
||||
dstsz=$(du -sk /sysroot | awk '{print $1}')
|
||||
pct=$((dstsz * 100 / srcsz))
|
||||
if [ $pct -gt 99 ]; then
|
||||
pct=99
|
||||
fi
|
||||
echo -en "Decrypting and extracting root filesystem: $pct%\r"
|
||||
sleep 0.25
|
||||
done &
|
||||
cp -ax /mnt/remote/* /sysroot/
|
||||
umount /mnt/remote
|
||||
if [ -e /dev/mapper/cryptimg ]; then
|
||||
dmsetup remove cryptimg
|
||||
fi
|
||||
losetup -d $loopdev
|
||||
rm /mnt/remoteimg/rootimg.sfs
|
||||
umount /mnt/remoteimg
|
||||
wait
|
||||
echo -e "Decrypting and extracting root filesystem: 100%"
|
||||
else
|
||||
for srcmount in $(cat /tmp/mountparts.sh | awk '{print $3}'); do
|
||||
mkdir -p /mnt/overlay${srcmount}/upper /mnt/overlay${srcmount}/work
|
||||
mount -t overlay -o upperdir=/mnt/overlay${srcmount}/upper,workdir=/mnt/overlay${srcmount}/work,lowerdir=${srcmount} disklesspart /sysroot${srcmount#/mnt/remote}
|
||||
done
|
||||
TETHERED=1
|
||||
mount -o discard /dev/zram0 /mnt/overlay
|
||||
if [ ! -f /tmp/mountparts.sh ]; then
|
||||
mkdir -p /mnt/overlay/upper /mnt/overlay/work
|
||||
mount -t overlay -o upperdir=/mnt/overlay/upper,workdir=/mnt/overlay/work,lowerdir=/mnt/remote disklessroot /sysroot
|
||||
else
|
||||
for srcmount in $(cat /tmp/mountparts.sh | awk '{print $3}'); do
|
||||
mkdir -p /mnt/overlay${srcmount}/upper /mnt/overlay${srcmount}/work
|
||||
mount -t overlay -o upperdir=/mnt/overlay${srcmount}/upper,workdir=/mnt/overlay${srcmount}/work,lowerdir=${srcmount} disklesspart /sysroot${srcmount#/mnt/remote}
|
||||
done
|
||||
fi
|
||||
fi
|
||||
mkdir -p /sysroot/etc/ssh
|
||||
mkdir -p /sysroot/etc/confluent
|
||||
@@ -109,7 +142,7 @@ echo ' EnableSSHKeysign yes' >> $sshconf
|
||||
echo ' HostbasedKeyTypes *ed25519*' >> $sshconf
|
||||
curl -sf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" https://$confluent_whost/confluent-api/self/nodelist > /sysroot/etc/ssh/shosts.equiv
|
||||
cp /sysroot/etc/ssh/shosts.equiv /sysroot/root/.shosts
|
||||
chmod 640 /sysroot/etc/ssh/*_key
|
||||
chmod 600 /sysroot/etc/ssh/*_key
|
||||
cp /tls/*.pem /sysroot/etc/pki/ca-trust/source/anchors/
|
||||
chroot /sysroot/ update-ca-trust
|
||||
curl -sf https://$confluent_whost/confluent-public/os/$confluent_profile/scripts/onboot.service > /sysroot/etc/systemd/system/onboot.service
|
||||
@@ -129,10 +162,25 @@ ln -s /sysroot/lib/modules/$(uname -r) /lib/modules/
|
||||
mv /lib/firmware /lib/firmware-ramfs
|
||||
ln -s /sysroot/lib/firmware /lib/firmware
|
||||
kill $(grep -l ^/usr/lib/systemd/systemd-udevd /proc/*/cmdline|cut -d/ -f 3)
|
||||
if [ $TETHERED -eq 1 ]; then
|
||||
(
|
||||
sleep 86400 &
|
||||
ONBOOTPID=$!
|
||||
mkdir -p /run/confluent
|
||||
echo $ONBOOTPID > /run/confluent/onboot_sleep.pid
|
||||
wait $ONBOOTPID
|
||||
dd if=/mnt/remoteimg/rootimg.sfs iflag=nocache count=0 >& /dev/null
|
||||
rm -rf /lib/modules/$(uname -r) /lib/modules/$(uname -r)-ramfs /lib/firmware-ramfs /usr/lib64/libcrypto.so* /usr/lib64/systemd/ /kernel/ /usr/bin/ /usr/sbin/ /usr/libexec/
|
||||
) &
|
||||
while [ ! -f /run/confluent/onboot_sleep.pid ]; do
|
||||
sleep 0.1
|
||||
done
|
||||
else
|
||||
rm -rf /lib/modules/$(uname -r) /lib/modules/$(uname -r)-ramfs /lib/firmware-ramfs /usr/lib64/libcrypto.so* /usr/lib64/systemd/ /kernel/ /usr/bin/ /usr/sbin/ /usr/libexec/
|
||||
fi
|
||||
if grep debugssh /proc/cmdline >& /dev/null; then
|
||||
exec /opt/confluent/bin/start_root
|
||||
else
|
||||
rm -rf /lib/modules/$(uname -r) /lib/modules/$(uname -r)-ramfs /lib/firmware-ramfs /usr/lib64/libcrypto.so* /usr/lib64/systemd/ /kernel/ /usr/bin/ /usr/sbin/ /usr/libexec/
|
||||
exec /opt/confluent/bin/start_root -s # share mount namespace, keep kernel callbacks intact
|
||||
fi
|
||||
|
||||
|
||||
@@ -4,10 +4,12 @@ if [[ "$confluent_whost" == *:* ]] && [[ "$confluent_whost" != "["* ]]; then
|
||||
confluent_whost="[$confluent_mgr]"
|
||||
fi
|
||||
mkdir -p /mnt/remoteimg /mnt/remote /mnt/overlay
|
||||
TETHERED=0
|
||||
if [ "untethered" = "$(getarg confluent_imagemethod)" ]; then
|
||||
mount -t tmpfs untethered /mnt/remoteimg
|
||||
curl https://$confluent_whost/confluent-public/os/$confluent_profile/rootimg.sfs -o /mnt/remoteimg/rootimg.sfs
|
||||
else
|
||||
TETHERED=1
|
||||
confluent_urls="$confluent_urls https://$confluent_whost/confluent-public/os/$confluent_profile/rootimg.sfs"
|
||||
/opt/confluent/bin/urlmount $confluent_urls /mnt/remoteimg
|
||||
fi
|
||||
@@ -130,4 +132,17 @@ ln -s /sysroot/lib/modules/$(uname -r) /lib/modules/
|
||||
mv /lib/firmware /lib/firmware-ramfs
|
||||
ln -s /sysroot/lib/firmware /lib/firmware
|
||||
kill $(grep -l ^/usr/lib/systemd/systemd-udevd /proc/*/cmdline|cut -d/ -f 3)
|
||||
if [ $TETHERED -eq 1 ]; then
|
||||
(
|
||||
sleep 86400 &
|
||||
ONBOOTPID=$!
|
||||
mkdir -p /run/confluent
|
||||
echo $ONBOOTPID > /run/confluent/onboot_sleep.pid
|
||||
wait $ONBOOTPID
|
||||
dd if=/mnt/remoteimg/rootimg.sfs iflag=nocache count=0 >& /dev/null
|
||||
) &
|
||||
while [ ! -f /run/confluent/onboot_sleep.pid ]; do
|
||||
sleep 0.1
|
||||
done
|
||||
fi
|
||||
exec /opt/confluent/bin/start_root
|
||||
|
||||
@@ -68,5 +68,14 @@ run_remote_parts onboot.d
|
||||
# Induce execution of remote configuration, e.g. ansible plays in ansible/onboot.d/
|
||||
run_remote_config onboot.d
|
||||
|
||||
if [ -f /run/confluent/onboot_sleep.pid ]; then
|
||||
loopdev=$(losetup -j /mnt/remoteimg/rootimg.sfs|cut -d: -f 1)
|
||||
if [ -n "$loopdev" ]; then
|
||||
losetup "$loopdev" --direct-io=on
|
||||
fi
|
||||
sleeppid=$(cat /run/confluent/onboot_sleep.pid)
|
||||
kill "$sleeppid"
|
||||
rm -f /run/confluent/onboot_sleep.pid
|
||||
fi
|
||||
#curl -X POST -d 'status: booted' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/updatestatus
|
||||
kill $logshowpid
|
||||
|
||||
@@ -277,7 +277,10 @@ def synchronize():
|
||||
try:
|
||||
uid = pwd.getpwnam(opts[fname][opt]['name']).pw_uid
|
||||
except KeyError:
|
||||
uid = opts[fname][opt]['id']
|
||||
try:
|
||||
uid = opts[fname][opt]['id']
|
||||
except KeyError:
|
||||
raise Exception(f"Unable to map owner of {fname}")
|
||||
elif opt == 'group':
|
||||
try:
|
||||
gid = grp.getgrnam(opts[fname][opt]['name']).gr_gid
|
||||
|
||||
@@ -3,7 +3,7 @@ sed -i 's/centos/CentOS/; s/rhel/Red Hat Enterprise Linux/; s/oraclelinux/Oracle
|
||||
if grep Fedora $2/profile.yaml > /dev/null; then
|
||||
sed -i 's/@^minimal-environment/#/' $2/packagelist
|
||||
fi
|
||||
if grep ^label: $2/profile.yaml | grep 10 > /dev/null; then
|
||||
if grep ^label: $2/profile.yaml | grep ' 10' > /dev/null; then
|
||||
echo 'echo openssh-keysign >> /tmp/addonpackages' > $2/scripts/pre.d/enablekeysign
|
||||
chmod 644 $2/scripts/pre.d/enablekeysign
|
||||
fi
|
||||
|
||||
@@ -467,7 +467,7 @@ def install_to_disk(imgpath):
|
||||
subprocess.check_call(['pvcreate', '-ff', '-y', lvmpart])
|
||||
subprocess.check_call(['vgcreate', vgname, lvmpart])
|
||||
vgroupmap = {}
|
||||
if yaml and vgmap:
|
||||
if yaml and vgmap and os.path.exists('/tmp/volumegroupmap.yml'):
|
||||
with open('/tmp/volumegroupmap.yml') as mapin:
|
||||
vgroupmap = yaml.safe_load(mapin)
|
||||
donedisks = {}
|
||||
|
||||
@@ -3,8 +3,8 @@ confluent_whost=$confluent_mgr
|
||||
if [[ "$confluent_whost" == *:* ]] && [[ "$confluent_whost" != "["* ]]; then
|
||||
confluent_whost="[$confluent_mgr]"
|
||||
fi
|
||||
mkdir -p /mnt/remoteimg /mnt/remote /mnt/overlay
|
||||
if [ "untethered" = "$(getarg confluent_imagemethod)" ]; then
|
||||
mkdir -p /mnt/remoteimg /mnt/remote /mnt/overlay /sysroot
|
||||
if [ "untethered" = "$(getarg confluent_imagemethod)" -o "uncompressed" = "$(getarg confluent_imagemethod)" ]; then
|
||||
mount -t tmpfs untethered /mnt/remoteimg
|
||||
curl https://$confluent_whost/confluent-public/os/$confluent_profile/rootimg.sfs -o /mnt/remoteimg/rootimg.sfs
|
||||
else
|
||||
@@ -40,20 +40,53 @@ fi
|
||||
|
||||
|
||||
#mount -t tmpfs overlay /mnt/overlay
|
||||
modprobe zram
|
||||
memtot=$(grep ^MemTotal: /proc/meminfo|awk '{print $2}')
|
||||
memtot=$((memtot/2))$(grep ^MemTotal: /proc/meminfo | awk '{print $3'})
|
||||
echo $memtot > /sys/block/zram0/disksize
|
||||
mkfs.xfs /dev/zram0 > /dev/null
|
||||
mount -o discard /dev/zram0 /mnt/overlay
|
||||
if [ ! -f /tmp/mountparts.sh ]; then
|
||||
mkdir -p /mnt/overlay/upper /mnt/overlay/work
|
||||
mount -t overlay -o upperdir=/mnt/overlay/upper,workdir=/mnt/overlay/work,lowerdir=/mnt/remote disklessroot /sysroot
|
||||
if [ ! "uncompressed" = "$(getarg confluent_imagemethod)" ]; then
|
||||
modprobe zram
|
||||
memtot=$(grep ^MemTotal: /proc/meminfo|awk '{print $2}')
|
||||
memtot=$((memtot/2))$(grep ^MemTotal: /proc/meminfo | awk '{print $3'})
|
||||
echo $memtot > /sys/block/zram0/disksize
|
||||
mkfs.xfs /dev/zram0 > /dev/null
|
||||
fi
|
||||
TETHERED=0
|
||||
if [ "untethered" = "$(getarg confluent_imagemethod)" -o "uncompressed" = "$(getarg confluent_imagemethod)" ]; then
|
||||
if [ "untethered" = "$(getarg confluent_imagemethod)" ]; then
|
||||
mount -o discard /dev/zram0 /sysroot
|
||||
else
|
||||
mount -t tmpfs disklessroot /sysroot
|
||||
fi
|
||||
echo -en "Decrypting and extracting root filesystem: 0%\r"
|
||||
srcsz=$(du -sk /mnt/remote | awk '{print $1}')
|
||||
while [ -f /mnt/remoteimg/rootimg.sfs ]; do
|
||||
dstsz=$(du -sk /sysroot | awk '{print $1}')
|
||||
pct=$((dstsz * 100 / srcsz))
|
||||
if [ $pct -gt 99 ]; then
|
||||
pct=99
|
||||
fi
|
||||
echo -en "Decrypting and extracting root filesystem: $pct%\r"
|
||||
sleep 0.25
|
||||
done &
|
||||
cp -ax /mnt/remote/* /sysroot/
|
||||
umount /mnt/remote
|
||||
if [ -e /dev/mapper/cryptimg ]; then
|
||||
dmsetup remove cryptimg
|
||||
fi
|
||||
losetup -d $loopdev
|
||||
rm /mnt/remoteimg/rootimg.sfs
|
||||
umount /mnt/remoteimg
|
||||
wait
|
||||
echo -e "Decrypting and extracting root filesystem: 100%"
|
||||
else
|
||||
for srcmount in $(cat /tmp/mountparts.sh | awk '{print $3}'); do
|
||||
mkdir -p /mnt/overlay${srcmount}/upper /mnt/overlay${srcmount}/work
|
||||
mount -t overlay -o upperdir=/mnt/overlay${srcmount}/upper,workdir=/mnt/overlay${srcmount}/work,lowerdir=${srcmount} disklesspart /sysroot${srcmount#/mnt/remote}
|
||||
done
|
||||
TETHERED=1
|
||||
mount -o discard /dev/zram0 /mnt/overlay
|
||||
if [ ! -f /tmp/mountparts.sh ]; then
|
||||
mkdir -p /mnt/overlay/upper /mnt/overlay/work
|
||||
mount -t overlay -o upperdir=/mnt/overlay/upper,workdir=/mnt/overlay/work,lowerdir=/mnt/remote disklessroot /sysroot
|
||||
else
|
||||
for srcmount in $(cat /tmp/mountparts.sh | awk '{print $3}'); do
|
||||
mkdir -p /mnt/overlay${srcmount}/upper /mnt/overlay${srcmount}/work
|
||||
mount -t overlay -o upperdir=/mnt/overlay${srcmount}/upper,workdir=/mnt/overlay${srcmount}/work,lowerdir=${srcmount} disklesspart /sysroot${srcmount#/mnt/remote}
|
||||
done
|
||||
fi
|
||||
fi
|
||||
mkdir -p /sysroot/etc/ssh
|
||||
mkdir -p /sysroot/etc/confluent
|
||||
@@ -109,8 +142,10 @@ echo ' EnableSSHKeysign yes' >> $sshconf
|
||||
echo ' HostbasedKeyTypes *ed25519*' >> $sshconf
|
||||
curl -sf -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $(cat /etc/confluent/confluent.apikey)" https://$confluent_whost/confluent-api/self/nodelist > /sysroot/etc/ssh/shosts.equiv
|
||||
cp /sysroot/etc/ssh/shosts.equiv /sysroot/root/.shosts
|
||||
chmod 640 /sysroot/etc/ssh/*_key
|
||||
chroot /sysroot chgrp ssh_keys /etc/ssh/*_key
|
||||
if grep ^ssh_keys: /etc/group > /dev/null; then
|
||||
chmod 640 /sysroot/etc/ssh/*_key
|
||||
chroot /sysroot chgrp ssh_keys /etc/ssh/*_key
|
||||
fi
|
||||
cp /tls/*.pem /sysroot/etc/pki/ca-trust/source/anchors/
|
||||
chroot /sysroot/ update-ca-trust
|
||||
curl -sf https://$confluent_whost/confluent-public/os/$confluent_profile/scripts/onboot.service > /sysroot/etc/systemd/system/onboot.service
|
||||
@@ -131,9 +166,35 @@ mv /lib/firmware /lib/firmware-ramfs
|
||||
ln -s /sysroot/lib/firmware /lib/firmware
|
||||
rm -f /sysroot/etc/dracut.conf.d/diskless.conf # remove diskless dracut from runtime, to make kdump happier
|
||||
kill $(grep -l ^/usr/lib/systemd/systemd-udevd /proc/*/cmdline|cut -d/ -f 3)
|
||||
if grep debugssh /proc/cmdline >& /dev/null; then
|
||||
debugssh=1
|
||||
else
|
||||
debugssh=0
|
||||
fi
|
||||
if [ $TETHERED -eq 1 ]; then
|
||||
# In tethered mode, the double-caching is useful to get through tricky part of
|
||||
# onboot with confignet. After that, it's excessive cache usage.
|
||||
# Give the onboot script a hook to have us come in and enable directio to the
|
||||
# squashfs and drop the cache of the rootimg so far
|
||||
(
|
||||
sleep 86400 &
|
||||
ONBOOTPID=$!
|
||||
mkdir -p /run/confluent
|
||||
echo $ONBOOTPID > /run/confluent/onboot_sleep.pid
|
||||
wait $ONBOOTPID
|
||||
dd if=/mnt/remoteimg/rootimg.sfs iflag=nocache count=0 >& /dev/null
|
||||
if [ $debugssh -eq 0 ]; then
|
||||
rm -rf /lib/modules/$(uname -r) /lib/modules/$(uname -r)-ramfs /lib/firmware-ramfs /usr/lib64/libcrypto.so* /usr/lib64/systemd/ /kernel/ /usr/bin/ /usr/sbin/ /usr/libexec/
|
||||
fi
|
||||
) &
|
||||
while [ ! -f /run/confluent/onboot_sleep.pid ]; do
|
||||
sleep 0.1
|
||||
done
|
||||
elif [ $debugssh -eq 0 ]; then
|
||||
rm -rf /lib/modules/$(uname -r) /lib/modules/$(uname -r)-ramfs /lib/firmware-ramfs /usr/lib64/libcrypto.so* /usr/lib64/systemd/ /kernel/ /usr/bin/ /usr/sbin/ /usr/libexec/
|
||||
fi
|
||||
if grep debugssh /proc/cmdline >& /dev/null; then
|
||||
exec /opt/confluent/bin/start_root
|
||||
else
|
||||
rm -rf /lib/modules/$(uname -r) /lib/modules/$(uname -r)-ramfs /lib/firmware-ramfs /usr/lib64/libcrypto.so* /usr/lib64/systemd/ /kernel/ /usr/bin/ /usr/sbin/ /usr/libexec/
|
||||
exec /opt/confluent/bin/start_root -s # share mount namespace, keep kernel callbacks intact
|
||||
fi
|
||||
|
||||
@@ -62,5 +62,15 @@ run_remote_parts onboot.d
|
||||
# Induce execution of remote configuration, e.g. ansible plays in ansible/onboot.d/
|
||||
run_remote_config onboot.d
|
||||
|
||||
if [ -f /run/confluent/onboot_sleep.pid ]; then
|
||||
loopdev=$(losetup -j /mnt/remoteimg/rootimg.sfs|cut -d: -f 1)
|
||||
if [ -n "$loopdev" ]; then
|
||||
losetup "$loopdev" --direct-io=on
|
||||
fi
|
||||
sleeppid=$(cat /run/confluent/onboot_sleep.pid)
|
||||
kill "$sleeppid"
|
||||
rm -f /run/confluent/onboot_sleep.pid
|
||||
fi
|
||||
|
||||
#curl -X POST -d 'status: booted' -H "CONFLUENT_NODENAME: $nodename" -H "CONFLUENT_APIKEY: $confluent_apikey" https://$confluent_mgr/confluent-api/self/updatestatus
|
||||
kill $logshowpid
|
||||
|
||||
@@ -56,7 +56,7 @@ cat /tls/*.pem > /etc/pki/tls/certs/ca-bundle.crt
|
||||
TRIES=0
|
||||
touch /etc/confluent/confluent.info
|
||||
TRIES=5
|
||||
echo -n "Waitiing for disks..."
|
||||
echo -n "Waiting for disks..."
|
||||
while [ ! -e /dev/disk/by-label ] && [ $TRIES -gt 0 ]; do
|
||||
sleep 1
|
||||
TRIES=$((TRIES - 1))
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <dirent.h>
|
||||
|
||||
#define COM1 0x3f8
|
||||
#define COM2 0x2f8
|
||||
@@ -19,6 +20,137 @@
|
||||
#define SPEED57600 6
|
||||
#define SPEED115200 7
|
||||
|
||||
typedef struct {
|
||||
char devnode[32];
|
||||
speed_t speed;
|
||||
int valid;
|
||||
} serial_port_t;
|
||||
|
||||
serial_port_t process_spcr() {
|
||||
serial_port_t result = {0};
|
||||
char buff[128];
|
||||
int fd;
|
||||
uint64_t address;
|
||||
int currspeed;
|
||||
|
||||
result.valid = 0;
|
||||
|
||||
fd = open("/sys/firmware/acpi/tables/SPCR", O_RDONLY);
|
||||
if (fd < 0) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (read(fd, buff, 80) < 80) {
|
||||
close(fd);
|
||||
return result;
|
||||
}
|
||||
close(fd);
|
||||
|
||||
if (buff[8] != 2) return result; // revision 2
|
||||
if (buff[36] != 0) return result; // 16550 only
|
||||
if (buff[40] != 1) return result; // IO only
|
||||
|
||||
address = *(uint64_t *)(buff + 44);
|
||||
currspeed = buff[58];
|
||||
|
||||
if (address == COM1) {
|
||||
strncpy(result.devnode, "/dev/ttyS0", sizeof(result.devnode));
|
||||
} else if (address == COM2) {
|
||||
strncpy(result.devnode, "/dev/ttyS1", sizeof(result.devnode));
|
||||
} else if (address == COM3) {
|
||||
strncpy(result.devnode, "/dev/ttyS2", sizeof(result.devnode));
|
||||
} else if (address == COM4) {
|
||||
strncpy(result.devnode, "/dev/ttyS3", sizeof(result.devnode));
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (currspeed == SPEED9600) {
|
||||
result.speed = B9600;
|
||||
} else if (currspeed == SPEED19200) {
|
||||
result.speed = B19200;
|
||||
} else if (currspeed == SPEED57600) {
|
||||
result.speed = B57600;
|
||||
} else if (currspeed == SPEED115200) {
|
||||
result.speed = B115200;
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
|
||||
result.valid = 1;
|
||||
return result;
|
||||
}
|
||||
|
||||
serial_port_t identify_by_sys_vendor() {
|
||||
serial_port_t result = {0};
|
||||
char buff[128];
|
||||
FILE *f;
|
||||
|
||||
f = fopen("/sys/devices/virtual/dmi/id/sys_vendor", "r");
|
||||
if (f) {
|
||||
if (fgets(buff, sizeof(buff), f)) {
|
||||
if (strstr(buff, "Supermicro")) {
|
||||
strncpy(result.devnode, "/dev/ttyS1", sizeof(result.devnode));
|
||||
result.speed = B115200;
|
||||
result.valid = 1;
|
||||
}
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
serial_port_t search_serial_ports() {
|
||||
serial_port_t result = {0};
|
||||
DIR *dir;
|
||||
struct dirent *entry;
|
||||
int fd;
|
||||
int status;
|
||||
int numfound= 0;
|
||||
int numpossible = 0;
|
||||
|
||||
dir = opendir("/dev");
|
||||
if (!dir) {
|
||||
return result;
|
||||
}
|
||||
|
||||
while ((entry = readdir(dir)) != NULL) {
|
||||
if (strncmp(entry->d_name, "ttyS", 4) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
char devpath[64];
|
||||
snprintf(devpath, sizeof(devpath), "/dev/%s", entry->d_name);
|
||||
|
||||
fd = open(devpath, O_RDWR | O_NOCTTY | O_NONBLOCK);
|
||||
if (fd < 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ioctl(fd, TIOCMGET, &status) == 0) {
|
||||
numpossible++;
|
||||
if (numfound < 1) {
|
||||
strncpy(result.devnode, devpath, sizeof(result.devnode));
|
||||
result.speed = B115200;
|
||||
}
|
||||
if (status & TIOCM_CAR) {
|
||||
strncpy(result.devnode, devpath, sizeof(result.devnode));
|
||||
numfound++;
|
||||
result.speed = B115200;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
close(fd);
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
if (numfound == 1 || numpossible == 1) {
|
||||
result.valid = 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
struct termios tty;
|
||||
struct termios tty2;
|
||||
@@ -36,46 +168,38 @@ int main(int argc, char* argv[]) {
|
||||
char* offset;
|
||||
uint64_t address;
|
||||
bufflen = 0;
|
||||
tmpi = open("/sys/firmware/acpi/tables/SPCR", O_RDONLY);
|
||||
if (tmpi < 0) {
|
||||
exit(0);
|
||||
}
|
||||
if (read(tmpi, buff, 80) < 80) {
|
||||
exit(0);
|
||||
}
|
||||
close(tmpi);
|
||||
if (buff[8] != 2) exit(0); //revision 2
|
||||
if (buff[36] != 0) exit(0); //16550 only
|
||||
if (buff[40] != 1) exit(0); //IO only
|
||||
address = *(uint64_t *)(buff + 44);
|
||||
currspeed = buff[58];
|
||||
offset = buff + 10;
|
||||
if (address == COM1) {
|
||||
strncpy(buff, "/dev/ttyS0", 128);
|
||||
} else if (address == COM2) {
|
||||
strncpy(buff, "/dev/ttyS1", 128);
|
||||
} else if (address == COM3) {
|
||||
strncpy(buff, "/dev/ttyS2", 128);
|
||||
} else if (address == COM4) {
|
||||
strncpy(buff, "/dev/ttyS3", 128);
|
||||
} else {
|
||||
#ifndef __x86_64__
|
||||
// Only x86 needs autoconsole, other platforms have reasonable default serial console
|
||||
exit(0);
|
||||
#endif
|
||||
serial_port_t spcr = process_spcr();
|
||||
if (!spcr.valid) {
|
||||
spcr = search_serial_ports();
|
||||
}
|
||||
if (!spcr.valid) {
|
||||
spcr = identify_by_sys_vendor();
|
||||
}
|
||||
if (!spcr.valid) {
|
||||
exit(0);
|
||||
}
|
||||
strncpy(buff, spcr.devnode, sizeof(buff));
|
||||
offset = strchr(buff, 0);
|
||||
currspeed = spcr.speed;
|
||||
ttyf = open(buff, O_RDWR | O_NOCTTY);
|
||||
if (ttyf < 0) {
|
||||
fprintf(stderr, "Unable to open tty\n");
|
||||
exit(1);
|
||||
}
|
||||
if (currspeed == SPEED9600) {
|
||||
if (currspeed == B9600) {
|
||||
cspeed = B9600;
|
||||
strncpy(offset, ",9600", 6);
|
||||
} else if (currspeed == SPEED19200) {
|
||||
} else if (currspeed == B19200) {
|
||||
cspeed = B19200;
|
||||
strncpy(offset, ",19200", 7);
|
||||
} else if (currspeed == SPEED57600) {
|
||||
} else if (currspeed == B57600) {
|
||||
cspeed = B57600;
|
||||
strncpy(offset, ",57600", 7);
|
||||
} else if (currspeed == SPEED115200) {
|
||||
} else if (currspeed == B115200) {
|
||||
cspeed = B115200;
|
||||
strncpy(offset, ",115200", 8);
|
||||
} else {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/python2
|
||||
#!/usr/bin/python3
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import errno
|
||||
import os
|
||||
import pwd
|
||||
@@ -14,9 +15,9 @@ if path.startswith('/opt'):
|
||||
# if installed into system path, do not muck with things
|
||||
sys.path.append(path)
|
||||
|
||||
import confluent.client as client
|
||||
import confluent.asynclient as client
|
||||
import confluent.sortutil as sortutil
|
||||
import confluent.tlvdata as tlvdata
|
||||
import confluent.asynctlvdata as tlvdata
|
||||
|
||||
try:
|
||||
input = raw_input
|
||||
@@ -52,40 +53,47 @@ def make_certificate():
|
||||
os.umask(umask)
|
||||
|
||||
|
||||
def show_invitation(name, nonvoting=False):
|
||||
async def show_invitation(name, nonvoting=False):
|
||||
if not os.path.exists('/etc/confluent/srvcert.pem'):
|
||||
make_certificate()
|
||||
s = client.Command().connection
|
||||
clicmd = client.Command()
|
||||
await clicmd.ensure_connected()
|
||||
s = clicmd.connection
|
||||
role = 'nonvoting' if nonvoting else None
|
||||
tlvdata.send(s, {'collective': {'operation': 'invite', 'name': name, 'role': role}})
|
||||
invite = tlvdata.recv(s)['collective']
|
||||
await tlvdata.send(s, {'collective': {'operation': 'invite', 'name': name, 'role': role}})
|
||||
invite = await tlvdata.recv(s)
|
||||
invite = invite['collective']
|
||||
if 'error' in invite:
|
||||
sys.stderr.write(invite['error'] + '\n')
|
||||
return
|
||||
print('{0}'.format(invite['invitation']))
|
||||
|
||||
|
||||
def join_collective(server, invitation):
|
||||
async def join_collective(server, invitation):
|
||||
if not os.path.exists('/etc/confluent/srvcert.pem'):
|
||||
make_certificate()
|
||||
s = client.Command().connection
|
||||
clicmd = client.Command()
|
||||
await clicmd.ensure_connected()
|
||||
s = clicmd.connection
|
||||
while not invitation:
|
||||
invitation = input('Paste the invitation here: ')
|
||||
tlvdata.send(s, {'collective': {'operation': 'join',
|
||||
await tlvdata.send(s, {'collective': {'operation': 'join',
|
||||
'invitation': invitation,
|
||||
'server': server}})
|
||||
res = tlvdata.recv(s)
|
||||
res = await tlvdata.recv(s)
|
||||
res = res.get('collective',
|
||||
{'status': 'Unknown response: ' + repr(res)})
|
||||
print(res.get('status', res.get('error', repr(res))))
|
||||
if 'error' in res:
|
||||
sys.exit(1)
|
||||
|
||||
def delete_member(name):
|
||||
s = client.Command().connection
|
||||
tlvdata.send(s, {'collective': {'operation': 'delete',
|
||||
async def delete_member(name):
|
||||
clicmd = client.Command()
|
||||
await clicmd.ensure_connected()
|
||||
s = clicmd.connection
|
||||
await tlvdata.send(s, {'collective': {'operation': 'delete',
|
||||
'member': name}})
|
||||
res = tlvdata.recv(s)
|
||||
res = await tlvdata.recv(s)
|
||||
res = res.get('collective',
|
||||
{'status': 'Unknown response: ' + repr(res)})
|
||||
print(res.get('status', res.get('error', repr(res))))
|
||||
@@ -93,10 +101,12 @@ def delete_member(name):
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def show_collective():
|
||||
s = client.Command().connection
|
||||
tlvdata.send(s, {'collective': {'operation': 'show'}})
|
||||
res = tlvdata.recv(s)
|
||||
async def show_collective():
|
||||
clicmd = client.Command()
|
||||
await clicmd.ensure_connected()
|
||||
s = clicmd.connection
|
||||
await tlvdata.send(s, {'collective': {'operation': 'show'}})
|
||||
res = await tlvdata.recv(s)
|
||||
if 'error' in res:
|
||||
print(res['error'])
|
||||
return
|
||||
@@ -121,7 +131,7 @@ def show_collective():
|
||||
else:
|
||||
print('Run collective show on leader for more data')
|
||||
|
||||
def main():
|
||||
async def main():
|
||||
a = argparse.ArgumentParser(description='Confluent server utility')
|
||||
sp = a.add_subparsers(dest='command')
|
||||
gc = sp.add_parser('gencert', help='Generate Confluent Certificates for '
|
||||
@@ -143,13 +153,13 @@ def main():
|
||||
if cmdset.command == 'gencert':
|
||||
make_certificate()
|
||||
elif cmdset.command == 'invite':
|
||||
show_invitation(cmdset.name, cmdset.n)
|
||||
await show_invitation(cmdset.name, cmdset.n)
|
||||
elif cmdset.command == 'join':
|
||||
join_collective(cmdset.server, cmdset.i)
|
||||
await join_collective(cmdset.server, cmdset.i)
|
||||
elif cmdset.command == 'show':
|
||||
show_collective()
|
||||
await show_collective()
|
||||
elif cmdset.command == 'delete':
|
||||
delete_member(cmdset.name)
|
||||
await delete_member(cmdset.name)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
asyncio.run(main())
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/python2
|
||||
#!/usr/bin/python3
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2014 IBM Corporation
|
||||
@@ -17,6 +17,9 @@
|
||||
|
||||
import sys
|
||||
import os
|
||||
|
||||
|
||||
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
@@ -30,9 +33,15 @@ import confluent.main
|
||||
#p.enable()
|
||||
#try:
|
||||
import multiprocessing
|
||||
if __name__ == '__main__':
|
||||
multiprocessing.freeze_support()
|
||||
|
||||
def main():
|
||||
confluent.main.run(sys.argv)
|
||||
if __name__ == '__main__':
|
||||
#multiprocessing.freeze_support()
|
||||
#asyncio.get_event_loop().run_until_complete(main())
|
||||
main()
|
||||
#gt = spawn_for_awaitable(confluent.main.run(sys.argv))
|
||||
#gt.wait()
|
||||
#except:
|
||||
# pass
|
||||
#p.disable()
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import os
|
||||
import socket
|
||||
import glob
|
||||
@@ -16,16 +17,13 @@ import confluent.certutil as certutil
|
||||
import confluent.client as client
|
||||
import confluent.config.configmanager as configmanager
|
||||
import confluent.netutil as netutil
|
||||
import eventlet.green.subprocess as subprocess
|
||||
import tempfile
|
||||
import shutil
|
||||
import eventlet.green.socket as socket
|
||||
import eventlet
|
||||
import greenlet
|
||||
import pwd
|
||||
import signal
|
||||
import confluent.collective.manager as collective
|
||||
import confluent.noderange as noderange
|
||||
import subprocess
|
||||
|
||||
def check_sysctl_tuning():
|
||||
with open('/proc/sys/net/ipv4/tcp_sack', 'r') as f:
|
||||
@@ -76,7 +74,7 @@ def webserver_listening():
|
||||
return False
|
||||
|
||||
|
||||
def certificates_missing_ips(conn):
|
||||
async def certificates_missing_ips(conn):
|
||||
# check if the tls can verify by the right CAs, then further
|
||||
# check if all ip addresses are in the certificate offered
|
||||
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
|
||||
@@ -86,9 +84,8 @@ def certificates_missing_ips(conn):
|
||||
sock = ctx.wrap_socket(conn)
|
||||
crt = sock.getpeercert()
|
||||
sans = crt.get('subjectAltName', [])
|
||||
ips = certutil.get_ip_addresses()
|
||||
missing_ips = []
|
||||
for ip in ips:
|
||||
async for ip in certutil.get_ip_addresses():
|
||||
for san in sans:
|
||||
field, val = san
|
||||
if val[-1] == '\n':
|
||||
@@ -123,7 +120,6 @@ def web_api_works():
|
||||
|
||||
def nics_missing_ipv6():
|
||||
# check for ability to create AF_INET6, for kernel disabled ipv6
|
||||
a = socket.socket(socket.AF_INET6)
|
||||
ipaddrs = subprocess.check_output(['ip', '-br', 'a']).split(b'\n')
|
||||
missingnics = []
|
||||
for line in ipaddrs:
|
||||
@@ -172,15 +168,69 @@ def uuid_matches():
|
||||
dbuuid = configmanager.get_global('confluent_uuid')
|
||||
return dbuuid == fsuuid
|
||||
|
||||
def lookup_node(node):
|
||||
async def lookup_node(node):
|
||||
try:
|
||||
return socket.getaddrinfo(node, 0)
|
||||
except greenlet.GreenletExit:
|
||||
return None
|
||||
cloop = asyncio.get_event_loop()
|
||||
return await cloop.getaddrinfo(node, 0)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
async def check_ssh_to_node(targsships):
|
||||
sshutil.ready_keys = {}
|
||||
sshutil.agent_pid = None
|
||||
cuser = pwd.getpwnam('confluent')
|
||||
os.setgid(cuser.pw_gid)
|
||||
os.setuid(cuser.pw_uid)
|
||||
await sshutil.prep_ssh_key('/etc/confluent/ssh/automation')
|
||||
for targ in targsships:
|
||||
srun = subprocess.run(
|
||||
['ssh', '-Tn', '-o', 'BatchMode=yes', '-l', 'root',
|
||||
'-o', 'StrictHostKeyChecking=yes', targ, 'true'],
|
||||
stdin=subprocess.DEVNULL, stderr=subprocess.PIPE)
|
||||
if srun.returncode == 0:
|
||||
print(f'Confluent automation access to {targ} seems OK')
|
||||
else:
|
||||
if b'Host key verification failed' in srun.stderr:
|
||||
emprint(f'Confluent ssh unable to verify host key for {targ}, check /etc/ssh/ssh_known_hosts. (Example resolution: osdeploy initialize -k)')
|
||||
elif b'ermission denied' in srun.stderr:
|
||||
emprint(f'Confluent user unable to ssh in to {targ}, check /root/.ssh/authorized_keys on the target system versus /etc/confluent/ssh/automation.pub (Example resolution: osdeploy initialize -a)')
|
||||
else:
|
||||
emprint('Unknown error attempting confluent automation ssh:')
|
||||
sys.stderr.buffer.write(srun.stderr)
|
||||
if sshutil.agent_pid:
|
||||
os.kill(int(sshutil.agent_pid), signal.SIGTERM)
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == '__main__':
|
||||
async def check_confluent_ssh():
|
||||
sshutil.ready_keys = {}
|
||||
sshutil.agent_pid = None
|
||||
cuser = pwd.getpwnam('confluent')
|
||||
os.setgid(cuser.pw_gid)
|
||||
os.setuid(cuser.pw_uid)
|
||||
fprint('Checking SSH Certificate authority: ')
|
||||
try:
|
||||
await sshutil.prep_ssh_key('/etc/confluent/ssh/ca')
|
||||
print('OK')
|
||||
except Exception as e:
|
||||
if type(e).__name__ == 'CalledProcessError' and 'UNPROTECTED' in e.stderr.decode():
|
||||
emprint('Permissions incorrect on /etc/confluent/ssh/ca (Example resolution: chmod 600 /etc/confluent/ssh/ca)')
|
||||
else:
|
||||
emprint('Failed to load SSH authority key, deployed servers will not have host certificates for known_hosts and users may be unable to ssh between nodes without a password (Example resolution: osdeploy initialize -s)')
|
||||
fprint('Checking confluent SSH automation key: ')
|
||||
try:
|
||||
await sshutil.prep_ssh_key('/etc/confluent/ssh/automation')
|
||||
print('OK')
|
||||
except Exception as e:
|
||||
if type(e).__name__ == 'CalledProcessError' and 'UNPROTECTED' in e.stderr.decode():
|
||||
emprint('Permissions incorrect on /etc/confluent/ssh/automation (Example resolution: chmod 600 /etc/confluent/ssh/automation)')
|
||||
else:
|
||||
emprint('Failed to load confluent automation key, syncfiles and profile ansible plays will not work (Example resolution: osdeploy initialize -a)')
|
||||
if sshutil.agent_pid:
|
||||
os.kill(int(sshutil.agent_pid), signal.SIGTERM)
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
async def main():
|
||||
ap = argparse.ArgumentParser(description='Run configuration checks for a system running confluent service')
|
||||
ap.add_argument('-n', '--node', help='A node name to run node specific checks against')
|
||||
ap.add_argument('-a', '--automation', help='Do checks against a deployed node for automation and syncfiles function', action='store_true')
|
||||
@@ -203,7 +253,7 @@ if __name__ == '__main__':
|
||||
if conn:
|
||||
print('Running')
|
||||
fprint('Web Certificate: ')
|
||||
cert = certificates_missing_ips(conn)
|
||||
cert = await certificates_missing_ips(conn)
|
||||
if cert:
|
||||
cert = ', '.join(cert)
|
||||
emprint('Addresses missing from certificate: {0} (Example resolution: osdeploy initialize -t)'.format(cert))
|
||||
@@ -257,37 +307,8 @@ if __name__ == '__main__':
|
||||
emprint('No matching public key found for root user (Example resolution: osdeploy initialize -u)')
|
||||
else:
|
||||
emprint('No trusted ssh keys for root user, passwordless SSH from managers to nodes may not work (Example resolution: osdeploy initialize -u)')
|
||||
if sshutil.sshver() > 7.6:
|
||||
child = os.fork()
|
||||
if child > 0:
|
||||
pid, extcode = os.waitpid(child, 0)
|
||||
else:
|
||||
sshutil.ready_keys = {}
|
||||
sshutil.agent_pid = None
|
||||
cuser = pwd.getpwnam('confluent')
|
||||
os.setgid(cuser.pw_gid)
|
||||
os.setuid(cuser.pw_uid)
|
||||
fprint('Checking SSH Certificate authority: ')
|
||||
try:
|
||||
sshutil.prep_ssh_key('/etc/confluent/ssh/ca')
|
||||
print('OK')
|
||||
except Exception as e:
|
||||
if type(e).__name__ == 'CalledProcessError' and 'UNPROTECTED' in e.stderr.decode():
|
||||
emprint('Permissions incorrect on /etc/confluent/ssh/ca (Example resolution: chmod 600 /etc/confluent/ssh/ca)')
|
||||
else:
|
||||
emprint('Failed to load SSH authority key, deployed servers will not have host certificates for known_hosts and users may be unable to ssh between nodes without a password (Example resolution: osdeploy initialize -s)')
|
||||
fprint('Checking confluent SSH automation key: ')
|
||||
try:
|
||||
sshutil.prep_ssh_key('/etc/confluent/ssh/automation')
|
||||
print('OK')
|
||||
except Exception as e:
|
||||
if type(e).__name__ == 'CalledProcessError' and 'UNPROTECTED' in e.stderr.decode():
|
||||
emprint('Permissions incorrect on /etc/confluent/ssh/automation (Example resolution: chmod 600 /etc/confluent/ssh/automation)')
|
||||
else:
|
||||
emprint('Failed to load confluent automation key, syncfiles and profile ansible plays will not work (Example resolution: osdeploy initialize -a)')
|
||||
if sshutil.agent_pid:
|
||||
os.kill(int(sshutil.agent_pid), signal.SIGTERM)
|
||||
sys.exit(0)
|
||||
if await sshutil.sshver() > 7.6:
|
||||
subprocess.run([sys.executable, __file__, '--check-ssh'])
|
||||
fprint('Checking for blocked insecure boot: ')
|
||||
if insecure_boot_attempts():
|
||||
emprint('Some nodes are attempting network boot using PXE or HTTP boot, but the node is not configured to allow this (Example resolution: nodegroupattrib everything deployment.useinsecureprotocols=firmware)')
|
||||
@@ -371,7 +392,7 @@ if __name__ == '__main__':
|
||||
for nic in glob.glob("/sys/class/net/*/ifindex"):
|
||||
idx = int(open(nic, "r").read())
|
||||
nicname = nic.split('/')[-2]
|
||||
ncfg = netutil.get_nic_config(cfg, args.node, ifidx=idx)
|
||||
ncfg = await netutil.get_nic_config(cfg, args.node, ifidx=idx)
|
||||
if ncfg['ipv4_address']:
|
||||
targsships.append(ncfg['ipv4_address'])
|
||||
if ncfg['ipv4_address'] or ncfg['ipv4_method'] == 'dhcp':
|
||||
@@ -392,55 +413,30 @@ if __name__ == '__main__':
|
||||
if allok:
|
||||
print(f'No issues detected with attributes of {args.node}')
|
||||
fprint("Checking name resolution: ")
|
||||
lk = eventlet.spawn(lookup_node, args.node)
|
||||
eventlet.sleep(0.1)
|
||||
tries = 5
|
||||
while not lk.dead and tries > 0:
|
||||
eventlet.sleep(1)
|
||||
tries -= 1
|
||||
deaddns = False
|
||||
if not tries:
|
||||
try:
|
||||
result = await asyncio.wait_for(lookup_node(args.node), timeout=5)
|
||||
except asyncio.exceptions.TimeoutError:
|
||||
emprint('Name resolution takes too long, check state of /etc/resolv.conf and indicated nameservers, this can produce failure to netboot or failure to commence installation')
|
||||
lk.kill()
|
||||
deaddns = True
|
||||
result = lk.wait()
|
||||
if not result and not deaddns:
|
||||
emprint('Name resolution failed for node, it is normally a good idea for the node name to resolve to an IP')
|
||||
if result:
|
||||
print("OK")
|
||||
if args.automation:
|
||||
print(f'Checking confluent automation access to {args.node}...')
|
||||
child = os.fork()
|
||||
if child > 0:
|
||||
pid, extcode = os.waitpid(child, 0)
|
||||
else:
|
||||
sshutil.ready_keys = {}
|
||||
sshutil.agent_pid = None
|
||||
cuser = pwd.getpwnam('confluent')
|
||||
os.setgid(cuser.pw_gid)
|
||||
os.setuid(cuser.pw_uid)
|
||||
sshutil.prep_ssh_key('/etc/confluent/ssh/automation')
|
||||
for targ in targsships:
|
||||
srun = subprocess.run(
|
||||
['ssh', '-Tn', '-o', 'BatchMode=yes', '-l', 'root',
|
||||
'-o', 'StrictHostKeyChecking=yes', targ, 'true'],
|
||||
stdin=subprocess.DEVNULL, stderr=subprocess.PIPE)
|
||||
if srun.returncode == 0:
|
||||
print(f'Confluent automation access to {targ} seems OK')
|
||||
else:
|
||||
if b'Host key verification failed' in srun.stderr:
|
||||
emprint(f'Confluent ssh unable to verify host key for {targ}, check /etc/ssh/ssh_known_hosts. (Example resolution: osdeploy initialize -k)')
|
||||
elif b'ermission denied' in srun.stderr:
|
||||
emprint(f'Confluent user unable to ssh in to {targ}, check /root/.ssh/authorized_keys on the target system versus /etc/confluent/ssh/automation.pub (Example resolution: osdeploy initialize -a)')
|
||||
else:
|
||||
emprint('Unknown error attempting confluent automation ssh:')
|
||||
sys.stderr.buffer.write(srun.stderr)
|
||||
if sshutil.agent_pid:
|
||||
os.kill(int(sshutil.agent_pid), signal.SIGTERM)
|
||||
sys.exit(0)
|
||||
subprocess.run([sys.executable, __file__, '--check-ssh'] + targsships)
|
||||
else:
|
||||
print("Skipping node checks, no node specified (Example: confluent_selfcheck -n n1)")
|
||||
# possible checks:
|
||||
# arping on the node, check for dupes/against nodeinventory?
|
||||
# arping -D for mgt own ip addresses? check for dupes, also check for bleed through from one nic to another
|
||||
# iterate through profiles, use mtools to extract site initramfs, check if outdated
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) >= 2 and sys.argv[1] == '--check-ssh':
|
||||
if len(sys.argv) >= 3:
|
||||
asyncio.run(check_ssh_to_node(sys.argv[2:]))
|
||||
else:
|
||||
asyncio.run(check_confluent_ssh())
|
||||
else:
|
||||
asyncio.run(main())
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
# limitations under the License.
|
||||
|
||||
|
||||
import asyncio
|
||||
import getpass
|
||||
import optparse
|
||||
import sys
|
||||
@@ -79,12 +80,13 @@ if args[0] in ('restore', 'merge'):
|
||||
# Use the format parameter based on the --yaml option
|
||||
format = 'yaml' if options.yaml else 'json'
|
||||
|
||||
cfm.restore_db_from_directory(
|
||||
dp = cfm.restore_db_from_directory(
|
||||
dumpdir, password,
|
||||
merge="skip" if args[0] == 'merge' else False,
|
||||
skipped=skipped,
|
||||
format=format)
|
||||
|
||||
asyncio.get_event_loop().run_until_complete(dp)
|
||||
if skipped['nodes']:
|
||||
skippedn = ','.join(skipped['nodes'])
|
||||
print('The following nodes were skipped during merge: '
|
||||
@@ -93,7 +95,6 @@ if args[0] in ('restore', 'merge'):
|
||||
skippedn = ','.join(skipped['nodegroups'])
|
||||
print('The following node groups were skipped during merge: '
|
||||
'{}'.format(skippedn))
|
||||
|
||||
cfm.statelessmode = False
|
||||
cfm.ConfigManager.wait_for_sync(True)
|
||||
if owner != 0:
|
||||
@@ -126,8 +127,8 @@ elif args[0] == 'dump':
|
||||
|
||||
# Use the format parameter based on the --yaml option
|
||||
format = 'yaml' if options.yaml else 'json'
|
||||
cfm.dump_db_to_directory(dumpdir, password, options.redact,
|
||||
options.skipkeys, format=format)
|
||||
|
||||
dp = cfm.dump_db_to_directory(dumpdir, password, options.redact,
|
||||
options.skipkeys, format=format)
|
||||
asyncio.get_event_loop().run_until_complete(dp)
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
__author__ = 'jjohnson2,bfinley'
|
||||
|
||||
import argparse
|
||||
import asyncio
|
||||
import glob
|
||||
import os
|
||||
import os.path
|
||||
@@ -16,10 +17,9 @@ path = os.path.realpath(os.path.join(path, '..', 'lib', 'python'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
import confluent.collective.manager as collective
|
||||
import eventlet.green.subprocess as subprocess
|
||||
import confluent.selfservice as selfservice
|
||||
import confluent.util as util
|
||||
import confluent.client as client
|
||||
import confluent.asynclient as client
|
||||
import confluent.sshutil as sshutil
|
||||
import confluent.certutil as certutil
|
||||
import confluent.netutil as netutil
|
||||
@@ -36,7 +36,7 @@ def emprint(txt):
|
||||
print(txt)
|
||||
|
||||
fnamechars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789.^'
|
||||
def main(args):
|
||||
async def main(args):
|
||||
ap = argparse.ArgumentParser(description='Manage OS deployment resources')
|
||||
sp = ap.add_subparsers(dest='command')
|
||||
wiz = sp.add_parser('initialize', help='Do OS deployment preparation')
|
||||
@@ -66,15 +66,15 @@ def main(args):
|
||||
if cmdset.command == 'list':
|
||||
return oslist()
|
||||
if cmdset.command == 'import':
|
||||
return osimport(cmdset.imagefile, custname=cmdset.n)
|
||||
return await osimport(cmdset.imagefile, custname=cmdset.n)
|
||||
if cmdset.command == 'importcheck':
|
||||
return osimport(cmdset.imagefile, checkonly=True)
|
||||
if cmdset.command == 'initialize':
|
||||
return initialize(cmdset)
|
||||
return await initialize(cmdset)
|
||||
if cmdset.command == 'updateboot':
|
||||
return updateboot(cmdset.profile)
|
||||
return await updateboot(cmdset.profile)
|
||||
if cmdset.command == 'rebase':
|
||||
return rebase(cmdset.profile)
|
||||
return await rebase(cmdset.profile)
|
||||
ap.print_help()
|
||||
|
||||
def symlinkp(src, trg):
|
||||
@@ -160,7 +160,7 @@ def init_confluent_myname():
|
||||
os._exit(0)
|
||||
|
||||
|
||||
def local_node_trust_setup():
|
||||
async def local_node_trust_setup():
|
||||
init_confluent_myname()
|
||||
allnodes, domain = selfservice.get_cluster_list()
|
||||
myname = collective.get_myname()
|
||||
@@ -173,7 +173,7 @@ def local_node_trust_setup():
|
||||
myprincipals.add(myshortname)
|
||||
if domain:
|
||||
myprincipals.add('{0}.{1}'.format(myshortname, domain))
|
||||
for addr in netutil.get_my_addresses():
|
||||
for addr in await netutil.get_my_addresses():
|
||||
addr = socket.inet_ntop(addr[0], addr[1])
|
||||
myprincipals.add(addr)
|
||||
for pubkey in glob.glob('/etc/ssh/ssh_host_*_key.pub'):
|
||||
@@ -186,7 +186,12 @@ def local_node_trust_setup():
|
||||
with open(certfile, 'w') as certout:
|
||||
certout.write(cert)
|
||||
if restorecon:
|
||||
subprocess.check_call(['/usr/sbin/restorecon', certfile])
|
||||
rcproc = await asyncio.create_subprocess_exec(
|
||||
'/usr/sbin/restorecon', certfile)
|
||||
rc = await rcproc.wait()
|
||||
if rc != 0:
|
||||
raise Exception("Failure to restorecon")
|
||||
#subprocess.check_call(['/usr/sbin/restorecon', certfile])
|
||||
with open('/etc/ssh/sshd_config', 'r') as sshconf:
|
||||
currconfig = sshconf.read().split('\n')
|
||||
for conline in currconfig:
|
||||
@@ -204,12 +209,17 @@ def local_node_trust_setup():
|
||||
for node in util.natural_sort(allnodes):
|
||||
equivout.write(node + '\n')
|
||||
if restorecon:
|
||||
subprocess.check_call(
|
||||
['/usr/sbin/restorecon',
|
||||
'/etc/ssh/shosts.equiv', '/root/.shosts'])
|
||||
rcproc = await asyncio.create_subprocess_exec(
|
||||
'/usr/sbin/restorecon', '/etc/ssh/shosts.equiv', '/root/.shosts')
|
||||
rc = await rcproc.wait()
|
||||
if rc != 0:
|
||||
raise Exception('Unable to restorecon')
|
||||
#subprocess.check_call(
|
||||
# ['/usr/sbin/restorecon',
|
||||
# '/etc/ssh/shosts.equiv', '/root/.shosts'])
|
||||
|
||||
|
||||
def install_tftp_content():
|
||||
async def install_tftp_content():
|
||||
tftplocation = None
|
||||
candidates = ('/tftpboot', '/var/lib/tftpboot', '/srv/tftpboot', '/srv/tftp')
|
||||
for cand in candidates:
|
||||
@@ -224,7 +234,11 @@ def install_tftp_content():
|
||||
emprint('/tftpboot is detected as tftp directory, will not try to automatically enable tftp, as it is presumed to be externally managed')
|
||||
else:
|
||||
try:
|
||||
subprocess.check_call(['systemctl', 'enable', 'tftp.socket', '--now'])
|
||||
tfproc = await asyncio.create_subprocess_exec('systemctl', 'enable', 'tftp.socket', '--now')
|
||||
rc = await tfproc.wait()
|
||||
if rc != 0:
|
||||
raise Exception('{0}'.format(rc))
|
||||
#subprocess.check_call(['systemctl', 'enable', 'tftp.socket', '--now'])
|
||||
print('TFTP service is enabled and running')
|
||||
except Exception:
|
||||
emprint('Unable to automatically enable and start tftp.socket, tftp server may already be running outside of systemd control')
|
||||
@@ -251,7 +265,7 @@ def install_tftp_content():
|
||||
|
||||
|
||||
|
||||
def initialize(cmdset):
|
||||
async def initialize(cmdset):
|
||||
if os.getuid() != 0:
|
||||
sys.stderr.write('This command must run as root user\n')
|
||||
sys.exit(1)
|
||||
@@ -300,20 +314,28 @@ def initialize(cmdset):
|
||||
'passphrase protected ssh key easier.\n')
|
||||
sys.exit(1)
|
||||
init_confluent_myname()
|
||||
sshutil.initialize_root_key(False)
|
||||
await sshutil.initialize_root_key(False)
|
||||
if cmdset.t:
|
||||
didsomething = True
|
||||
init_confluent_myname()
|
||||
certutil.create_certificate()
|
||||
await certutil.create_certificate()
|
||||
if os.path.exists('/usr/lib/systemd/system/httpd.service'):
|
||||
try:
|
||||
subprocess.check_call(['systemctl', 'try-restart', 'httpd'])
|
||||
hrproc = await asyncio.create_subprocess_exec('systemctl', 'try-restart', 'httpd')
|
||||
rc = await hrproc.wait()
|
||||
if rc != 0:
|
||||
raise Exception('Failed restarting HTTP')
|
||||
#subprocess.check_call(['systemctl', 'try-restart', 'httpd'])
|
||||
print('HTTP server has been restarted if it was running')
|
||||
except Exception:
|
||||
emprint('New HTTPS certificates generated, restart the web server manually')
|
||||
elif os.path.exists('/usr/lib/systemd/system/apache2.service'):
|
||||
try:
|
||||
subprocess.check_call(['systemctl', 'try-restart', 'apache2'])
|
||||
hrproc = await asyncio.create_subprocess_exec('systemctl', 'try-restart', 'apache2')
|
||||
rc = await hrproc.wait()
|
||||
if rc != 0:
|
||||
raise Exception('Failed restarting HTTP')
|
||||
# subprocess.check_call(['systemctl', 'try-restart', 'apache2'])
|
||||
print('HTTP server has been restarted if it was running')
|
||||
except Exception:
|
||||
emprint('New HTTPS certificates generated, restart the web server manually')
|
||||
@@ -323,20 +345,20 @@ def initialize(cmdset):
|
||||
didsomething = True
|
||||
init_confluent_myname()
|
||||
try:
|
||||
sshutil.initialize_ca()
|
||||
await sshutil.initialize_ca()
|
||||
except sshutil.AlreadyExists:
|
||||
emprint('Skipping generation of SSH CA, already present and would likely be more problematic to regenerate than to reuse (if absolutely sure you want to discard old CA, then delete /etc/confluent/ssh/ca* and restart confluent)')
|
||||
if cmdset.a:
|
||||
didsomething = True
|
||||
init_confluent_myname()
|
||||
try:
|
||||
sshutil.initialize_root_key(True, True)
|
||||
await sshutil.initialize_root_key(True, True)
|
||||
except sshutil.AlreadyExists:
|
||||
emprint('Skipping generation of new automation key, already present and regeneration usually causes more problems. (If absolutely certain, delete /etc/confluent/ssh/automation* and restart confluent)')
|
||||
if cmdset.p:
|
||||
install_tftp_content()
|
||||
await install_tftp_content()
|
||||
if cmdset.l:
|
||||
local_node_trust_setup()
|
||||
await local_node_trust_setup()
|
||||
if cmdset.k:
|
||||
cas = set([])
|
||||
cakeys = set([])
|
||||
@@ -369,7 +391,7 @@ def initialize(cmdset):
|
||||
sys.exit(rc)
|
||||
if not didsomething and (cmdset.k or cmdset.l or cmdset.g or cmdset.p):
|
||||
if cmdset.g:
|
||||
updateboot('genesis-x86_64')
|
||||
await updateboot('genesis-x86_64')
|
||||
sys.exit(0)
|
||||
if not didsomething:
|
||||
sys.stderr.write('Nothing was done, use initialize -i for '
|
||||
@@ -384,7 +406,7 @@ def initialize(cmdset):
|
||||
totar = []
|
||||
if not os.path.exists('confluent_uuid'):
|
||||
c = client.Command()
|
||||
for rsp in c.read('/uuid'):
|
||||
async for rsp in c.read('/uuid'):
|
||||
uuid = rsp.get('uuid', {}).get('value', None)
|
||||
if uuid:
|
||||
oum = os.umask(0o11)
|
||||
@@ -410,15 +432,20 @@ def initialize(cmdset):
|
||||
for fname in files:
|
||||
topack.append(os.path.join(currd, fname))
|
||||
with open(tmpname, 'wb') as initramfs:
|
||||
packit = subprocess.Popen(['cpio', '-H', 'newc', '-o'],
|
||||
stdout=initramfs, stdin=subprocess.PIPE)
|
||||
packit = await asyncio.subprocess.create_subprocess_exec(
|
||||
'cpio', '-H', 'newc', '-o',
|
||||
stdin=asyncio.subprocess.PIPE, stdout=initramfs)
|
||||
#packit = subprocess.Popen(['cpio', '-H', 'newc', '-o'],
|
||||
# stdout=initramfs, stdin=subprocess.PIPE)
|
||||
for packfile in topack:
|
||||
if not isinstance(packfile, bytes):
|
||||
packfile = packfile.encode('utf8')
|
||||
packit.stdin.write(packfile)
|
||||
packit.stdin.write(b'\n')
|
||||
await packit.stdin.drain()
|
||||
packit.stdin.close()
|
||||
res = packit.wait()
|
||||
await packit.stdin.wait_closed()
|
||||
res = await packit.wait()
|
||||
if res:
|
||||
sys.stderr.write('Error occurred while packing site initramfs')
|
||||
sys.exit(1)
|
||||
@@ -434,11 +461,13 @@ def initialize(cmdset):
|
||||
finally:
|
||||
os.umask(oum)
|
||||
if cmdset.g:
|
||||
updateboot('genesis-x86_64')
|
||||
await updateboot('genesis-x86_64')
|
||||
if totar:
|
||||
tmptarname = tmpname.replace('cpio', 'tgz')
|
||||
tarcmd = ['tar', '-czf', tmptarname] + totar
|
||||
subprocess.check_call(tarcmd)
|
||||
tarproc = await asyncio.subprocess.create_subprocess_exec(*tarcmd)
|
||||
await tarproc.wait()
|
||||
#subprocess.check_call(tarcmd)
|
||||
os.rename(tmptarname, '/var/lib/confluent/public/site/initramfs.tgz')
|
||||
oum = os.umask(0o22)
|
||||
try:
|
||||
@@ -449,17 +478,19 @@ def initialize(cmdset):
|
||||
print('Site initramfs content packed successfully')
|
||||
|
||||
if not os.path.exists('/etc/confluent/srvcert.pem'):
|
||||
subprocess.check_call(['collective', 'gencert'])
|
||||
gcproc = await asyncio.subprocess.create_subprocess_exec('collective', 'gencert')
|
||||
await gcproc.wait()
|
||||
#subprocess.check_call(['collective', 'gencert'])
|
||||
# TODO: check selinux and segetbool for httpd_can_network_connect
|
||||
# httpd available and enabled?
|
||||
|
||||
|
||||
def updateboot(profilename):
|
||||
async def updateboot(profilename):
|
||||
if not os.path.exists('/var/lib/confluent/public/site/initramfs.cpio'):
|
||||
emprint('Must generate site content first (TLS (-t) and/or SSH (-s))')
|
||||
return 1
|
||||
c = client.Command()
|
||||
for rsp in c.update('/deployment/profiles/{0}'.format(profilename),
|
||||
async for rsp in c.update('/deployment/profiles/{0}'.format(profilename),
|
||||
{'updateboot': 1}):
|
||||
if 'updated' in rsp:
|
||||
print('Updated: {0}'.format(rsp['updated']))
|
||||
@@ -467,9 +498,9 @@ def updateboot(profilename):
|
||||
print(repr(rsp))
|
||||
|
||||
|
||||
def rebase(profilename):
|
||||
async def rebase(profilename):
|
||||
c = client.Command()
|
||||
for rsp in c.update('/deployment/profiles/{0}'.format(profilename), {'rebase': 1}):
|
||||
async for rsp in c.update('/deployment/profiles/{0}'.format(profilename), {'rebase': 1}):
|
||||
if 'updated' in rsp:
|
||||
print('Updated: {0}'.format(rsp['updated']))
|
||||
elif 'customized' in rsp:
|
||||
@@ -501,7 +532,7 @@ def oslist():
|
||||
print("")
|
||||
|
||||
|
||||
def osimport(imagefile, checkonly=False, custname=None):
|
||||
async def osimport(imagefile, checkonly=False, custname=None):
|
||||
c = client.Command()
|
||||
imagefile = os.path.abspath(imagefile)
|
||||
if c.unixdomain:
|
||||
@@ -518,7 +549,7 @@ def osimport(imagefile, checkonly=False, custname=None):
|
||||
apiargs = {'filename': imagefile}
|
||||
if custname:
|
||||
apiargs['custname'] = custname
|
||||
for rsp in c.create(apipath, apiargs):
|
||||
async for rsp in c.create(apipath, apiargs):
|
||||
if 'target' in rsp:
|
||||
importing = True
|
||||
shortname = rsp['name']
|
||||
@@ -543,7 +574,7 @@ def osimport(imagefile, checkonly=False, custname=None):
|
||||
print(repr(rsp))
|
||||
try:
|
||||
while importing:
|
||||
for rsp in c.read('/deployment/importing/{0}'.format(shortname)):
|
||||
async for rsp in c.read('/deployment/importing/{0}'.format(shortname)):
|
||||
if 'progress' in rsp:
|
||||
sys.stdout.write('{0}: {1:.2f}% \r'.format(rsp['phase'],
|
||||
rsp['progress']))
|
||||
@@ -563,7 +594,8 @@ def osimport(imagefile, checkonly=False, custname=None):
|
||||
time.sleep(0.5)
|
||||
finally:
|
||||
if shortname:
|
||||
list(c.delete('/deployment/importing/{0}'.format(shortname)))
|
||||
async for x in c.delete('/deployment/importing/{0}'.format(shortname)):
|
||||
pass
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv)
|
||||
asyncio.run(main(sys.argv))
|
||||
|
||||
@@ -38,9 +38,9 @@ if [ "$OPKGNAME" = "confluent-server" ]; then
|
||||
if grep wheezy /etc/os-release; then
|
||||
sed -i 's/^\(Depends:.*\)/\1, python-confluent-client, python-lxml, python-eficompressor, python-pycryptodomex, python-dateutil, python-pyopenssl, python-msgpack/' debian/control
|
||||
elif grep jammy /etc/os-release; then
|
||||
sed -i 's/^\(Depends:.*\)/\1, confluent-client, python3-lxml, python3-eficompressor, python3-pycryptodome, python3-websocket, python3-msgpack, python3-eventlet, python3-pyparsing, python3-pyghmi(>=1.5.71), python3-paramiko, python3-pysnmp4, python3-libarchive-c, confluent-vtbufferd, python3-netifaces, python3-yaml, python3-dateutil/' debian/control
|
||||
sed -i 's/^\(Depends:.*\)/\1, confluent-client, python3-lxml, python3-eficompressor, python3-pycryptodome, python3-websocket, python3-msgpack, python3-aiohttp, python3-pyparsing, python3-pyghmi(>=1.5.71), python3-paramiko, python3-pysnmp4, python3-libarchive-c, confluent-vtbufferd, python3-netifaces, python3-yaml, python3-dateutil/' debian/control
|
||||
else
|
||||
sed -i 's/^\(Depends:.*\)/\1, confluent-client, python3-lxml, python3-eficompressor, python3-pycryptodome, python3-websocket, python3-msgpack, python3-eventlet, python3-pyparsing, python3-pyghmi(>=1.5.71), python3-paramiko, python3-pysnmp4, python3-libarchive-c, confluent-vtbufferd, python3-netifaces, python3-yaml, python3-dateutil, python3-pyasyncore/' debian/control
|
||||
sed -i 's/^\(Depends:.*\)/\1, confluent-client, python3-lxml, python3-eficompressor, python3-pycryptodome, python3-websocket, python3-msgpack, python3-aiohttp, python3-pyparsing, python3-pyghmi(>=1.5.71), python3-paramiko, python3-pysnmp4, python3-libarchive-c, confluent-vtbufferd, python3-netifaces, python3-yaml, python3-dateutil, python3-pyasyncore/' debian/control
|
||||
fi
|
||||
if grep wheezy /etc/os-release; then
|
||||
echo 'confluent_client python-confluent-client' >> debian/pydist-overrides
|
||||
|
||||
@@ -34,7 +34,7 @@ import confluent.exceptions as exc
|
||||
import confluent.lookuptools as lookuptools
|
||||
import confluent.core
|
||||
|
||||
def decode_alert(varbinds, configmanager):
|
||||
async def decode_alert(varbinds, configmanager):
|
||||
"""Decode an SNMP alert for a server
|
||||
|
||||
Given the agentaddr, OID for the trap, and a dict of varbinds,
|
||||
@@ -49,11 +49,11 @@ def decode_alert(varbinds, configmanager):
|
||||
agentaddr = varbinds['.1.3.6.1.6.3.18.1.3.0']
|
||||
except KeyError:
|
||||
agentaddr = varbinds['1.3.6.1.6.3.18.1.3.0']
|
||||
node = lookuptools.node_by_manager(agentaddr)
|
||||
node = await lookuptools.node_by_manager(agentaddr)
|
||||
if node is None:
|
||||
raise exc.InvalidArgumentException(
|
||||
'Unable to find a node with specified manager')
|
||||
return confluent.core.handle_path(
|
||||
return await confluent.core.handle_path(
|
||||
'/nodes/{0}/events/hardware/decode'.format(node), 'update',
|
||||
configmanager, varbinds, autostrip=False)
|
||||
|
||||
|
||||
@@ -14,21 +14,10 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
# Overall, the result of this shall be:
|
||||
# - Web clients can create the same out-of-order responsiveness as socket
|
||||
# clients (but with more complexity on their end)
|
||||
# - Web clients can share single request among console sessions
|
||||
# - Web clients can get async notify of things like node add/remove, events
|
||||
|
||||
# This provides an async strategy to http clients. The design is that a http
|
||||
# session may have an 'async' resource. In such a case, any requests are
|
||||
# queued and immediately the response is given accepting the queued request.
|
||||
# A request flags itself as queue-compatible through an HTTP header indicating
|
||||
# the identifier of the async thread. As responses happen to the queued
|
||||
# request, data is dispatched to the first registered poller for data on
|
||||
# the session. This way, a client may elect to provide multiple pollers
|
||||
# to mitigate general choppiness of http network pattern. It may not be
|
||||
# worth it, but it's possible.
|
||||
# This handles ownership of asynchronous behavior driving sessions
|
||||
# with websockets. There was a long-polling HTTP mechanism but that is removed
|
||||
# Now it's possible to have asynchronous requests multiplexed over a single websockets
|
||||
# with none of the "choppiness" inherent to multiple long-polling requests
|
||||
|
||||
# Additionally support console session multiplexing, to mitigate needed
|
||||
# connection count.
|
||||
@@ -39,16 +28,16 @@
|
||||
# Much like console sessions, these will be reaped if a client spends too
|
||||
# far away.
|
||||
|
||||
import asyncio
|
||||
import collections
|
||||
import confluent.exceptions as exc
|
||||
import confluent.messages as messages
|
||||
import confluent.util as util
|
||||
import eventlet
|
||||
import greenlet
|
||||
import confluent.core as core
|
||||
import confluent.log as log
|
||||
import time
|
||||
|
||||
_asyncsessions = {}
|
||||
_cleanthread = None
|
||||
_consolesessions = None
|
||||
|
||||
|
||||
@@ -74,25 +63,14 @@ class AsyncTermRelation(object):
|
||||
|
||||
class AsyncSession(object):
|
||||
|
||||
def __init__(self, wshandler=None):
|
||||
def __init__(self, wshandler):
|
||||
self.asyncid = _assign_asyncid(self)
|
||||
self.responses = collections.deque()
|
||||
self.wshandler = wshandler
|
||||
self._evt = None
|
||||
self.termrelations = []
|
||||
self.consoles = set([])
|
||||
if not wshandler:
|
||||
self.reaper = eventlet.spawn_after(15, self.destroy)
|
||||
|
||||
def add(self, requestid, rsp):
|
||||
if self.wshandler:
|
||||
self.wshandler(messages.AsyncMessage((requestid, rsp)))
|
||||
if self.responses is None:
|
||||
return
|
||||
self.responses.append((requestid, rsp))
|
||||
if self._evt:
|
||||
self._evt.send()
|
||||
self._evt = None
|
||||
async def add(self, requestid, rsp):
|
||||
await self.wshandler(messages.AsyncMessage((requestid, rsp)))
|
||||
|
||||
def set_term_relation(self, env):
|
||||
# need a term relation to keep track of what data belongs
|
||||
@@ -107,64 +85,39 @@ class AsyncSession(object):
|
||||
self.consoles.add(sessionid)
|
||||
|
||||
def destroy(self):
|
||||
if self._evt:
|
||||
self._evt.send()
|
||||
self._evt = None
|
||||
for console in self.consoles:
|
||||
_consolesessions[console]['session'].destroy()
|
||||
self.consoles = set([])
|
||||
self.responses = None
|
||||
del _asyncsessions[self.asyncid]
|
||||
|
||||
def run_handler(self, handler, requestid):
|
||||
async def run_handler(self, handler, requestid):
|
||||
try:
|
||||
for rsp in handler:
|
||||
self.add(requestid, rsp)
|
||||
self.add(requestid, messages.AsyncCompletion())
|
||||
async for rsp in core.iterate_responses(handler):
|
||||
await self.add(requestid, rsp)
|
||||
await self.add(requestid, messages.AsyncCompletion())
|
||||
except Exception as e:
|
||||
self.add(requestid, e)
|
||||
print(repr(e))
|
||||
log.logtrace()
|
||||
await self.add(requestid, e)
|
||||
|
||||
def get_responses(self, timeout=25):
|
||||
self.reaper.cancel()
|
||||
self.reaper = eventlet.spawn_after(timeout + 15, self.destroy)
|
||||
nextexpiry = time.time() + 90
|
||||
for csess in list(self.consoles):
|
||||
try:
|
||||
_consolesessions[csess]['expiry'] = nextexpiry
|
||||
except KeyError: # session has been closed elsewhere
|
||||
self.consoles.discard(csess)
|
||||
if self._evt:
|
||||
# TODO(jjohnson2): This precludes the goal of 'double barreled'
|
||||
# access.... revisit if this could matter
|
||||
raise Exception('get_responses is not re-entrant')
|
||||
if not self.responses: # wait to accumulate some
|
||||
self._evt = eventlet.event.Event()
|
||||
with eventlet.Timeout(timeout, False):
|
||||
self._evt.wait()
|
||||
self._evt = None
|
||||
while self.responses:
|
||||
yield self.responses.popleft()
|
||||
|
||||
|
||||
def run_handler(hdlr, env):
|
||||
asyncsessid = env['HTTP_CONFLUENTASYNCID']
|
||||
async def run_handler(hdlr, req):
|
||||
asyncsessid = req.headers['ConfluentAsyncId']
|
||||
try:
|
||||
asyncsession = _asyncsessions[asyncsessid]['asyncsession']
|
||||
requestid = env['HTTP_CONFLUENTREQUESTID']
|
||||
requestid = req.headers['ConfluentRequestId']
|
||||
except KeyError:
|
||||
raise exc.InvalidArgumentException(
|
||||
'Invalid Session ID or missing request id')
|
||||
eventlet.spawn_n(asyncsession.run_handler, hdlr, requestid)
|
||||
cloop = asyncio.get_event_loop()
|
||||
cloop.create_task(asyncsession.run_handler(hdlr, requestid))
|
||||
return requestid
|
||||
|
||||
|
||||
def get_async(env, querydict):
|
||||
global _cleanthread
|
||||
return _asyncsessions[env['HTTP_CONFLUENTASYNCID']]['asyncsession']
|
||||
|
||||
|
||||
def handle_async(env, querydict, threadset, wshandler=None):
|
||||
global _cleanthread
|
||||
# This may be one of two things, a request for a new async stream
|
||||
# or a request for next data from async stream
|
||||
# httpapi otherwise handles requests an injecting them to queue
|
||||
@@ -174,25 +127,7 @@ def handle_async(env, querydict, threadset, wshandler=None):
|
||||
if wshandler:
|
||||
yield currsess
|
||||
return
|
||||
yield messages.AsyncSession(currsess.asyncid)
|
||||
return
|
||||
if querydict['asyncid'] not in _asyncsessions:
|
||||
raise exc.InvalidArgumentException(
|
||||
'Invalid or expired async id')
|
||||
mythreadid = greenlet.getcurrent()
|
||||
threadset.add(mythreadid)
|
||||
loggedout = None
|
||||
currsess = None
|
||||
try:
|
||||
currsess = _asyncsessions[querydict['asyncid']]['asyncsession']
|
||||
for rsp in currsess.get_responses():
|
||||
yield messages.AsyncMessage(rsp)
|
||||
except greenlet.GreenletExit as ge:
|
||||
loggedout = ge
|
||||
threadset.discard(mythreadid)
|
||||
if loggedout is not None:
|
||||
currsess.destroy()
|
||||
raise exc.LoggedOut()
|
||||
raise Exception("Long polling asynchttp is discontinued")
|
||||
|
||||
|
||||
def set_console_sessions(consolesessions):
|
||||
|
||||
@@ -19,13 +19,13 @@
|
||||
# the PBKDF2 transform is skipped unless a user has been idle for sufficient
|
||||
# time
|
||||
|
||||
import asyncio
|
||||
import confluent.config.configmanager as configmanager
|
||||
import eventlet
|
||||
import eventlet.tpool
|
||||
try:
|
||||
import Cryptodome.Protocol.KDF as KDF
|
||||
except ImportError:
|
||||
import Crypto.Protocol.KDF as KDF
|
||||
from concurrent.futures import ProcessPoolExecutor
|
||||
from fnmatch import fnmatch
|
||||
import hashlib
|
||||
import hmac
|
||||
@@ -33,6 +33,7 @@ import msgpack
|
||||
import multiprocessing
|
||||
import os
|
||||
import pwd
|
||||
import confluent.tasks as tasks
|
||||
import confluent.userutil as userutil
|
||||
import confluent.util as util
|
||||
pam = None
|
||||
@@ -238,7 +239,7 @@ def authorize(name, element, tenant=False, operation='create',
|
||||
return False
|
||||
|
||||
|
||||
def check_user_passphrase(name, passphrase, operation=None, element=None, tenant=False):
|
||||
async def check_user_passphrase(name, passphrase, operation=None, element=None, tenant=False):
|
||||
"""Check a a login name and passphrase for authenticity and authorization
|
||||
|
||||
The function combines authentication and authorization into one function.
|
||||
@@ -268,7 +269,7 @@ def check_user_passphrase(name, passphrase, operation=None, element=None, tenant
|
||||
# by a user, which might be malicious
|
||||
# would normally make an event and wait
|
||||
# but here there's no need for that
|
||||
eventlet.sleep(0.5)
|
||||
await asyncio.sleep(0.5)
|
||||
cfm = configmanager.ConfigManager(tenant, username=user)
|
||||
ucfg = cfm.get_user(user)
|
||||
if ucfg is None:
|
||||
@@ -280,7 +281,7 @@ def check_user_passphrase(name, passphrase, operation=None, element=None, tenant
|
||||
except KeyError:
|
||||
pass
|
||||
if ucfg is None:
|
||||
eventlet.sleep(0.05)
|
||||
await asyncio.sleep(0.05)
|
||||
return None
|
||||
bpassphrase = None
|
||||
if isinstance(passphrase, dict) and len(passphrase) == 1:
|
||||
@@ -304,22 +305,16 @@ def check_user_passphrase(name, passphrase, operation=None, element=None, tenant
|
||||
# throw it at the worker pool when implemented
|
||||
# maybe a distinct worker pool, wondering about starving out non-auth stuff
|
||||
salt, crypt = ucfg['cryptpass']
|
||||
# execute inside tpool to get greenthreads to give it a special thread
|
||||
# world
|
||||
# TODO(jbjohnso): util function to generically offload a call
|
||||
# such a beast could be passed into pyghmi as a way for pyghmi to
|
||||
# magically get offload of the crypto functions without having
|
||||
# to explicitly get into the eventlet tpool game
|
||||
global authworkers
|
||||
global authcleaner
|
||||
if authworkers is None:
|
||||
authworkers = multiprocessing.Pool(processes=1)
|
||||
authworkers = ProcessPoolExecutor(max_workers=1) # multiprocessing.Pool(processes=1)
|
||||
else:
|
||||
authcleaner.cancel()
|
||||
authcleaner = eventlet.spawn_after(30, _clean_authworkers)
|
||||
crypted = eventlet.tpool.execute(_do_pbkdf, passphrase, salt)
|
||||
authcleaner = tasks.spawn_task_after(30, _clean_authworkers)
|
||||
crypted = await _do_pbkdf(passphrase, salt)
|
||||
del _passchecking[(user, tenant)]
|
||||
eventlet.sleep(
|
||||
await asyncio.sleep(
|
||||
0.05) # either way, we want to stall so that client can't
|
||||
# determine failure because there is a delay, valid response will
|
||||
# delay as well
|
||||
@@ -332,52 +327,56 @@ def check_user_passphrase(name, passphrase, operation=None, element=None, tenant
|
||||
pwe = pwd.getpwnam(user)
|
||||
except KeyError:
|
||||
#pam won't work if the user doesn't exist, don't go further
|
||||
eventlet.sleep(0.05) # stall even on test for existence of a username
|
||||
await asyncio.sleep(0.05) # stall even on test for existence of a username
|
||||
return None
|
||||
if os.getuid() != 0:
|
||||
# confluent is running with reduced privilege, however, pam_unix refuses
|
||||
# to let a non-0 user check anothers password.
|
||||
# We will fork and the child will assume elevated privilege to
|
||||
# get unix_chkpwd helper to enable checking /etc/shadow
|
||||
getprompt, sendprompt = os.pipe()
|
||||
getprompt, sendprompt = os.fdopen(getprompt, 'rb', 0), os.fdopen(sendprompt, 'wb', 0)
|
||||
pid = os.fork()
|
||||
if not pid:
|
||||
usergood = False
|
||||
try:
|
||||
getprompt.close()
|
||||
# we change to the uid we are trying to authenticate as, because
|
||||
# pam_unix uses unix_chkpwd which reque
|
||||
os.setuid(pwe.pw_uid)
|
||||
pa = pam.pam()
|
||||
usergood = pa.authenticate(user, passphrase, service=_pamservice)
|
||||
if (not usergood and len(pa.prompts) > 1 and
|
||||
(not isinstance(passphrase, dict) or
|
||||
(set(passphrase) - pa.prompts))):
|
||||
sendprompt.write(msgpack.packb(list(pa.prompts)))
|
||||
sendprompt.close()
|
||||
os._exit(2)
|
||||
finally:
|
||||
os._exit(0 if usergood else 1)
|
||||
sendprompt.close()
|
||||
usergood = os.waitpid(pid, 0)[1]
|
||||
if (usergood >> 8) == 2:
|
||||
prompts = getprompt.read()
|
||||
if (prompts):
|
||||
raise PromptsNeeded(msgpack.unpackb(prompts))
|
||||
usergood = usergood == 0
|
||||
getprompt.close()
|
||||
else:
|
||||
# We are running as root, we don't need to fork in order to authenticate the
|
||||
# user
|
||||
usergood = pam.authenticate(user, passphrase, service=_pamservice)
|
||||
usergood = await asyncio.get_event_loop().run_in_executor(authworkers, pam_check, pwe, user, passphrase)
|
||||
if usergood:
|
||||
if bpassphrase:
|
||||
_passcache[(user, tenant)] = hashlib.sha256(bpassphrase).digest()
|
||||
return authorize(user, element, tenant, operation, skipuserobj=False)
|
||||
eventlet.sleep(0.05) # stall even on test for existence of a username
|
||||
await asyncio.sleep(0.05) # stall even on test for existence of a username
|
||||
return None
|
||||
|
||||
def pam_check(pwe, user, passphrase):
|
||||
if os.getuid() != 0:
|
||||
# confluent is running with reduced privilege, however, pam_unix refuses
|
||||
# to let a non-0 user check anothers password.
|
||||
# We will fork and the child will assume elevated privilege to
|
||||
# get unix_chkpwd helper to enable checking /etc/shadow
|
||||
getprompt, sendprompt = os.pipe()
|
||||
getprompt, sendprompt = os.fdopen(getprompt, 'rb', 0), os.fdopen(sendprompt, 'wb', 0)
|
||||
pid = os.fork() # we are forking with asyncio, but we are not using async in the child so it should be fine.
|
||||
if not pid:
|
||||
usergood = False
|
||||
try:
|
||||
getprompt.close()
|
||||
# we change to the uid we are trying to authenticate as, because
|
||||
# pam_unix uses unix_chkpwd which reque
|
||||
os.setuid(pwe.pw_uid)
|
||||
pa = pam.pam()
|
||||
usergood = pa.authenticate(user, passphrase, service=_pamservice)
|
||||
if (not usergood and len(pa.prompts) > 1 and
|
||||
(not isinstance(passphrase, dict) or
|
||||
(set(passphrase) - pa.prompts))):
|
||||
sendprompt.write(msgpack.packb(list(pa.prompts)))
|
||||
sendprompt.close()
|
||||
os._exit(2)
|
||||
finally:
|
||||
os._exit(0 if usergood else 1)
|
||||
sendprompt.close()
|
||||
usergood = os.waitpid(pid, 0)[1]
|
||||
if (usergood >> 8) == 2:
|
||||
prompts = getprompt.read()
|
||||
if (prompts):
|
||||
raise PromptsNeeded(msgpack.unpackb(prompts))
|
||||
usergood = usergood == 0
|
||||
getprompt.close()
|
||||
else:
|
||||
# We are running as root, we don't need to fork in order to authenticate the
|
||||
# user
|
||||
usergood = pam.authenticate(user, passphrase, service=_pamservice)
|
||||
return usergood
|
||||
|
||||
def _apply_pbkdf(passphrase, salt):
|
||||
return KDF.PBKDF2(passphrase, salt, 32, 10000,
|
||||
lambda p, s: hmac.new(p, s, hashlib.sha256).digest())
|
||||
@@ -390,9 +389,10 @@ def _clean_authworkers():
|
||||
authcleaner = None
|
||||
|
||||
|
||||
def _do_pbkdf(passphrase, salt):
|
||||
async def _do_pbkdf(passphrase, salt):
|
||||
# we must get it over to the authworkers pool or else get blocked in
|
||||
# compute. However, we do want to wait for result, so we have
|
||||
# one of the exceedingly rare sort of circumstances where 'apply'
|
||||
# actually makes sense
|
||||
return authworkers.apply(_apply_pbkdf, [passphrase, salt])
|
||||
res = await asyncio.get_event_loop().run_in_executor(authworkers, _apply_pbkdf, passphrase, salt)
|
||||
return res
|
||||
|
||||
@@ -1,11 +1,21 @@
|
||||
import os
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
path = os.path.dirname(os.path.realpath(__file__))
|
||||
path = os.path.realpath(os.path.join(path, '..'))
|
||||
if path.startswith('/opt'):
|
||||
sys.path.append(path)
|
||||
import confluent.collective.manager as collective
|
||||
import confluent.util as util
|
||||
from os.path import exists
|
||||
import datetime
|
||||
import shutil
|
||||
import socket
|
||||
import eventlet.green.subprocess as subprocess
|
||||
import tempfile
|
||||
try:
|
||||
import cryptography.x509 as x509
|
||||
except ImportError:
|
||||
x509 = None
|
||||
|
||||
def mkdirp(targ):
|
||||
try:
|
||||
@@ -31,8 +41,8 @@ def normalize_uid():
|
||||
raise Exception('Need to run as root or owner of /etc/confluent')
|
||||
return curruid
|
||||
|
||||
def get_ip_addresses():
|
||||
lines, _ = util.run(['ip', 'addr'])
|
||||
async def get_ip_addresses():
|
||||
lines, _ = await util.check_output('ip', 'addr')
|
||||
if not isinstance(lines, str):
|
||||
lines = lines.decode('utf8')
|
||||
for line in lines.split('\n'):
|
||||
@@ -143,11 +153,11 @@ def get_certificate_paths():
|
||||
tlsmateriallocation.setdefault('bundles', []).append(ngbundlepath)
|
||||
return tlsmateriallocation
|
||||
|
||||
def assure_tls_ca():
|
||||
async def assure_tls_ca():
|
||||
keyout, certout = ('/etc/confluent/tls/cakey.pem', '/etc/confluent/tls/cacert.pem')
|
||||
if not os.path.exists(certout):
|
||||
#create_simple_ca(keyout, certout)
|
||||
create_full_ca(certout)
|
||||
await create_full_ca(certout)
|
||||
fname = '/var/lib/confluent/public/site/tls/{0}.pem'.format(
|
||||
collective.get_myname())
|
||||
ouid = normalize_uid()
|
||||
@@ -159,8 +169,8 @@ def assure_tls_ca():
|
||||
raise
|
||||
try:
|
||||
shutil.copy2('/etc/confluent/tls/cacert.pem', fname)
|
||||
hv, _ = util.run(
|
||||
['openssl', 'x509', '-in', '/etc/confluent/tls/cacert.pem', '-hash', '-noout'])
|
||||
hv, _ = await util.check_output(
|
||||
'openssl', 'x509', '-in', '/etc/confluent/tls/cacert.pem', '-hash', '-noout')
|
||||
if not isinstance(hv, str):
|
||||
hv = hv.decode('utf8')
|
||||
hv = hv.strip()
|
||||
@@ -178,6 +188,17 @@ def assure_tls_ca():
|
||||
os.symlink(certname, hashname)
|
||||
finally:
|
||||
os.seteuid(ouid)
|
||||
return certout
|
||||
|
||||
#def is_self_signed(pem):
|
||||
# cert = ssl.PEM_cert_to_DER_cert(pem)
|
||||
# return cert.get('subjectAltName', []) == cert.get('issuer', [])
|
||||
# x509 certificate issuer subject comparison..
|
||||
#>>> b.issuer
|
||||
#<Name(C=US,ST=NC,L=RTP,O=Lenovo,CN=XCC-7D9D-J102MM2T)>
|
||||
#>>> b.subject
|
||||
#<Name(C=US,ST=NC,L=RTP,O=Lenovo,CN=XCC-7D9D-J102MM2T)>
|
||||
|
||||
|
||||
def substitute_cfg(setting, key, val, newval, cfgfile, line):
|
||||
if key.strip() == setting:
|
||||
@@ -185,7 +206,7 @@ def substitute_cfg(setting, key, val, newval, cfgfile, line):
|
||||
return True
|
||||
return False
|
||||
|
||||
def create_full_ca(certout):
|
||||
async def create_full_ca(certout):
|
||||
mkdirp('/etc/confluent/tls/ca/private')
|
||||
keyout = '/etc/confluent/tls/ca/private/cakey.pem'
|
||||
csrout = '/etc/confluent/tls/ca/ca.csr'
|
||||
@@ -222,24 +243,24 @@ def create_full_ca(certout):
|
||||
cfgfile.write(line.strip() + '\n')
|
||||
continue
|
||||
cfgfile.write(line.strip() + '\n')
|
||||
cfgfile.write('\n[CACert]\nbasicConstraints = CA:true\n\n[ca_confluent]\n')
|
||||
subprocess.check_call(
|
||||
['openssl', 'ecparam', '-name', 'secp384r1', '-genkey', '-out',
|
||||
keyout])
|
||||
subprocess.check_call(
|
||||
['openssl', 'req', '-new', '-key', keyout, '-out', csrout, '-subj', subj])
|
||||
subprocess.check_call(
|
||||
['openssl', 'ca', '-config', newcfg, '-batch', '-selfsign',
|
||||
cfgfile.write('\n[CACert]\nbasicConstraints = critical,CA:true\nkeyUsage = critical,keyCertSign,cRLSign\n[ca_confluent]\n')
|
||||
await util.check_call(
|
||||
'openssl', 'ecparam', '-name', 'secp384r1', '-genkey', '-out',
|
||||
keyout)
|
||||
await util.check_call(
|
||||
'openssl', 'req', '-new', '-key', keyout, '-out', csrout, '-subj', subj)
|
||||
await util.check_call(
|
||||
'openssl', 'ca', '-config', newcfg, '-batch', '-selfsign',
|
||||
'-extensions', 'CACert', '-extfile', newcfg,
|
||||
'-notext', '-startdate',
|
||||
'-notext', '-md', 'sha384', '-startdate',
|
||||
'19700101010101Z', '-enddate', '21000101010101Z', '-keyfile',
|
||||
keyout, '-out', '/etc/confluent/tls/ca/cacert.pem', '-in', csrout]
|
||||
keyout, '-out', '/etc/confluent/tls/ca/cacert.pem', '-in', csrout
|
||||
)
|
||||
shutil.copy2('/etc/confluent/tls/ca/cacert.pem', certout)
|
||||
#openssl ca -config openssl.cnf -selfsign -keyfile cakey.pem -startdate 20150214120000Z -enddate 20160214120000Z
|
||||
#20160107071311Z -enddate 20170106071311Z
|
||||
|
||||
def create_simple_ca(keyout, certout):
|
||||
async def create_simple_ca(keyout, certout):
|
||||
try:
|
||||
os.makedirs('/etc/confluent/tls')
|
||||
except OSError as e:
|
||||
@@ -249,92 +270,136 @@ def create_simple_ca(keyout, certout):
|
||||
tmphdl, tmpconfig = tempfile.mkstemp()
|
||||
os.close(tmphdl)
|
||||
shutil.copy2(sslcfg, tmpconfig)
|
||||
subprocess.check_call(
|
||||
['openssl', 'ecparam', '-name', 'secp384r1', '-genkey', '-out',
|
||||
keyout])
|
||||
await util.check_call(
|
||||
'openssl', 'ecparam', '-name', 'secp384r1', '-genkey', '-out',
|
||||
keyout)
|
||||
try:
|
||||
subj = '/CN=Confluent TLS Certificate authority ({0})'.format(socket.gethostname())
|
||||
if len(subj) > 68:
|
||||
subj = subj[:68]
|
||||
with open(tmpconfig, 'a') as cfgfile:
|
||||
cfgfile.write('\n[CACert]\nbasicConstraints = CA:true\n')
|
||||
subprocess.check_call([
|
||||
cfgfile.write('\n[CACert]\nbasicConstraints = critical,CA:true\n')
|
||||
await util.check_call(
|
||||
'openssl', 'req', '-new', '-x509', '-key', keyout, '-days',
|
||||
'27300', '-out', certout, '-subj', subj,
|
||||
'-extensions', 'CACert', '-config', tmpconfig
|
||||
])
|
||||
)
|
||||
finally:
|
||||
os.remove(tmpconfig)
|
||||
|
||||
def create_certificate(keyout=None, certout=None, csrout=None):
|
||||
if not keyout:
|
||||
async def create_certificate(keyout=None, certout=None, csrfile=None, subj=None, san=None, backdate=True, days=None):
|
||||
now_utc = datetime.datetime.now(datetime.timezone.utc)
|
||||
if backdate:
|
||||
# To deal with wildly off clocks, we backdate certificates.
|
||||
startdate = '20000101010101Z'
|
||||
else:
|
||||
# apply a mild backdate anyway, even if these are supposed to be for more accurate clocks
|
||||
startdate = (now_utc - datetime.timedelta(hours=24)).strftime('%Y%m%d%H%M%SZ')
|
||||
if days is None:
|
||||
enddate = '21000101010101Z'
|
||||
else:
|
||||
enddate = (now_utc + datetime.timedelta(days=days)).strftime('%Y%m%d%H%M%SZ')
|
||||
tlsmateriallocation = {}
|
||||
if not certout:
|
||||
tlsmateriallocation = get_certificate_paths()
|
||||
keyout = tlsmateriallocation.get('keys', [None])[0]
|
||||
certout = tlsmateriallocation.get('certs', [None])[0]
|
||||
if not certout:
|
||||
certout = tlsmateriallocation.get('bundles', [None])[0]
|
||||
if not keyout or not certout:
|
||||
if (not keyout and not csrfile) or not certout:
|
||||
raise Exception('Unable to locate TLS certificate path automatically')
|
||||
assure_tls_ca()
|
||||
shortname = socket.gethostname().split('.')[0]
|
||||
longname = shortname # socket.getfqdn()
|
||||
if not csrout:
|
||||
subprocess.check_call(
|
||||
['openssl', 'ecparam', '-name', 'secp384r1', '-genkey', '-out',
|
||||
keyout])
|
||||
ipaddrs = list(get_ip_addresses())
|
||||
san = ['IP:{0}'.format(x) for x in ipaddrs]
|
||||
# It is incorrect to put IP addresses as DNS type. However
|
||||
# there exists non-compliant clients that fail with them as IP
|
||||
# san.extend(['DNS:{0}'.format(x) for x in ipaddrs])
|
||||
dnsnames = set(ipaddrs)
|
||||
dnsnames.add(shortname)
|
||||
for currip in ipaddrs:
|
||||
dnsnames.add(socket.getnameinfo((currip, 0), 0)[0])
|
||||
for currname in dnsnames:
|
||||
san.append('DNS:{0}'.format(currname))
|
||||
#san.append('DNS:{0}'.format(longname))
|
||||
san = ','.join(san)
|
||||
cacertname = await assure_tls_ca()
|
||||
if not subj:
|
||||
shortname = socket.gethostname().split('.')[0]
|
||||
longname = shortname # socket.getfqdn()
|
||||
subj = '/CN={0}'.format(longname)
|
||||
elif '/CN=' not in subj:
|
||||
subj = '/CN={0}'.format(subj)
|
||||
if not csrfile:
|
||||
await util.check_call(
|
||||
'openssl', 'ecparam', '-name', 'secp384r1', '-genkey', '-out',
|
||||
keyout)
|
||||
permitdomains = []
|
||||
if x509:
|
||||
# check if this CA has name constraints, and avoid violating them
|
||||
with open(cacertname, 'rb') as f:
|
||||
cer = x509.load_pem_x509_certificate(f.read())
|
||||
for extension in cer.extensions:
|
||||
if extension.oid == x509.ExtensionOID.NAME_CONSTRAINTS:
|
||||
nc = extension.value
|
||||
for pname in nc.permitted_subtrees:
|
||||
permitdomains.append(pname.value)
|
||||
if not san:
|
||||
ipaddrs = []
|
||||
async for ip in get_ip_addresses():
|
||||
ipaddrs.append(ip)
|
||||
if not permitdomains:
|
||||
san = ['IP:{0}'.format(x) for x in ipaddrs]
|
||||
# It is incorrect to put IP addresses as DNS type. However
|
||||
# there exists non-compliant clients that fail with them as IP
|
||||
# san.extend(['DNS:{0}'.format(x) for x in ipaddrs])
|
||||
dnsnames = set(ipaddrs)
|
||||
dnsnames.add(shortname)
|
||||
dnsnames.add(longname)
|
||||
else:
|
||||
# nameconstraints preclude IP and shortname
|
||||
san = []
|
||||
dnsnames = set()
|
||||
for suffix in permitdomains:
|
||||
if longname.endswith(suffix):
|
||||
dnsnames.add(longname)
|
||||
break
|
||||
for currip in ipaddrs:
|
||||
currname = socket.getnameinfo((currip, 0), 0)[0]
|
||||
for suffix in permitdomains:
|
||||
if currname.endswith(suffix):
|
||||
dnsnames.add(currname)
|
||||
break
|
||||
if not permitdomains:
|
||||
dnsnames.add(currname)
|
||||
for currname in dnsnames:
|
||||
san.append('DNS:{0}'.format(currname))
|
||||
#san.append('DNS:{0}'.format(longname))
|
||||
san = ','.join(san)
|
||||
sslcfg = get_openssl_conf_location()
|
||||
tmphdl, tmpconfig = tempfile.mkstemp()
|
||||
os.close(tmphdl)
|
||||
tmphdl, extconfig = tempfile.mkstemp()
|
||||
os.close(tmphdl)
|
||||
needcsr = False
|
||||
if csrout is None:
|
||||
if csrfile is None:
|
||||
needcsr = True
|
||||
tmphdl, csrout = tempfile.mkstemp()
|
||||
tmphdl, csrfile = tempfile.mkstemp()
|
||||
os.close(tmphdl)
|
||||
shutil.copy2(sslcfg, tmpconfig)
|
||||
try:
|
||||
with open(extconfig, 'a') as cfgfile:
|
||||
cfgfile.write('\nbasicConstraints=critical,CA:false\nkeyUsage=critical,digitalSignature\nextendedKeyUsage=serverAuth,clientAuth\nsubjectAltName={0}'.format(san))
|
||||
if needcsr:
|
||||
with open(tmpconfig, 'a') as cfgfile:
|
||||
cfgfile.write('\n[SAN]\nsubjectAltName={0}'.format(san))
|
||||
with open(extconfig, 'a') as cfgfile:
|
||||
cfgfile.write('\nbasicConstraints=CA:false\nsubjectAltName={0}'.format(san))
|
||||
subprocess.check_call([
|
||||
'openssl', 'req', '-new', '-key', keyout, '-out', csrout, '-subj',
|
||||
'/CN={0}'.format(longname),
|
||||
'-extensions', 'SAN', '-config', tmpconfig
|
||||
])
|
||||
else:
|
||||
# when used manually, allow the csr SAN to stand
|
||||
# may add explicit subj/SAN argument, in which case we would skip copy
|
||||
with open(tmpconfig, 'a') as cfgfile:
|
||||
cfgfile.write('\ncopy_extensions=copy\n')
|
||||
with open(extconfig, 'a') as cfgfile:
|
||||
cfgfile.write('\nbasicConstraints=CA:false\n')
|
||||
await util.check_call(
|
||||
'openssl', 'req', '-new', '-key', keyout, '-out', csrfile, '-subj',
|
||||
subj, '-extensions', 'SAN', '-config', tmpconfig
|
||||
)
|
||||
#else:
|
||||
# # when used manually, allow the csr SAN to stand
|
||||
# # may add explicit subj/SAN argument, in which case we would skip copy
|
||||
# #with open(tmpconfig, 'a') as cfgfile:
|
||||
# # cfgfile.write('\ncopy_extensions=copy\n')
|
||||
# with open(extconfig, 'a') as cfgfile:
|
||||
# cfgfile.write('\nbasicConstraints=CA:false\n')
|
||||
if os.path.exists('/etc/confluent/tls/cakey.pem'):
|
||||
# simple style CA in effect, make a random serial number and
|
||||
# hope for the best, and accept inability to backdate the cert
|
||||
serialnum = '0x' + ''.join(['{:02x}'.format(x) for x in bytearray(os.urandom(20))])
|
||||
subprocess.check_call([
|
||||
'openssl', 'x509', '-req', '-in', csrout,
|
||||
await util.check_call(
|
||||
'openssl', 'x509', '-req', '-in', csrfile,
|
||||
'-CA', '/etc/confluent/tls/cacert.pem',
|
||||
'-CAkey', '/etc/confluent/tls/cakey.pem',
|
||||
'-set_serial', serialnum, '-out', certout, '-days', '27300',
|
||||
'-extfile', extconfig
|
||||
])
|
||||
)
|
||||
else:
|
||||
# we moved to a 'proper' CA, mainly for access to backdating
|
||||
# start of certs for finicky system clocks
|
||||
@@ -348,13 +413,12 @@ def create_certificate(keyout=None, certout=None, csrout=None):
|
||||
shutil.copy2(cacfgfile, tmpcafile)
|
||||
os.close(tmphdl)
|
||||
cacfgfile = tmpcafile
|
||||
# with realcalock: # if we put it in server, we must lock it
|
||||
subprocess.check_call([
|
||||
'openssl', 'ca', '-config', cacfgfile,
|
||||
'-in', csrout, '-out', certout, '-batch', '-notext',
|
||||
'-startdate', '19700101010101Z', '-enddate', '21000101010101Z',
|
||||
'-extfile', extconfig
|
||||
])
|
||||
await util.check_call(
|
||||
'openssl', 'ca', '-config', cacfgfile, '-rand_serial',
|
||||
'-in', csrfile, '-out', certout, '-batch', '-notext',
|
||||
'-startdate', startdate, '-enddate', enddate, '-md', 'sha384',
|
||||
'-extfile', extconfig, '-subj', subj
|
||||
)
|
||||
for keycopy in tlsmateriallocation.get('keys', []):
|
||||
if keycopy != keyout:
|
||||
shutil.copy2(keyout, keycopy)
|
||||
@@ -381,18 +445,43 @@ def create_certificate(keyout=None, certout=None, csrout=None):
|
||||
finally:
|
||||
os.remove(tmpconfig)
|
||||
if needcsr:
|
||||
os.remove(csrout)
|
||||
print(extconfig) # os.remove(extconfig)
|
||||
os.remove(csrfile)
|
||||
os.remove(extconfig)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
import ipaddress
|
||||
outdir = os.getcwd()
|
||||
keyout = os.path.join(outdir, 'key.pem')
|
||||
certout = os.path.join(outdir, sys.argv[2] + 'cert.pem')
|
||||
certout = os.path.join(outdir, 'cert.pem')
|
||||
csrout = None
|
||||
subj, san = (None, None)
|
||||
try:
|
||||
bindex = sys.argv.index('-b')
|
||||
bmcnode = sys.argv.pop(bindex + 1) # Remove bmcnode argument
|
||||
sys.argv.pop(bindex) # Remove -b flag
|
||||
import confluent.config.configmanager as cfm
|
||||
c = cfm.ConfigManager(None)
|
||||
subj, san = util.get_bmc_subject_san(c, bmcnode)
|
||||
except ValueError:
|
||||
bindex = None
|
||||
if subj is None:
|
||||
try:
|
||||
sans = set()
|
||||
sindex = sys.argv.index('-s')
|
||||
subj = sys.argv.pop(sindex + 1) # Remove subject argument
|
||||
sys.argv.pop(sindex) # Remove -s flag
|
||||
try:
|
||||
ipaddress.ip_address(subj)
|
||||
sans.add('IP:{0}'.format(subj))
|
||||
except ValueError:
|
||||
sans.add('DNS:{0}'.format(subj))
|
||||
san = ','.join(sans) if sans else None
|
||||
except ValueError:
|
||||
pass
|
||||
try:
|
||||
csrout = sys.argv[1]
|
||||
except IndexError:
|
||||
csrout = None
|
||||
create_certificate(keyout, certout, csrout)
|
||||
create_certificate(keyout, certout, csrout, subj, san, backdate=False, days=3650)
|
||||
|
||||
@@ -41,7 +41,7 @@ def check_server_proof(invitation, mycert, peercert, proof):
|
||||
def check_client_proof(servername, mycert, peercert, proof):
|
||||
servername = servername.encode('utf-8')
|
||||
if servername not in pending_invites:
|
||||
return False
|
||||
return False, None
|
||||
invitation = pending_invites[servername]
|
||||
role = invitation['role']
|
||||
invitation = invitation['invitation']
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
# Copyright 2014 IBM Corporation
|
||||
# Copyright 2015-2019 Lenovo
|
||||
# Copyright 2015-2025 Lenovo
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
@@ -408,6 +408,12 @@ node = {
|
||||
'include /<prefixlen> CIDR suffix to indicate subnet length, which is '
|
||||
'autodetected by default where possible.',
|
||||
},
|
||||
'hardwaremanagement.manager_tls_name': {
|
||||
'description': 'A name to use in lieu of the value in hardwaremanagement.manager for '
|
||||
'TLS certificate verification purposes. Some strategies involve a non-IP, '
|
||||
'non-resolvable name, or this can be used to access by IP while using name-based '
|
||||
'validation',
|
||||
},
|
||||
'hardwaremanagement.method': {
|
||||
'description': 'The method used to perform operations such as power '
|
||||
'control, get sensor data, get inventory, and so on. '
|
||||
@@ -444,6 +450,9 @@ node = {
|
||||
#IBM Flex)''',
|
||||
# 'appliesto': ['system'],
|
||||
# },
|
||||
'id.index': {
|
||||
'description': 'Confluent generated numeric index for the node.',
|
||||
},
|
||||
'id.model': {
|
||||
'description': 'The model number of a node. In scenarios where there '
|
||||
'is both a name and a model number, it is generally '
|
||||
@@ -469,17 +478,17 @@ node = {
|
||||
'the discovery process to decide where to place the mac address of a detected PXE nic.',
|
||||
},
|
||||
'net.connection_name': {
|
||||
'description': 'Name to use when specifiying a name for connection and/or interface name for a team. This may be the name of a team interface, '
|
||||
'description': 'Name to use when specifiying a name for connection and/or interface name for a team/bond. This may be the name of a team/bond interface, '
|
||||
'the connection name in network manager for the interface, or may be installed as an altname '
|
||||
'as supported by the respective OS deployment profiles. Default is to accept default name for '
|
||||
'a team consistent with the respective OS, or to use the matching original port name as connection name.'
|
||||
'a team/bond consistent with the respective OS, or to use the matching original port name as connection name.'
|
||||
},
|
||||
'net.interface_names': {
|
||||
'description': 'Interface name or comma delimited list of names to match for this interface. It is generally recommended '
|
||||
'to leave this blank unless needing to set up interfaces that are not on a common subnet with a confluent server, '
|
||||
'as confluent servers provide autodetection for matching the correct network definition to an interface. '
|
||||
'This would be the default name per the deployed OS and can be a comma delimited list to denote members of '
|
||||
'a team or a single interface for VLAN/PKEY connections.'
|
||||
'a team/bond or a single interface for VLAN/PKEY connections.'
|
||||
},
|
||||
'net.mtu': {
|
||||
'description': 'MTU to apply to this connection',
|
||||
@@ -565,7 +574,7 @@ node = {
|
||||
'operating system',
|
||||
},
|
||||
'net.team_mode': {
|
||||
'description': 'Indicates that this interface should be a team and what mode or runner to use when teamed. '
|
||||
'description': 'Indicates that this interface should be a team/bond and what mode or runner to use when teamed or bonded. '
|
||||
'If this covers a deployment interface, one of the member interfaces may be brought up as '
|
||||
'a standalone interface until deployment is complete, as supported by the OS deployment profile. '
|
||||
'To support this scenario, the switch should be set up to allow independent operation of member ports (e.g. lacp bypass mode or fallback mode).',
|
||||
@@ -599,6 +608,10 @@ node = {
|
||||
'description': ('SNMPv1 community string, it is highly recommended to'
|
||||
'step up to SNMPv3'),
|
||||
},
|
||||
'snmp.privacyprotocol': {
|
||||
'description': 'The privacy protocol to use for SNMPv3',
|
||||
'valid_values': ('aes', 'des'),
|
||||
},
|
||||
# 'secret.snmplocalizedkey': {
|
||||
# 'description': ("SNMPv3 key localized to this node's SNMP Engine id"
|
||||
# 'This can be used in lieu of snmppassphrase to avoid'
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,6 +21,7 @@
|
||||
|
||||
# we track nodes that are actively being logged, watched, or have attached
|
||||
# there should be no more than one handler per node
|
||||
import asyncio
|
||||
import codecs
|
||||
import collections
|
||||
import confluent.collective.manager as collective
|
||||
@@ -29,17 +30,10 @@ import confluent.exceptions as exc
|
||||
import confluent.interface.console as conapi
|
||||
import confluent.log as log
|
||||
import confluent.core as plugin
|
||||
import confluent.tlvdata as tlvdata
|
||||
import confluent.asynctlvdata as tlvdata
|
||||
import confluent.tasks as tasks
|
||||
import confluent.util as util
|
||||
import eventlet
|
||||
import eventlet.event
|
||||
import eventlet.green.os as os
|
||||
import eventlet.green.select as select
|
||||
import eventlet.green.socket as socket
|
||||
import eventlet.green.subprocess as subprocess
|
||||
import eventlet.green.ssl as ssl
|
||||
import eventlet.semaphore as semaphore
|
||||
import fcntl
|
||||
import socket
|
||||
import random
|
||||
import struct
|
||||
import time
|
||||
@@ -60,39 +54,43 @@ def chunk_output(output, n):
|
||||
for i in range(0, len(output), n):
|
||||
yield output[i:i + n]
|
||||
|
||||
def get_buffer_output(nodename):
|
||||
async def get_buffer_output(nodename):
|
||||
out = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
out.setsockopt(socket.SOL_SOCKET, socket.SO_PASSCRED, 1)
|
||||
out.connect("\x00confluent-vtbuffer")
|
||||
rdr, writer = await asyncio.open_unix_connection(sock=out)
|
||||
if not isinstance(nodename, bytes):
|
||||
nodename = nodename.encode('utf8')
|
||||
outdata = bytearray()
|
||||
out.send(struct.pack('I', len(nodename)))
|
||||
out.send(nodename)
|
||||
select.select((out,), (), (), 30)
|
||||
writer.write(struct.pack('I', len(nodename)))
|
||||
writer.write(nodename)
|
||||
await writer.drain()
|
||||
while not outdata or outdata[-1]:
|
||||
try:
|
||||
chunk = os.read(out.fileno(), 128)
|
||||
except IOError:
|
||||
chunk = None
|
||||
if chunk:
|
||||
outdata.extend(chunk)
|
||||
else:
|
||||
select.select((out,), (), (), 0)
|
||||
chunk = await rdr.read(128) # os.read(out.fileno(), 128)
|
||||
if not chunk:
|
||||
raise Exception("bad read")
|
||||
outdata.extend(chunk)
|
||||
writer.close()
|
||||
await writer.wait_closed()
|
||||
return bytes(outdata[:-1])
|
||||
|
||||
|
||||
def send_output(nodename, output):
|
||||
async def send_output(nodename, output):
|
||||
if not isinstance(nodename, bytes):
|
||||
nodename = nodename.encode('utf8')
|
||||
out = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
out.setsockopt(socket.SOL_SOCKET, socket.SO_PASSCRED, 1)
|
||||
out.connect("\x00confluent-vtbuffer")
|
||||
out.send(struct.pack('I', len(nodename) | (1 << 29)))
|
||||
out.send(nodename)
|
||||
rdr, writer = await asyncio.open_unix_connection(sock=out)
|
||||
hdr = struct.pack('I', len(nodename) | (1 << 29))
|
||||
writer.write(hdr)
|
||||
writer.write(nodename)
|
||||
for chunk in chunk_output(output, 8192):
|
||||
out.send(struct.pack('I', len(chunk) | (2 << 29)))
|
||||
out.send(chunk)
|
||||
writer.write(struct.pack('I', len(chunk) | (2 << 29)))
|
||||
writer.write(chunk)
|
||||
await writer.drain()
|
||||
writer.close()
|
||||
await writer.wait_closed()
|
||||
|
||||
def _utf8_normalize(data, decoder):
|
||||
# first we give the stateful decoder a crack at the byte stream,
|
||||
@@ -158,7 +156,6 @@ class ConsoleHandler(object):
|
||||
# wall clock has gone backwards, use current time as best
|
||||
# guess
|
||||
self.lasttime = util.monotonic_time()
|
||||
self.clearbuffer()
|
||||
self.reconnect = None
|
||||
self.users = {}
|
||||
self._attribwatcher = None
|
||||
@@ -168,10 +165,14 @@ class ConsoleHandler(object):
|
||||
if self._genwatchattribs:
|
||||
self._attribwatcher = self.cfgmgr.watch_attributes(
|
||||
(self.node,), self._genwatchattribs, self._attribschanged)
|
||||
self.check_isondemand()
|
||||
tasks.spawn(self.ondemand_init())
|
||||
|
||||
async def ondemand_init(self):
|
||||
await self.clearbuffer()
|
||||
await self.check_isondemand()
|
||||
if not self._isondemand:
|
||||
self.connectstate = 'connecting'
|
||||
eventlet.spawn(self._connect)
|
||||
self._connect()
|
||||
|
||||
def resize(self, width, height):
|
||||
return None
|
||||
@@ -186,7 +187,7 @@ class ConsoleHandler(object):
|
||||
retrytime = 120
|
||||
return retrytime + (retrytime * random.random())
|
||||
|
||||
def feedbuffer(self, data):
|
||||
async def feedbuffer(self, data):
|
||||
if not isinstance(data, bytes):
|
||||
data = data.encode('utf-8')
|
||||
if self.pendingbytes is not None:
|
||||
@@ -194,16 +195,16 @@ class ConsoleHandler(object):
|
||||
self.pendingbytes = b''
|
||||
nodeid = self.termprefix + self.node
|
||||
try:
|
||||
send_output(nodeid, data)
|
||||
await send_output(nodeid, data)
|
||||
data = self.pendingbytes
|
||||
self.pendingbytes = None
|
||||
if data:
|
||||
send_output(nodeid, data)
|
||||
await send_output(nodeid, data)
|
||||
except Exception:
|
||||
_tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
|
||||
event=log.Events.stacktrace)
|
||||
|
||||
def check_isondemand(self):
|
||||
async def check_isondemand(self):
|
||||
self._dologging = True
|
||||
attrvalue = self.cfgmgr.get_node_attributes(
|
||||
(self.node,), ('console.logging', 'collective.manager'))
|
||||
@@ -217,21 +218,21 @@ class ConsoleHandler(object):
|
||||
self._isondemand = True
|
||||
if (attrvalue[self.node]['console.logging']['value']) in ('none', 'memory'):
|
||||
self._dologging = False
|
||||
self.check_collective(attrvalue)
|
||||
await self.check_collective(attrvalue)
|
||||
|
||||
def check_collective(self, attrvalue):
|
||||
async def check_collective(self, attrvalue):
|
||||
myc = attrvalue.get(self.node, {}).get('collective.manager', {}).get(
|
||||
'value', None)
|
||||
if list(configmodule.list_collective()) and not myc:
|
||||
self._is_local = False
|
||||
self._detach()
|
||||
self._disconnect()
|
||||
await self._disconnect()
|
||||
if myc and myc != collective.get_myname():
|
||||
# Do not do console connect for nodes managed by another
|
||||
# confluent collective member
|
||||
self._is_local = False
|
||||
self._detach()
|
||||
self._disconnect()
|
||||
await self._disconnect()
|
||||
else:
|
||||
self._is_local = True
|
||||
|
||||
@@ -244,11 +245,11 @@ class ConsoleHandler(object):
|
||||
return util.monotonic_time() - self.lasttime
|
||||
return False
|
||||
|
||||
def _attribschanged(self, nodeattribs, configmanager, **kwargs):
|
||||
async def _attribschanged(self, nodeattribs, configmanager, **kwargs):
|
||||
if 'collective.manager' in nodeattribs[self.node]:
|
||||
attrval = configmanager.get_node_attributes(self.node,
|
||||
'collective.manager')
|
||||
self.check_collective(attrval)
|
||||
await self.check_collective(attrval)
|
||||
if 'console.logging' in nodeattribs[self.node]:
|
||||
# decide whether logging changes how we react or not
|
||||
self._dologging = True
|
||||
@@ -271,11 +272,11 @@ class ConsoleHandler(object):
|
||||
if onlylogging:
|
||||
return
|
||||
else:
|
||||
self._ondemand()
|
||||
await self._ondemand()
|
||||
if logvalue in ('none', 'memory'):
|
||||
self._dologging = False
|
||||
if not self._isondemand or self.livesessions:
|
||||
eventlet.spawn(self._connect)
|
||||
self._connect()
|
||||
|
||||
def log(self, *args, **kwargs):
|
||||
if not self._dologging:
|
||||
@@ -291,8 +292,8 @@ class ConsoleHandler(object):
|
||||
else:
|
||||
self._console.ping()
|
||||
|
||||
def clearbuffer(self):
|
||||
self.feedbuffer(
|
||||
async def clearbuffer(self):
|
||||
await self.feedbuffer(
|
||||
'\x1bc[No data has been received from the remote console since ' \
|
||||
'connecting. This could\r\nbe due to having the console.logging ' \
|
||||
'attribute set to none or interactive,\r\nserial console not ' \
|
||||
@@ -304,40 +305,40 @@ class ConsoleHandler(object):
|
||||
for ses in list(self.livesessions):
|
||||
ses.detach()
|
||||
|
||||
def _disconnect(self):
|
||||
async def _disconnect(self):
|
||||
if self.connectionthread:
|
||||
self.connectionthread.kill()
|
||||
self.connectionthread.cancel()
|
||||
self.connectionthread = None
|
||||
# clear the terminal buffer when disconnected
|
||||
self.clearbuffer()
|
||||
await self.clearbuffer()
|
||||
if self._console:
|
||||
self.log(
|
||||
logdata='console disconnected', ltype=log.DataTypes.event,
|
||||
event=log.Events.consoledisconnect)
|
||||
self._console.close()
|
||||
await self._console.close()
|
||||
self._console = None
|
||||
self.connectstate = 'unconnected'
|
||||
self._send_rcpts({'connectstate': self.connectstate})
|
||||
await self._send_rcpts({'connectstate': self.connectstate})
|
||||
|
||||
def _ondemand(self):
|
||||
async def _ondemand(self):
|
||||
self._isondemand = True
|
||||
if not self.livesessions and self._console:
|
||||
self._disconnect()
|
||||
await self._disconnect()
|
||||
|
||||
def _connect(self):
|
||||
if not self._is_local:
|
||||
return
|
||||
if self.connectionthread:
|
||||
self.connectionthread.kill()
|
||||
self.connectionthread.cancel()
|
||||
self.connectionthread = None
|
||||
self.connectionthread = eventlet.spawn(self._connect_backend)
|
||||
self.connectionthread = tasks.spawn_task(self._connect_backend())
|
||||
|
||||
def _connect_backend(self):
|
||||
async def _connect_backend(self):
|
||||
if self._console:
|
||||
self._console.close()
|
||||
await self._console.close()
|
||||
self._console = None
|
||||
self.connectstate = 'connecting'
|
||||
self._send_rcpts({'connectstate': self.connectstate})
|
||||
await self._send_rcpts({'connectstate': self.connectstate})
|
||||
if self.reconnect:
|
||||
self.reconnect.cancel()
|
||||
self.reconnect = None
|
||||
@@ -345,9 +346,11 @@ class ConsoleHandler(object):
|
||||
'not configured,\r\nset it to a valid value for console '
|
||||
'function')
|
||||
try:
|
||||
self._console = list(plugin.handle_path(
|
||||
self._plugin_path.format(self.node),
|
||||
"create", self.cfgmgr))[0]
|
||||
consoles = await plugin.handle_path(
|
||||
self._plugin_path.format(self.node),
|
||||
"create", self.cfgmgr)
|
||||
async for cns in consoles:
|
||||
self._console = cns
|
||||
except (exc.NotImplementedException, exc.NotFoundException):
|
||||
self._console = None
|
||||
except Exception as e:
|
||||
@@ -358,21 +361,21 @@ class ConsoleHandler(object):
|
||||
else:
|
||||
print(traceback.format_exc())
|
||||
if not isinstance(self._console, conapi.Console):
|
||||
self.clearbuffer()
|
||||
await self.clearbuffer()
|
||||
self.connectstate = 'unconnected'
|
||||
self.error = 'misconfigured'
|
||||
self._send_rcpts({'connectstate': self.connectstate,
|
||||
await self._send_rcpts({'connectstate': self.connectstate,
|
||||
'error': self.error})
|
||||
self.feedbuffer(
|
||||
await self.feedbuffer(
|
||||
'\x1bc\x1b[2J\x1b[1;1H[{0}]'.format(strerror))
|
||||
self._send_rcpts(
|
||||
await self._send_rcpts(
|
||||
'\x1bc\x1b[2J\x1b[1;1H[{0}]'.format(strerror))
|
||||
self.clearerror = True
|
||||
return
|
||||
if self.clearerror:
|
||||
self.clearerror = False
|
||||
self.clearbuffer()
|
||||
self._send_rcpts(b'\x1bc\x1b[2J\x1b[1;1H')
|
||||
await self.clearbuffer()
|
||||
await self._send_rcpts(b'\x1bc\x1b[2J\x1b[1;1H')
|
||||
self.send_break = self._console.send_break
|
||||
self.resize = self._console.resize
|
||||
if self._attribwatcher:
|
||||
@@ -387,66 +390,66 @@ class ConsoleHandler(object):
|
||||
(self.node,), attribstowatch, self._attribschanged)
|
||||
try:
|
||||
self.resize(width=self.initsize[0], height=self.initsize[1])
|
||||
self._console.connect(self.get_console_output)
|
||||
await self._console.connect(self.get_console_output)
|
||||
except exc.TargetEndpointBadCredentials:
|
||||
self.clearbuffer()
|
||||
await self.clearbuffer()
|
||||
self.error = 'badcredentials'
|
||||
self.connectstate = 'unconnected'
|
||||
self._send_rcpts({'connectstate': self.connectstate,
|
||||
await self._send_rcpts({'connectstate': self.connectstate,
|
||||
'error': self.error})
|
||||
retrytime = self._get_retry_time()
|
||||
if not self.reconnect:
|
||||
self.reconnect = eventlet.spawn_after(retrytime, self._connect)
|
||||
self.reconnect = tasks.spawn_task_after(retrytime, self._connect)
|
||||
return
|
||||
except (exc.TargetEndpointUnreachable, socket.gaierror) as se:
|
||||
self.clearbuffer()
|
||||
await self.clearbuffer()
|
||||
self.error = 'unreachable'
|
||||
self.connectstate = 'unconnected'
|
||||
self._send_rcpts({'connectstate': self.connectstate,
|
||||
await self._send_rcpts({'connectstate': self.connectstate,
|
||||
'error': self.error})
|
||||
retrytime = self._get_retry_time()
|
||||
if not self.reconnect:
|
||||
self.reconnect = eventlet.spawn_after(retrytime, self._connect)
|
||||
self.reconnect = tasks.spawn_task_after(retrytime, self._connect)
|
||||
return
|
||||
except Exception:
|
||||
self.clearbuffer()
|
||||
await self.clearbuffer()
|
||||
_tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
|
||||
event=log.Events.stacktrace)
|
||||
self.error = 'unknown'
|
||||
self.connectstate = 'unconnected'
|
||||
self._send_rcpts({'connectstate': self.connectstate,
|
||||
await self._send_rcpts({'connectstate': self.connectstate,
|
||||
'error': self.error})
|
||||
retrytime = self._get_retry_time()
|
||||
if not self.reconnect:
|
||||
self.reconnect = eventlet.spawn_after(retrytime, self._connect)
|
||||
self.reconnect = tasks.spawn_task_after(retrytime, self._connect)
|
||||
return
|
||||
self._got_connected()
|
||||
await self._got_connected()
|
||||
|
||||
def _got_connected(self):
|
||||
async def _got_connected(self):
|
||||
self.connectstate = 'connected'
|
||||
self._retrytime = 0
|
||||
self.log(
|
||||
logdata='console connected', ltype=log.DataTypes.event,
|
||||
event=log.Events.consoleconnect)
|
||||
self._send_rcpts({'connectstate': self.connectstate})
|
||||
await self._send_rcpts({'connectstate': self.connectstate})
|
||||
|
||||
def _got_disconnected(self):
|
||||
async def _got_disconnected(self):
|
||||
if self.connectstate != 'unconnected':
|
||||
self._console.close()
|
||||
await self._console.close()
|
||||
self.connectstate = 'unconnected'
|
||||
self.log(
|
||||
logdata='console disconnected', ltype=log.DataTypes.event,
|
||||
event=log.Events.consoledisconnect)
|
||||
self._send_rcpts({'connectstate': self.connectstate})
|
||||
await self._send_rcpts({'connectstate': self.connectstate})
|
||||
if self._isalive:
|
||||
self._connect()
|
||||
else:
|
||||
self.clearbuffer()
|
||||
await self.clearbuffer()
|
||||
|
||||
def close(self):
|
||||
async def close(self):
|
||||
self._isalive = False
|
||||
self._send_rcpts({'deleting': True})
|
||||
self._disconnect()
|
||||
await self._send_rcpts({'deleting': True})
|
||||
await self._disconnect()
|
||||
if self._console:
|
||||
|
||||
self._console.close()
|
||||
@@ -458,12 +461,12 @@ class ConsoleHandler(object):
|
||||
self.cfgmgr.remove_watcher(self._attribwatcher)
|
||||
self._attribwatcher = None
|
||||
|
||||
def get_console_output(self, data):
|
||||
async def get_console_output(self, data):
|
||||
# Spawn as a greenthread, return control as soon as possible
|
||||
# to the console object
|
||||
eventlet.spawn(self._handle_console_output, data)
|
||||
await self._handle_console_output(data)
|
||||
|
||||
def attachsession(self, session):
|
||||
async def attachsession(self, session):
|
||||
edata = 1
|
||||
for currsession in self.livesessions:
|
||||
if currsession.username == session.username:
|
||||
@@ -473,7 +476,7 @@ class ConsoleHandler(object):
|
||||
self.log(
|
||||
logdata=session.username, ltype=log.DataTypes.event,
|
||||
event=log.Events.clientconnect, eventdata=edata)
|
||||
self._send_rcpts({'clientcount': len(self.livesessions)})
|
||||
await self._send_rcpts({'clientcount': len(self.livesessions)})
|
||||
if self.connectstate == 'unconnected':
|
||||
# if console is not connected, take time to try to assert
|
||||
# connectivity now.
|
||||
@@ -482,11 +485,11 @@ class ConsoleHandler(object):
|
||||
self.reconnect.cancel()
|
||||
self.reconnect = None
|
||||
self.connectstate = 'connecting'
|
||||
eventlet.spawn(self._connect)
|
||||
self._connect()
|
||||
|
||||
|
||||
|
||||
def detachsession(self, session):
|
||||
async def detachsession(self, session):
|
||||
edata = 0
|
||||
self.livesessions.discard(session)
|
||||
for currsession in self.livesessions:
|
||||
@@ -497,18 +500,18 @@ class ConsoleHandler(object):
|
||||
self.log(
|
||||
logdata=session.username, ltype=log.DataTypes.event,
|
||||
event=log.Events.clientdisconnect, eventdata=edata)
|
||||
self._send_rcpts({'clientcount': len(self.livesessions)})
|
||||
await self._send_rcpts({'clientcount': len(self.livesessions)})
|
||||
if self._isondemand and not self.livesessions:
|
||||
self._disconnect()
|
||||
await self._disconnect()
|
||||
|
||||
|
||||
def reopen(self):
|
||||
self._got_disconnected()
|
||||
async def reopen(self):
|
||||
await self._got_disconnected()
|
||||
|
||||
def _handle_console_output(self, data):
|
||||
async def _handle_console_output(self, data):
|
||||
if type(data) == int:
|
||||
if data == conapi.ConsoleEvent.Disconnect:
|
||||
self._got_disconnected()
|
||||
await self._got_disconnected()
|
||||
return
|
||||
elif data in (b'', u''):
|
||||
# ignore empty strings from a cconsole provider
|
||||
@@ -522,23 +525,24 @@ class ConsoleHandler(object):
|
||||
if self.clearpending or self.clearerror:
|
||||
self.clearpending = False
|
||||
self.clearerror = False
|
||||
self.feedbuffer(b'\x1bc\x1b[2J\x1b[1;1H')
|
||||
self._send_rcpts(b'\x1bc\x1b[2J\x1b[1;1H')
|
||||
self._send_rcpts(_utf8_normalize(data, self.utf8decoder))
|
||||
await self.feedbuffer(b'\x1bc\x1b[2J\x1b[1;1H')
|
||||
await self._send_rcpts(b'\x1bc\x1b[2J\x1b[1;1H')
|
||||
await self._send_rcpts(_utf8_normalize(data, self.utf8decoder))
|
||||
self.log(data, eventdata=eventdata)
|
||||
self.lasttime = util.monotonic_time()
|
||||
self.feedbuffer(data)
|
||||
await self.feedbuffer(data)
|
||||
|
||||
|
||||
def _send_rcpts(self, data):
|
||||
async def _send_rcpts(self, data):
|
||||
for rcpt in list(self.livesessions):
|
||||
try:
|
||||
rcpt.data_handler(data)
|
||||
except: # No matter the reason, advance to next recipient
|
||||
await rcpt.data_handler(data)
|
||||
except Exception as e: # No matter the reason, advance to next recipient
|
||||
print(repr(e))
|
||||
_tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
|
||||
event=log.Events.stacktrace)
|
||||
|
||||
def get_recent(self):
|
||||
async def get_recent(self):
|
||||
"""Retrieve 'recent' data
|
||||
|
||||
Replay data in the intent to perhaps reproduce the display.
|
||||
@@ -551,39 +555,41 @@ class ConsoleHandler(object):
|
||||
'clientcount': len(self.livesessions),
|
||||
}
|
||||
nodeid = self.termprefix + self.node
|
||||
retdata = get_buffer_output(nodeid)
|
||||
retdata = await get_buffer_output(nodeid)
|
||||
return retdata, connstate
|
||||
|
||||
def write(self, data):
|
||||
async def write(self, data):
|
||||
if self.connectstate == 'connected':
|
||||
try:
|
||||
if isinstance(data, str) and not isinstance(data, bytes):
|
||||
data = data.encode('utf-8')
|
||||
self._console.write(data)
|
||||
await self._console.write(data)
|
||||
except Exception:
|
||||
_tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
|
||||
event=log.Events.stacktrace)
|
||||
self._got_disconnected()
|
||||
await self._got_disconnected()
|
||||
|
||||
|
||||
def disconnect_node(node, configmanager):
|
||||
async def disconnect_node(node, configmanager):
|
||||
consk = (node, configmanager.tenant)
|
||||
if consk in _handled_consoles:
|
||||
_handled_consoles[consk].close()
|
||||
await _handled_consoles[consk].close()
|
||||
del _handled_consoles[consk]
|
||||
|
||||
|
||||
def _nodechange(added, deleting, renamed, configmanager):
|
||||
async def _replace_node(old, new, cfm):
|
||||
await disconnect_node(old, cfm)
|
||||
await connect_node(new, cfm)
|
||||
for node in deleting:
|
||||
eventlet.spawn(disconnect_node, node, configmanager)
|
||||
tasks.spawn(disconnect_node(node, configmanager))
|
||||
for node in renamed:
|
||||
disconnect_node(node, configmanager)
|
||||
eventlet.spawn(connect_node, renamed[node], configmanager)
|
||||
tasks.spawn(_replace_node(node, renamed[node], configmanager))
|
||||
for node in added:
|
||||
eventlet.spawn(connect_node, node, configmanager)
|
||||
tasks.spawn(connect_node(node, configmanager))
|
||||
|
||||
|
||||
def _start_tenant_sessions(cfm):
|
||||
async def _start_tenant_sessions(cfm):
|
||||
nodeattrs = cfm.get_node_attributes(cfm.list_nodes(), 'collective.manager')
|
||||
for node in nodeattrs:
|
||||
manager = nodeattrs[node].get('collective.manager', {}).get('value',
|
||||
@@ -591,23 +597,26 @@ def _start_tenant_sessions(cfm):
|
||||
if manager and collective.get_myname() != manager:
|
||||
continue
|
||||
try:
|
||||
connect_node(node, cfm)
|
||||
await connect_node(node, cfm)
|
||||
except:
|
||||
_tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
|
||||
event=log.Events.stacktrace)
|
||||
cfm.watch_nodecollection(_nodechange)
|
||||
|
||||
|
||||
def initialize():
|
||||
async def initialize():
|
||||
global _tracelog
|
||||
global _bufferdaemon
|
||||
_tracelog = log.Logger('trace')
|
||||
_bufferdaemon = subprocess.Popen(
|
||||
['/opt/confluent/bin/vtbufferd', 'confluent-vtbuffer'], bufsize=0, stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.DEVNULL)
|
||||
_bufferdaemon = await asyncio.subprocess.create_subprocess_exec(
|
||||
'/opt/confluent/bin/vtbufferd', 'confluent-vtbuffer')
|
||||
#_bufferdaemon = subprocess.Popen(
|
||||
# ['/opt/confluent/bin/vtbufferd', 'confluent-vtbuffer'], bufsize=0, stdin=subprocess.DEVNULL,
|
||||
# stdout=subprocess.DEVNULL)
|
||||
|
||||
def start_console_sessions():
|
||||
configmodule.hook_new_configmanagers(_start_tenant_sessions)
|
||||
|
||||
async def start_console_sessions():
|
||||
await configmodule.hook_new_configmanagers(_start_tenant_sessions)
|
||||
|
||||
|
||||
def connect_node(node, configmanager, username=None, direct=True, width=80,
|
||||
@@ -654,34 +663,36 @@ class ProxyConsole(object):
|
||||
self.clisession.detach()
|
||||
self.clisession = None
|
||||
|
||||
def relay_data(self):
|
||||
data = tlvdata.recv(self.remote)
|
||||
async def relay_data(self):
|
||||
data = await tlvdata.recv(self.remote)
|
||||
while data:
|
||||
self.data_handler(data)
|
||||
data = tlvdata.recv(self.remote)
|
||||
self.remote.close()
|
||||
await self.data_handler(data)
|
||||
data = await tlvdata.recv(self.remote)
|
||||
self.remote[1].close()
|
||||
|
||||
def get_buffer_age(self):
|
||||
# the server sends a buffer age if appropriate, no need to handle
|
||||
# it explicitly in the proxy instance
|
||||
return False
|
||||
|
||||
def get_recent(self):
|
||||
async def get_recent(self):
|
||||
# Again, delegate this to the remote collective member
|
||||
self.skipreplay = False
|
||||
return b''
|
||||
|
||||
def write(self, data):
|
||||
async def write(self, data):
|
||||
# Relay data to the collective manager
|
||||
try:
|
||||
tlvdata.send(self.remote, data)
|
||||
except Exception:
|
||||
await tlvdata.send(self.remote, data)
|
||||
except Exception as e:
|
||||
print(repr(e))
|
||||
raise
|
||||
if self.clisession:
|
||||
self.clisession.detach()
|
||||
await self.clisession.detach()
|
||||
self.clisession = None
|
||||
|
||||
|
||||
def attachsession(self, session):
|
||||
async def attachsession(self, session):
|
||||
self.clisession = session
|
||||
self.data_handler = session.data_handler
|
||||
termreq = {
|
||||
@@ -698,33 +709,26 @@ class ProxyConsole(object):
|
||||
},
|
||||
}
|
||||
try:
|
||||
remote = socket.create_connection((self.managerinfo['address'], 13001))
|
||||
remote = ssl.wrap_socket(remote, cert_reqs=ssl.CERT_NONE,
|
||||
keyfile='/etc/confluent/privkey.pem',
|
||||
certfile='/etc/confluent/srvcert.pem')
|
||||
if not util.cert_matches(self.managerinfo['fingerprint'],
|
||||
remote.getpeercert(binary_form=True)):
|
||||
raise Exception('Invalid peer certificate')
|
||||
remote = await collective.connect_to_collective(None, self.managerinfo['address'])
|
||||
except Exception as e:
|
||||
if _tracelog:
|
||||
_tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
|
||||
event=log.Events.stacktrace)
|
||||
eventlet.sleep(3)
|
||||
_tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event, event=log.Events.stacktrace)
|
||||
await asyncio.sleep(3)
|
||||
if self.clisession:
|
||||
self.clisession.detach(False)
|
||||
self.detachsession(None)
|
||||
await self.clisession.detach(False)
|
||||
await self.detachsession(None)
|
||||
return
|
||||
tlvdata.recv(remote)
|
||||
tlvdata.recv(remote)
|
||||
tlvdata.send(remote, termreq)
|
||||
await tlvdata.recv(remote)
|
||||
await tlvdata.recv(remote)
|
||||
await tlvdata.send(remote, termreq)
|
||||
self.remote = remote
|
||||
eventlet.spawn(self.relay_data)
|
||||
tasks.spawn(self.relay_data())
|
||||
|
||||
def detachsession(self, session):
|
||||
async def detachsession(self, session):
|
||||
# we will disappear, so just let that happen...
|
||||
if self.remote:
|
||||
try:
|
||||
tlvdata.send(self.remote, {'operation': 'stop'})
|
||||
await tlvdata.send(self.remote, {'operation': 'stop'})
|
||||
except Exception:
|
||||
pass
|
||||
self.clisession = None
|
||||
@@ -757,8 +761,7 @@ class ConsoleSession(object):
|
||||
:param configmanager: A configuration manager object for current context
|
||||
:param username: Username for which this session object will operate
|
||||
:param datacallback: An asynchronous data handler, to be called when data
|
||||
is available. Note that if passed, it makes
|
||||
'get_next_output' non-functional
|
||||
is available.
|
||||
:param skipreplay: If true, will skip the attempt to redraw the screen
|
||||
"""
|
||||
|
||||
@@ -780,19 +783,22 @@ class ConsoleSession(object):
|
||||
self._evt = None
|
||||
self.node = node
|
||||
self.write = self.conshdl.write
|
||||
tasks.spawn(self.delayinit(datacallback, skipreplay))
|
||||
|
||||
async def delayinit(self, datacallback, skipreplay):
|
||||
if datacallback is None:
|
||||
self.reaper = eventlet.spawn_after(15, self.destroy)
|
||||
self.reaper = tasks.spawn_task_after(15, self.destroy)
|
||||
self.databuffer = collections.deque([])
|
||||
self.data_handler = self.got_data
|
||||
if not skipreplay:
|
||||
self.databuffer.extend(self.conshdl.get_recent())
|
||||
self.databuffer.extend(await self.conshdl.get_recent())
|
||||
else:
|
||||
self.data_handler = datacallback
|
||||
if not skipreplay:
|
||||
for recdata in self.conshdl.get_recent():
|
||||
for recdata in await self.conshdl.get_recent():
|
||||
if recdata:
|
||||
datacallback(recdata)
|
||||
self.conshdl.attachsession(self)
|
||||
await datacallback(recdata)
|
||||
await self.conshdl.attachsession(self)
|
||||
|
||||
|
||||
def connect_session(self):
|
||||
@@ -819,7 +825,7 @@ class ConsoleSession(object):
|
||||
Returns False if no data buffered yet"""
|
||||
return self.conshdl.get_buffer_age()
|
||||
|
||||
def reopen(self):
|
||||
async def reopen(self):
|
||||
"""Reopen the session
|
||||
|
||||
This can be useful if there is suspicion that the remote console is
|
||||
@@ -828,27 +834,27 @@ class ConsoleSession(object):
|
||||
automatically detecting an unusable console in the underlying
|
||||
technology that cannot be unambiguously autodetected.
|
||||
"""
|
||||
self.conshdl.reopen()
|
||||
await self.conshdl.reopen()
|
||||
|
||||
def destroy(self):
|
||||
async def destroy(self):
|
||||
if self.registered:
|
||||
self.conshdl.detachsession(self)
|
||||
await self.conshdl.detachsession(self)
|
||||
if self._evt:
|
||||
self._evt.send()
|
||||
self._evt = None
|
||||
self.reghdl = None
|
||||
|
||||
def detach(self, reattach=True):
|
||||
async def detach(self, reattach=True):
|
||||
"""Handler for the console handler to detach so it can reattach,
|
||||
currently to facilitate changing from one collective.manager to
|
||||
another
|
||||
|
||||
:return:
|
||||
"""
|
||||
self.conshdl.detachsession(self)
|
||||
await self.conshdl.detachsession(self)
|
||||
if reattach:
|
||||
self.connect_session()
|
||||
self.conshdl.attachsession(self)
|
||||
await self.conshdl.attachsession(self)
|
||||
self.write = self.conshdl.write
|
||||
|
||||
def got_data(self, data):
|
||||
@@ -863,32 +869,3 @@ class ConsoleSession(object):
|
||||
if self._evt:
|
||||
self._evt.send()
|
||||
self._evt = None
|
||||
|
||||
def get_next_output(self, timeout=45):
|
||||
"""Poll for next available output on this console.
|
||||
|
||||
Ideally purely event driven scheme is perfect. AJAX over HTTP is
|
||||
at least one case where we don't have that luxury. This function
|
||||
will not work if the session was initialized with a data callback
|
||||
instead of polling mode.
|
||||
"""
|
||||
self.reaper.cancel()
|
||||
# postpone death to be 15 seconds after this would timeout
|
||||
self.reaper = eventlet.spawn_after(timeout + 15, self.destroy)
|
||||
if self._evt:
|
||||
raise Exception('get_next_output is not re-entrant')
|
||||
if not self.databuffer:
|
||||
self._evt = eventlet.event.Event()
|
||||
with eventlet.Timeout(timeout, False):
|
||||
self._evt.wait()
|
||||
self._evt = None
|
||||
if not self.databuffer:
|
||||
return ""
|
||||
currdata = self.databuffer.popleft()
|
||||
if isinstance(currdata, dict):
|
||||
return currdata
|
||||
retval = currdata
|
||||
while self.databuffer and not isinstance(self.databuffer[0], dict):
|
||||
retval += self.databuffer.popleft()
|
||||
|
||||
return retval
|
||||
|
||||
+167
-123
@@ -33,10 +33,11 @@
|
||||
# functions. Console is special and just get's passed through
|
||||
# see API.txt
|
||||
|
||||
import asyncio
|
||||
import confluent
|
||||
import confluent.alerts as alerts
|
||||
import confluent.log as log
|
||||
import confluent.tlvdata as tlvdata
|
||||
import confluent.asynctlvdata as tlvdata
|
||||
import confluent.config.attributes as attrscheme
|
||||
import confluent.config.configmanager as cfm
|
||||
import confluent.collective.manager as collective
|
||||
@@ -49,25 +50,17 @@ import confluent.networking.macmap as macmap
|
||||
import confluent.noderange as noderange
|
||||
import confluent.osimage as osimage
|
||||
import confluent.plugin as plugin
|
||||
import types
|
||||
try:
|
||||
import confluent.shellmodule as shellmodule
|
||||
except ImportError:
|
||||
pass
|
||||
try:
|
||||
import OpenSSL.crypto as crypto
|
||||
except ImportError:
|
||||
# Only required for collective mode
|
||||
crypto = None
|
||||
import confluent.tasks as tasks
|
||||
import confluent.util as util
|
||||
import eventlet
|
||||
import eventlet.greenpool as greenpool
|
||||
import eventlet.green.ssl as ssl
|
||||
import eventlet.queue as queue
|
||||
import eventlet.semaphore as semaphore
|
||||
import inspect
|
||||
import itertools
|
||||
import msgpack
|
||||
import os
|
||||
import eventlet.green.socket as socket
|
||||
import struct
|
||||
import sys
|
||||
import uuid
|
||||
@@ -76,7 +69,7 @@ import shutil
|
||||
|
||||
vinz = None
|
||||
pluginmap = {}
|
||||
dispatch_plugins = (b'ipmi', u'ipmi', b'redfish', u'redfish', b'tsmsol', u'tsmsol', b'geist', u'geist', b'deltapdu', u'deltapdu', b'eatonpdu', u'eatonpdu', b'affluent', u'affluent', b'cnos', u'cnos', b'enos', u'enos')
|
||||
dispatch_plugins = (b'remoteconfig', b'ipmi', u'ipmi', b'redfish', u'redfish', b'tsmsol', u'tsmsol', b'geist', u'geist', b'deltapdu', u'deltapdu', b'eatonpdu', u'eatonpdu', b'raritan', u'raritan', b'affluent', u'affluent', b'cnos', u'cnos', b'enos', u'enos')
|
||||
|
||||
PluginCollection = plugin.PluginCollection
|
||||
|
||||
@@ -85,6 +78,25 @@ try:
|
||||
except NameError:
|
||||
unicode = str
|
||||
|
||||
|
||||
async def iterate_responses(responses):
|
||||
# normalize plugin behaviors
|
||||
# First, take care of whatever potentially nested levels of awaitables
|
||||
# Then handle async generators, generators, then just general iterable types
|
||||
while inspect.isawaitable(responses):
|
||||
responses = await responses
|
||||
if inspect.isasyncgen(responses):
|
||||
async for rsp in responses:
|
||||
yield rsp
|
||||
return
|
||||
elif inspect.isgenerator(responses) or isinstance(responses, list) or isinstance(responses, tuple):
|
||||
for rsp in responses:
|
||||
yield rsp
|
||||
return
|
||||
for rsp in responses:
|
||||
yield rsp
|
||||
|
||||
|
||||
def seek_element(currplace, currkey, depth):
|
||||
try:
|
||||
return currplace[currkey]
|
||||
@@ -175,15 +187,15 @@ class PluginRoute(object):
|
||||
|
||||
|
||||
|
||||
def handle_storage(configmanager, inputdata, pathcomponents, operation):
|
||||
async def handle_storage(configmanager, inputdata, pathcomponents, operation):
|
||||
if len(pathcomponents) == 1:
|
||||
yield msg.ChildCollection('remote/')
|
||||
return
|
||||
if pathcomponents[1] == 'remote':
|
||||
for rsp in mountmanager.handle_request(configmanager, inputdata, pathcomponents[2:], operation):
|
||||
async for rsp in mountmanager.handle_request(configmanager, inputdata, pathcomponents[2:], operation):
|
||||
yield rsp
|
||||
|
||||
def handle_deployment(configmanager, inputdata, pathcomponents,
|
||||
async def handle_deployment(configmanager, inputdata, pathcomponents,
|
||||
operation):
|
||||
if len(pathcomponents) == 1:
|
||||
yield msg.ChildCollection('distributions/')
|
||||
@@ -221,12 +233,12 @@ def handle_deployment(configmanager, inputdata, pathcomponents,
|
||||
yield msg.ChildCollection('info')
|
||||
if operation == 'update':
|
||||
if 'updateboot' in inputdata:
|
||||
osimage.update_boot(profname)
|
||||
await osimage.update_boot(profname)
|
||||
yield msg.KeyValueData({'updated': profname})
|
||||
return
|
||||
elif 'rebase' in inputdata:
|
||||
try:
|
||||
updated, customized = osimage.rebase_profile(profname)
|
||||
updated, customized = await osimage.rebase_profile(profname)
|
||||
except osimage.ManifestMissing:
|
||||
raise exc.InvalidArgumentException('Specified profile {0} does not have a manifest.yaml for rebase'.format(profname))
|
||||
for upd in updated:
|
||||
@@ -236,7 +248,8 @@ def handle_deployment(configmanager, inputdata, pathcomponents,
|
||||
return
|
||||
if pathcomponents[1] == 'fingerprint':
|
||||
if operation == 'create':
|
||||
importer = osimage.MediaImporter(inputdata['filename'], configmanager, checkonly=True)
|
||||
importer = osimage.MediaImporter()
|
||||
await importer.init(inputdata['filename'], configmanager, checkonly=True)
|
||||
medinfo = {
|
||||
'targetpath': importer.targpath,
|
||||
'name': importer.osname,
|
||||
@@ -253,10 +266,12 @@ def handle_deployment(configmanager, inputdata, pathcomponents,
|
||||
return
|
||||
elif operation == 'create':
|
||||
if inputdata.get('custname', None):
|
||||
importer = osimage.MediaImporter(inputdata['filename'],
|
||||
importer = osimage.MediaImporter()
|
||||
await importer.init(inputdata['filename'],
|
||||
configmanager, inputdata['custname'])
|
||||
else:
|
||||
importer = osimage.MediaImporter(inputdata['filename'],
|
||||
importer = osimage.MediaImporter()
|
||||
await importer.init(inputdata['filename'],
|
||||
configmanager)
|
||||
yield msg.KeyValueData({'target': importer.targpath,
|
||||
'name': importer.importkey})
|
||||
@@ -300,6 +315,20 @@ def _init_core():
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
},
|
||||
'certificate': {
|
||||
'sign': PluginRoute({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
'generate_csr': PluginRoute({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
'install': PluginRoute({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
}),
|
||||
},
|
||||
'certificate_authorities': PluginCollection({
|
||||
'pluginattrs': ['hardwaremanagement.method'],
|
||||
'default': 'ipmi',
|
||||
@@ -462,7 +491,15 @@ def _init_core():
|
||||
}),
|
||||
'ident_image': PluginRoute({
|
||||
'handler': 'identimage'
|
||||
})
|
||||
}),
|
||||
'remote_config': {
|
||||
'run': PluginRoute({
|
||||
'handler': 'remoteconfig'
|
||||
}),
|
||||
'active': PluginCollection({
|
||||
'handler': 'remoteconfig'
|
||||
}),
|
||||
},
|
||||
},
|
||||
'events': {
|
||||
'hardware': {
|
||||
@@ -620,7 +657,7 @@ def _init_core():
|
||||
}
|
||||
|
||||
|
||||
def create_user(inputdata, configmanager):
|
||||
async def create_user(inputdata, configmanager):
|
||||
try:
|
||||
username = inputdata['name']
|
||||
del inputdata['name']
|
||||
@@ -628,10 +665,10 @@ def create_user(inputdata, configmanager):
|
||||
del inputdata['role']
|
||||
except (KeyError, ValueError):
|
||||
raise exc.InvalidArgumentException('Missing user name or role')
|
||||
configmanager.create_user(username, role, attributemap=inputdata)
|
||||
await configmanager.create_user(username, role, attributemap=inputdata)
|
||||
|
||||
|
||||
def create_usergroup(inputdata, configmanager):
|
||||
async def create_usergroup(inputdata, configmanager):
|
||||
try:
|
||||
groupname = inputdata['name']
|
||||
role = inputdata['role']
|
||||
@@ -639,18 +676,18 @@ def create_usergroup(inputdata, configmanager):
|
||||
del inputdata['role']
|
||||
except (KeyError, ValueError):
|
||||
raise exc.InvalidArgumentException("Missing user name or role")
|
||||
configmanager.create_usergroup(groupname, role)
|
||||
await configmanager.create_usergroup(groupname, role)
|
||||
|
||||
|
||||
def update_usergroup(groupname, attribmap, configmanager):
|
||||
async def update_usergroup(groupname, attribmap, configmanager):
|
||||
try:
|
||||
configmanager.set_usergroup(groupname, attribmap)
|
||||
await configmanager.set_usergroup(groupname, attribmap)
|
||||
except ValueError as e:
|
||||
raise exc.InvalidArgumentException(str(e))
|
||||
|
||||
def update_user(name, attribmap, configmanager):
|
||||
async def update_user(name, attribmap, configmanager):
|
||||
try:
|
||||
configmanager.set_user(name, attribmap)
|
||||
await configmanager.set_user(name, attribmap)
|
||||
except ValueError as e:
|
||||
raise exc.InvalidArgumentException(str(e))
|
||||
|
||||
@@ -682,22 +719,25 @@ def show_user(name, configmanager):
|
||||
|
||||
|
||||
|
||||
def stripnode(iterablersp, node):
|
||||
for i in iterablersp:
|
||||
async def stripnode(iterablersp, node):
|
||||
async for i in iterate_responses(iterablersp):
|
||||
if i is None:
|
||||
raise exc.NotImplementedException("Not Implemented")
|
||||
if isinstance(i, console.Console):
|
||||
yield i
|
||||
continue
|
||||
i.strip_node(node)
|
||||
yield i
|
||||
|
||||
|
||||
def iterate_collections(iterable, forcecollection=True):
|
||||
async def iterate_collections(iterable, forcecollection=True):
|
||||
for coll in iterable:
|
||||
if forcecollection and coll[-1] != '/':
|
||||
coll += '/'
|
||||
yield msg.ChildCollection(coll, candelete=True)
|
||||
|
||||
|
||||
def iterate_resources(fancydict):
|
||||
async def iterate_resources(fancydict):
|
||||
for resource in fancydict:
|
||||
if resource.startswith("_"):
|
||||
continue
|
||||
@@ -726,12 +766,12 @@ def delete_nodegroup_collection(collectionpath, configmanager):
|
||||
raise Exception("Not implemented")
|
||||
|
||||
|
||||
def delete_node_collection(collectionpath, configmanager, isnoderange):
|
||||
async def delete_node_collection(collectionpath, configmanager, isnoderange):
|
||||
if len(collectionpath) == 2: # just node
|
||||
nodes = [collectionpath[-1]]
|
||||
if isnoderange:
|
||||
nodes = noderange.NodeRange(nodes[0], configmanager).nodes
|
||||
configmanager.del_nodes(nodes)
|
||||
await configmanager.del_nodes(nodes)
|
||||
for node in nodes:
|
||||
yield msg.DeletedResource(node)
|
||||
else:
|
||||
@@ -782,7 +822,7 @@ def create_group(inputdata, configmanager):
|
||||
yield msg.CreatedResource(groupname)
|
||||
|
||||
|
||||
def create_node(inputdata, configmanager):
|
||||
async def create_node(inputdata, configmanager):
|
||||
try:
|
||||
nodename = inputdata['name']
|
||||
if ' ' in nodename:
|
||||
@@ -792,13 +832,13 @@ def create_node(inputdata, configmanager):
|
||||
except KeyError:
|
||||
raise exc.InvalidArgumentException('name not specified')
|
||||
try:
|
||||
configmanager.add_node_attributes(attribmap)
|
||||
await configmanager.add_node_attributes(attribmap)
|
||||
except ValueError as e:
|
||||
raise exc.InvalidArgumentException(str(e))
|
||||
yield msg.CreatedResource(nodename)
|
||||
|
||||
|
||||
def create_noderange(inputdata, configmanager):
|
||||
async def create_noderange(inputdata, configmanager):
|
||||
try:
|
||||
noder = inputdata['name']
|
||||
del inputdata['name']
|
||||
@@ -808,7 +848,7 @@ def create_noderange(inputdata, configmanager):
|
||||
except KeyError:
|
||||
raise exc.InvalidArgumentException('name not specified')
|
||||
try:
|
||||
configmanager.add_node_attributes(attribmap)
|
||||
await configmanager.add_node_attributes(attribmap)
|
||||
except ValueError as e:
|
||||
raise exc.InvalidArgumentException(str(e))
|
||||
for node in attribmap:
|
||||
@@ -816,7 +856,7 @@ def create_noderange(inputdata, configmanager):
|
||||
|
||||
|
||||
|
||||
def enumerate_collections(collections):
|
||||
async def enumerate_collections(collections):
|
||||
for collection in collections:
|
||||
yield msg.ChildCollection(collection)
|
||||
|
||||
@@ -896,14 +936,13 @@ def abbreviate_noderange(configmanager, inputdata, operation):
|
||||
return (msg.KeyValueData({'noderange': noderange.ReverseNodeRange(inputdata['nodes'], configmanager).noderange}),)
|
||||
|
||||
|
||||
def _keepalivefn(connection, xmitlock):
|
||||
async def _keepalivefn(connection, xmitlock):
|
||||
while True:
|
||||
eventlet.sleep(30)
|
||||
with xmitlock:
|
||||
await asyncio.sleep(30)
|
||||
async with xmitlock:
|
||||
connection.sendall(b'\x00\x00\x00\x00\x00\x00\x00\x01\x00')
|
||||
|
||||
def handle_dispatch(connection, cert, dispatch, peername):
|
||||
cert = crypto.dump_certificate(crypto.FILETYPE_ASN1, cert)
|
||||
async def handle_dispatch(connection, cert, dispatch, peername):
|
||||
if not util.cert_matches(
|
||||
cfm.get_collective_member(peername)['fingerprint'], cert):
|
||||
connection.close()
|
||||
@@ -912,10 +951,11 @@ def handle_dispatch(connection, cert, dispatch, peername):
|
||||
# We only support msgpack now
|
||||
# The magic should preclude any pickle, as the first byte can never be
|
||||
# under 0x20 or so.
|
||||
connection.close()
|
||||
connection[1].close()
|
||||
await connection[1].wait_closed()
|
||||
return
|
||||
xmitlock = semaphore.Semaphore()
|
||||
keepalive = eventlet.spawn(_keepalivefn, connection, xmitlock)
|
||||
xmitlock = asyncio.Lock()
|
||||
keepalive = tasks.spawn_task(_keepalivefn(connection, xmitlock))
|
||||
dispatch = msgpack.unpackb(dispatch[2:], raw=False)
|
||||
configmanager = cfm.ConfigManager(dispatch['tenant'])
|
||||
nodes = dispatch['nodes']
|
||||
@@ -928,15 +968,16 @@ def handle_dispatch(connection, cert, dispatch, peername):
|
||||
pathcomponents, operation, inputdata, nodes, dispatch['isnoderange'],
|
||||
configmanager)
|
||||
except Exception as res:
|
||||
with xmitlock:
|
||||
_forward_rsp(connection, res)
|
||||
keepalive.kill()
|
||||
connection.sendall('\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
connection.close()
|
||||
async with xmitlock:
|
||||
await _forward_rsp(connection, res)
|
||||
keepalive.cancel()
|
||||
connection[1].write('\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
await connection[1].drain()
|
||||
connection[1].close()
|
||||
await connection[1].wait_closed()
|
||||
return
|
||||
plugroute = routespec.routeinfo
|
||||
nodesbyhandler = {}
|
||||
passvalues = []
|
||||
nodeattr = configmanager.get_node_attributes(
|
||||
nodes, plugroute['pluginattrs'])
|
||||
for node in nodes:
|
||||
@@ -957,25 +998,32 @@ def handle_dispatch(connection, cert, dispatch, peername):
|
||||
else:
|
||||
nodesbyhandler[hfunc] = [node]
|
||||
try:
|
||||
passvalues = asyncio.Queue()
|
||||
numworkers = 0
|
||||
for hfunc in nodesbyhandler:
|
||||
passvalues.append(hfunc(
|
||||
nodes=nodesbyhandler[hfunc], element=pathcomponents,
|
||||
configmanager=configmanager,
|
||||
inputdata=inputdata))
|
||||
for res in itertools.chain(*passvalues):
|
||||
with xmitlock:
|
||||
_forward_rsp(connection, res)
|
||||
numworkers += 1
|
||||
tasks.spawn(addtoqueue(passvalues, hfunc, {
|
||||
'nodes': nodesbyhandler[hfunc],
|
||||
'element': pathcomponents,
|
||||
'configmanager': configmanager,
|
||||
'inputdata': inputdata}))
|
||||
async for res in iterate_queue(numworkers, passvalues):
|
||||
async with xmitlock:
|
||||
await _forward_rsp(connection, res)
|
||||
except Exception as res:
|
||||
with xmitlock:
|
||||
_forward_rsp(connection, res)
|
||||
keepalive.kill()
|
||||
connection.sendall('\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
connection.close()
|
||||
print("oh noes, " + repr(res))
|
||||
async with xmitlock:
|
||||
await _forward_rsp(connection, res)
|
||||
keepalive.cancel()
|
||||
connection[1].write(b'\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||
await connection[1].drain()
|
||||
connection[1].close()
|
||||
await connection[1].wait_closed()
|
||||
|
||||
|
||||
def _forward_rsp(connection, res):
|
||||
async def _forward_rsp(connection, res):
|
||||
try:
|
||||
r = res.serialize()
|
||||
r = res.serialize()
|
||||
except AttributeError:
|
||||
if isinstance(res, Exception):
|
||||
r = msgpack.packb(['Exception', str(res)], use_bin_type=False)
|
||||
@@ -985,16 +1033,18 @@ def _forward_rsp(connection, res):
|
||||
use_bin_type=False)
|
||||
except Exception as e:
|
||||
r = msgpack.packb(
|
||||
['Exception', 'Unable to serialize response ' + repr(res) + ' due to ' + str(e)],
|
||||
use_bin_type=False)
|
||||
['Exception',
|
||||
'Unable to serialize response ' + repr(res) + ' due to ' + str(e)],
|
||||
use_bin_type=False)
|
||||
rlen = len(r)
|
||||
if not rlen:
|
||||
return
|
||||
connection.sendall(struct.pack('!Q', rlen))
|
||||
connection.sendall(r)
|
||||
connection[1].write(struct.pack('!Q', rlen))
|
||||
connection[1].write(r)
|
||||
await connection[1].drain()
|
||||
|
||||
|
||||
def handle_node_request(configmanager, inputdata, operation,
|
||||
async def handle_node_request(configmanager, inputdata, operation,
|
||||
pathcomponents, autostrip=True):
|
||||
global vinz
|
||||
if log.logfull:
|
||||
@@ -1018,7 +1068,7 @@ def handle_node_request(configmanager, inputdata, operation,
|
||||
try:
|
||||
nodeorrange = pathcomponents[1]
|
||||
if not isnoderange and not configmanager.is_node(nodeorrange):
|
||||
raise exc.NotFoundException("Invalid Node")
|
||||
raise exc.NotFoundException(f'Invalid Node: {repr(pathcomponents)}')
|
||||
if isnoderange and not (len(pathcomponents) == 3 and
|
||||
pathcomponents[2] == 'abbreviate'):
|
||||
try:
|
||||
@@ -1070,13 +1120,13 @@ def handle_node_request(configmanager, inputdata, operation,
|
||||
if iscollection:
|
||||
if operation == "delete":
|
||||
return delete_node_collection(pathcomponents, configmanager,
|
||||
isnoderange)
|
||||
isnoderange)
|
||||
elif operation == "retrieve":
|
||||
return enumerate_node_collection(pathcomponents, configmanager)
|
||||
else:
|
||||
raise Exception("TODO here")
|
||||
del pathcomponents[0:2]
|
||||
passvalues = queue.Queue()
|
||||
passvalues = asyncio.Queue()
|
||||
plugroute = routespec.routeinfo
|
||||
_plugin = None
|
||||
|
||||
@@ -1096,8 +1146,6 @@ def handle_node_request(configmanager, inputdata, operation,
|
||||
inputdata=msginputdata)
|
||||
if isnoderange:
|
||||
return passvalue
|
||||
elif isinstance(passvalue, console.Console):
|
||||
return [passvalue]
|
||||
else:
|
||||
return stripnode(passvalue, nodes[0])
|
||||
elif 'pluginattrs' in plugroute:
|
||||
@@ -1144,27 +1192,26 @@ def handle_node_request(configmanager, inputdata, operation,
|
||||
nodesbyhandler[hfunc] = [node]
|
||||
for bn in badcollnodes:
|
||||
nodesbyhandler[BadCollective(bn).error] = [bn]
|
||||
workers = greenpool.GreenPool()
|
||||
numworkers = 0
|
||||
for hfunc in nodesbyhandler:
|
||||
numworkers += 1
|
||||
workers.spawn(addtoqueue, passvalues, hfunc, {'nodes': nodesbyhandler[hfunc],
|
||||
tasks.spawn(addtoqueue(passvalues, hfunc, {'nodes': nodesbyhandler[hfunc],
|
||||
'element': pathcomponents,
|
||||
'configmanager': configmanager,
|
||||
'inputdata': _get_input_data(_plugin, pathcomponents,
|
||||
operation, inputdata,nodes,
|
||||
isnoderange, configmanager)})
|
||||
isnoderange, configmanager)}))
|
||||
for manager in nodesbymanager:
|
||||
numworkers += 1
|
||||
workers.spawn(addtoqueue, passvalues, dispatch_request, {
|
||||
tasks.spawn(addtoqueue(passvalues, dispatch_request, {
|
||||
'nodes': nodesbymanager[manager], 'manager': manager,
|
||||
'element': pathcomponents, 'configmanager': configmanager,
|
||||
'inputdata': inputdata, 'operation': operation, 'isnoderange': isnoderange})
|
||||
'inputdata': inputdata, 'operation': operation, 'isnoderange': isnoderange}))
|
||||
if isnoderange or not autostrip:
|
||||
return iterate_queue(numworkers, passvalues)
|
||||
return iterate_queue(numworkers, passvalues) # [x async for x in iterate_queue(numworkers, passvalues)]
|
||||
else:
|
||||
if numworkers > 0:
|
||||
return iterate_queue(numworkers, passvalues, nodes[0])
|
||||
return iterate_queue(numworkers, passvalues, nodes[0]) # [x async for x in iterate_queue(numworkers, passvalues, nodes[0])]
|
||||
else:
|
||||
raise exc.NotImplementedException()
|
||||
|
||||
@@ -1185,10 +1232,10 @@ def _get_input_data(plugin_ext, pathcomponents, operation, inputdata,
|
||||
nodes, isnoderange,configmanager)
|
||||
|
||||
|
||||
def iterate_queue(numworkers, passvalues, strip=False):
|
||||
async def iterate_queue(numworkers, passvalues, strip=False):
|
||||
completions = 0
|
||||
while completions < numworkers:
|
||||
nv = passvalues.get()
|
||||
nv = await passvalues.get()
|
||||
if nv == 'theend':
|
||||
completions += 1
|
||||
else:
|
||||
@@ -1199,30 +1246,27 @@ def iterate_queue(numworkers, passvalues, strip=False):
|
||||
yield nv
|
||||
|
||||
|
||||
def addtoqueue(theq, fun, kwargs):
|
||||
async def addtoqueue(theq, fun, kwargs):
|
||||
try:
|
||||
result = fun(**kwargs)
|
||||
if isinstance(result, console.Console):
|
||||
theq.put(result)
|
||||
await theq.put(result)
|
||||
else:
|
||||
for pv in result:
|
||||
theq.put(pv)
|
||||
async for pv in iterate_responses(result):
|
||||
await theq.put(pv)
|
||||
except Exception as e:
|
||||
theq.put(e)
|
||||
await theq.put(e)
|
||||
finally:
|
||||
theq.put('theend')
|
||||
await theq.put('theend')
|
||||
|
||||
|
||||
def dispatch_request(nodes, manager, element, configmanager, inputdata,
|
||||
async def dispatch_request(nodes, manager, element, configmanager, inputdata,
|
||||
operation, isnoderange):
|
||||
a = configmanager.get_collective_member(manager)
|
||||
try:
|
||||
remote = socket.create_connection((a['address'], 13001))
|
||||
remote.settimeout(180)
|
||||
remote = ssl.wrap_socket(remote, cert_reqs=ssl.CERT_NONE,
|
||||
keyfile='/etc/confluent/privkey.pem',
|
||||
certfile='/etc/confluent/srvcert.pem')
|
||||
remote = await collective.connect_to_collective(a['fingerprint'], a['address'])
|
||||
except Exception as e:
|
||||
raise
|
||||
for node in nodes:
|
||||
if a:
|
||||
yield msg.ConfluentResourceUnavailable(
|
||||
@@ -1235,10 +1279,7 @@ def dispatch_request(nodes, manager, element, configmanager, inputdata,
|
||||
manager))
|
||||
|
||||
return
|
||||
if not util.cert_matches(a['fingerprint'], remote.getpeercert(
|
||||
binary_form=True)):
|
||||
raise Exception("Invalid certificate on peer")
|
||||
banner = tlvdata.recv(remote)
|
||||
banner = await tlvdata.recv(remote)
|
||||
vers = banner.split()[2]
|
||||
if vers == b'v0':
|
||||
pvers = 2
|
||||
@@ -1246,17 +1287,18 @@ def dispatch_request(nodes, manager, element, configmanager, inputdata,
|
||||
pvers = 4
|
||||
if sys.version_info[0] < 3:
|
||||
pvers = 2
|
||||
tlvdata.recv(remote)
|
||||
await tlvdata.recv(remote)
|
||||
myname = collective.get_myname()
|
||||
dreq = b'\x01\x03' + msgpack.packb(
|
||||
{'name': myname, 'nodes': list(nodes),
|
||||
'path': element,'tenant': configmanager.tenant,
|
||||
'operation': operation, 'inputdata': inputdata, 'isnoderange': isnoderange}, use_bin_type=False)
|
||||
tlvdata.send(remote, {'dispatch': {'name': myname, 'length': len(dreq)}})
|
||||
remote.sendall(dreq)
|
||||
await tlvdata.send(remote, {'dispatch': {'name': myname, 'length': len(dreq)}})
|
||||
remote[1].write(dreq)
|
||||
await remote[1].drain()
|
||||
while True:
|
||||
try:
|
||||
rlen = remote.recv(8)
|
||||
rlen = await remote[0].read(8)
|
||||
except Exception:
|
||||
for node in nodes:
|
||||
yield msg.ConfluentResourceUnavailable(
|
||||
@@ -1265,7 +1307,7 @@ def dispatch_request(nodes, manager, element, configmanager, inputdata,
|
||||
return
|
||||
while len(rlen) < 8:
|
||||
try:
|
||||
nlen = remote.recv(8 - len(rlen))
|
||||
nlen = await remote[0].read(8 - len(rlen))
|
||||
except Exception:
|
||||
nlen = 0
|
||||
if not nlen:
|
||||
@@ -1279,7 +1321,7 @@ def dispatch_request(nodes, manager, element, configmanager, inputdata,
|
||||
if rlen == 0:
|
||||
break
|
||||
try:
|
||||
rsp = remote.recv(rlen)
|
||||
rsp = await remote[0].read(rlen)
|
||||
except Exception:
|
||||
for node in nodes:
|
||||
yield msg.ConfluentResourceUnavailable(
|
||||
@@ -1288,7 +1330,7 @@ def dispatch_request(nodes, manager, element, configmanager, inputdata,
|
||||
return
|
||||
while len(rsp) < rlen:
|
||||
try:
|
||||
nrsp = remote.recv(rlen - len(rsp))
|
||||
nrsp = await remote[0].read(rlen - len(rsp))
|
||||
except Exception:
|
||||
nrsp = 0
|
||||
if not nrsp:
|
||||
@@ -1315,6 +1357,7 @@ def handle_discovery(pathcomponents, operation, configmanager, inputdata):
|
||||
if pathcomponents[0] == 'detected':
|
||||
pass
|
||||
|
||||
|
||||
class Staging:
|
||||
def __init__(self, user, uuid):
|
||||
self.uuid_str = uuid
|
||||
@@ -1362,7 +1405,7 @@ class Staging:
|
||||
raise FileNotFoundError
|
||||
return directory
|
||||
|
||||
def handle_staging(pathcomponents, operation, configmanager, inputdata):
|
||||
async def handle_staging(pathcomponents, operation, configmanager, inputdata):
|
||||
'''
|
||||
e.g push_url: /confluent-api/staging/user/<unique_id>
|
||||
'''
|
||||
@@ -1391,10 +1434,11 @@ def handle_staging(pathcomponents, operation, configmanager, inputdata):
|
||||
with open(file, 'wb') as f:
|
||||
while remaining_length > 0:
|
||||
progress = (1 - (remaining_length/content_length)) * 100
|
||||
#TODO: ASYNC Need to change to aiohttp approach
|
||||
datachunk = filedata['wsgi.input'].read(min(chunk_size, remaining_length))
|
||||
f.write(datachunk)
|
||||
remaining_length -= len(datachunk)
|
||||
eventlet.sleep(0)
|
||||
await asyncio.sleep(0)
|
||||
yield msg.FileUploadProgress(progress)
|
||||
yield msg.FileUploadProgress(100)
|
||||
|
||||
@@ -1406,7 +1450,7 @@ def handle_staging(pathcomponents, operation, configmanager, inputdata):
|
||||
else:
|
||||
raise Exception("Invalid url")
|
||||
|
||||
def handle_path(path, operation, configmanager, inputdata=None, autostrip=True):
|
||||
async def handle_path(path, operation, configmanager, inputdata=None, autostrip=True):
|
||||
"""Given a full path request, return an object.
|
||||
|
||||
The plugins should generally return some sort of iterator.
|
||||
@@ -1420,7 +1464,7 @@ def handle_path(path, operation, configmanager, inputdata=None, autostrip=True):
|
||||
if not pathcomponents: # root collection list
|
||||
return enumerate_collections(rootcollections)
|
||||
elif pathcomponents[0] == 'noderange':
|
||||
return handle_node_request(configmanager, inputdata, operation,
|
||||
return await handle_node_request(configmanager, inputdata, operation,
|
||||
pathcomponents, autostrip)
|
||||
elif pathcomponents[0] == 'deployment':
|
||||
return handle_deployment(configmanager, inputdata, pathcomponents,
|
||||
@@ -1434,13 +1478,13 @@ def handle_path(path, operation, configmanager, inputdata=None, autostrip=True):
|
||||
operation)
|
||||
elif pathcomponents[0] == 'nodes':
|
||||
# single node request of some sort
|
||||
return handle_node_request(configmanager, inputdata,
|
||||
return await handle_node_request(configmanager, inputdata,
|
||||
operation, pathcomponents, autostrip)
|
||||
elif pathcomponents[0] == 'discovery':
|
||||
return disco.handle_api_request(
|
||||
return await disco.handle_api_request(
|
||||
configmanager, inputdata, operation, pathcomponents)
|
||||
elif pathcomponents[0] == 'networking':
|
||||
return macmap.handle_api_request(
|
||||
return await macmap.handle_api_request(
|
||||
configmanager, inputdata, operation, pathcomponents)
|
||||
elif pathcomponents[0] == 'version':
|
||||
return (msg.Attributes(kv={'version': confluent.__version__}),)
|
||||
@@ -1460,7 +1504,7 @@ def handle_path(path, operation, configmanager, inputdata=None, autostrip=True):
|
||||
inputdata = msg.get_input_message(
|
||||
pathcomponents, operation, inputdata,
|
||||
configmanager=configmanager)
|
||||
create_usergroup(inputdata.attribs, configmanager)
|
||||
await create_usergroup(inputdata.attribs, configmanager)
|
||||
return iterate_collections(configmanager.list_usergroups(),
|
||||
forcecollection=False)
|
||||
if usergroup not in configmanager.list_usergroups():
|
||||
@@ -1473,7 +1517,7 @@ def handle_path(path, operation, configmanager, inputdata=None, autostrip=True):
|
||||
inputdata = msg.get_input_message(
|
||||
pathcomponents, operation, inputdata,
|
||||
configmanager=configmanager)
|
||||
update_usergroup(usergroup, inputdata.attribs, configmanager)
|
||||
await update_usergroup(usergroup, inputdata.attribs, configmanager)
|
||||
return show_usergroup(usergroup, configmanager)
|
||||
elif pathcomponents[0] == 'users':
|
||||
# TODO: when non-administrator accounts exist,
|
||||
@@ -1485,7 +1529,7 @@ def handle_path(path, operation, configmanager, inputdata=None, autostrip=True):
|
||||
inputdata = msg.get_input_message(
|
||||
pathcomponents, operation, inputdata,
|
||||
configmanager=configmanager)
|
||||
create_user(inputdata.attribs, configmanager)
|
||||
await create_user(inputdata.attribs, configmanager)
|
||||
return iterate_collections(configmanager.list_users(),
|
||||
forcecollection=False)
|
||||
if user not in configmanager.list_users():
|
||||
@@ -1510,11 +1554,11 @@ def handle_path(path, operation, configmanager, inputdata=None, autostrip=True):
|
||||
if element != 'decode':
|
||||
raise exc.NotFoundException()
|
||||
if operation == 'update':
|
||||
return alerts.decode_alert(inputdata, configmanager)
|
||||
return await alerts.decode_alert(inputdata, configmanager)
|
||||
elif pathcomponents[0] == 'discovery':
|
||||
return handle_discovery(pathcomponents[1:], operation, configmanager,
|
||||
inputdata)
|
||||
elif pathcomponents[0] == 'staging':
|
||||
return handle_staging(pathcomponents, operation, configmanager, inputdata)
|
||||
return await handle_staging(pathcomponents, operation, configmanager, inputdata)
|
||||
else:
|
||||
raise exc.NotFoundException()
|
||||
|
||||
@@ -14,14 +14,11 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import asyncio
|
||||
import confluent.config.configmanager as cfm
|
||||
import confluent.netutil as netutil
|
||||
import confluent.util as util
|
||||
import datetime
|
||||
import eventlet
|
||||
import eventlet.green.select as select
|
||||
import eventlet.green.socket as socket
|
||||
import eventlet.greenpool
|
||||
import hashlib
|
||||
import hmac
|
||||
import os
|
||||
@@ -42,10 +39,10 @@ libc = ctypes.CDLL(ctypes.util.find_library('c'))
|
||||
# 128, len, len, key - sealed key
|
||||
|
||||
|
||||
def address_is_somewhat_trusted(address, nodename, cfm):
|
||||
if netutil.ip_on_same_subnet(address.split('%')[0], 'fe80::', 64):
|
||||
async def address_is_somewhat_trusted(address, nodename, cfm):
|
||||
if await netutil.ip_on_same_subnet(address.split('%')[0], 'fe80::', 64):
|
||||
return True
|
||||
if netutil.address_is_local(address):
|
||||
if await netutil.address_is_local(address):
|
||||
return True
|
||||
authnets = cfm.get_node_attributes(nodename, 'trusted.subnets')
|
||||
authnets = authnets.get(nodename, {}).get('trusted.subnets', {}).get('value', None)
|
||||
@@ -54,7 +51,7 @@ def address_is_somewhat_trusted(address, nodename, cfm):
|
||||
for anet in authnet.split():
|
||||
na, plen = anet.split('/')
|
||||
plen = int(plen)
|
||||
if netutil.ip_on_same_subnet(address, na, plen):
|
||||
if await netutil.ip_on_same_subnet(address, na, plen):
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -62,31 +59,33 @@ class CredServer(object):
|
||||
def __init__(self):
|
||||
self.cfm = cfm.ConfigManager(None)
|
||||
|
||||
def handle_client(self, client, peer):
|
||||
async def handle_client(self, client, peer):
|
||||
disarm = None
|
||||
try:
|
||||
apiarmed = None
|
||||
hmackey = None
|
||||
hmacval = None
|
||||
client.send(b'\xc2\xd1-\xa8\x80\xd8j\xba')
|
||||
tlv = bytearray(client.recv(2))
|
||||
cloop = asyncio.get_event_loop()
|
||||
await cloop.sock_sendall(client, b'\xc2\xd1-\xa8\x80\xd8j\xba')
|
||||
tlv = bytearray(await cloop.sock_recv(client, 2))
|
||||
if tlv[0] != 1:
|
||||
client.close()
|
||||
return
|
||||
nodename = util.stringify(client.recv(tlv[1]))
|
||||
tlv = bytearray(client.recv(2)) # should always be null
|
||||
nodename = util.stringify(await cloop.sock_recv(client, tlv[1]))
|
||||
tlv = bytearray(await cloop.sock_recv(client, 2)) # should always be null
|
||||
onlylocal = True
|
||||
if tlv[0] == 6:
|
||||
hmacval = client.recv(tlv[1])
|
||||
hmacval = await cloop.sock_recv(client, tlv[1])
|
||||
hmackey = self.cfm.get_node_attributes(nodename, ['secret.selfapiarmtoken'], decrypt=True)
|
||||
hmackey = hmackey.get(nodename, {}).get('secret.selfapiarmtoken', {}).get('value', None)
|
||||
elif tlv[1]:
|
||||
client.recv(tlv[1])
|
||||
await cloop.sock_recv(client, tlv[1])
|
||||
apimats = self.cfm.get_node_attributes(nodename,
|
||||
['deployment.apiarmed', 'deployment.sealedapikey'])
|
||||
apiarmed = apimats.get(nodename, {}).get('deployment.apiarmed', {}).get(
|
||||
'value', None)
|
||||
if not hmackey:
|
||||
if not address_is_somewhat_trusted(peer[0], nodename, self.cfm):
|
||||
if not await address_is_somewhat_trusted(peer[0], nodename, self.cfm):
|
||||
client.close()
|
||||
return
|
||||
if not apiarmed:
|
||||
@@ -97,7 +96,7 @@ class CredServer(object):
|
||||
if not isinstance(sealed, bytes):
|
||||
sealed = sealed.encode('utf8')
|
||||
reply = b'\x80' + struct.pack('>H', len(sealed) + 1) + sealed + b'\x00'
|
||||
client.send(reply)
|
||||
await cloop.sock_sendall(client, reply)
|
||||
client.close()
|
||||
return
|
||||
if apiarmed not in ('once', 'continuous'):
|
||||
@@ -107,41 +106,48 @@ class CredServer(object):
|
||||
self.cfm.set_node_attributes({nodename: {'deployment.apiarmed': ''}})
|
||||
client.close()
|
||||
return
|
||||
client.send(b'\x02\x20')
|
||||
await cloop.sock_sendall(client, b'\x02\x20')
|
||||
rttoken = os.urandom(32)
|
||||
client.send(rttoken)
|
||||
client.send(b'\x00\x00')
|
||||
tlv = bytearray(client.recv(2))
|
||||
await cloop.sock_sendall(client, rttoken)
|
||||
await cloop.sock_sendall(client, b'\x00\x00')
|
||||
tlv = bytearray(await cloop.sock_recv(client, 2))
|
||||
if tlv[0] != 3:
|
||||
client.close()
|
||||
return
|
||||
echotoken = client.recv(tlv[1])
|
||||
echotoken = await cloop.sock_recv(client, tlv[1])
|
||||
if echotoken != rttoken:
|
||||
client.close()
|
||||
return
|
||||
tlv = bytearray(client.recv(2))
|
||||
tlv = bytearray(await cloop.sock_recv(client, 2))
|
||||
if tlv[0] != 4:
|
||||
client.close()
|
||||
return
|
||||
echotoken = util.stringify(client.recv(tlv[1]))
|
||||
echotoken = util.stringify(await cloop.sock_recv(client, tlv[1]))
|
||||
if hmackey:
|
||||
etok = echotoken.encode('utf8')
|
||||
if hmacval != hmac.new(hmackey, etok, hashlib.sha256).digest():
|
||||
client.close()
|
||||
return
|
||||
cfgupdate = {nodename: {'crypted.selfapikey': {'hashvalue': echotoken}}}
|
||||
self.cfm.set_node_attributes(cfgupdate)
|
||||
client.recv(2) # drain end of message
|
||||
client.send(b'\x05\x00') # report success
|
||||
await self.cfm.set_node_attributes(cfgupdate)
|
||||
await cloop.sock_recv(client, 2) # drain end of message
|
||||
await cloop.sock_sendall(client, b'\x05\x00') # report success
|
||||
if hmackey and apiarmed != 'continuous':
|
||||
self.cfm.clear_node_attributes([nodename], ['secret.selfapiarmtoken'])
|
||||
if apiarmed != 'continuous':
|
||||
tokclear = {nodename: {'deployment.sealedapikey': '', 'deployment.apiarmed': ''}}
|
||||
self.cfm.set_node_attributes(tokclear)
|
||||
disarm = {nodename: {'deployment.sealedapikey': '', 'deployment.apiarmed': ''}}
|
||||
finally:
|
||||
client.close()
|
||||
try:
|
||||
client.close()
|
||||
except Exception:
|
||||
pass
|
||||
if disarm:
|
||||
await self.cfm.set_node_attributes(disarm)
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
async def main():
|
||||
a = CredServer()
|
||||
while True:
|
||||
eventlet.sleep(86400)
|
||||
await asyncio.sleep(86400)
|
||||
if __name__ == '__main__':
|
||||
asyncio.get_event_loop().run_until_complete(main())
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
import asyncio
|
||||
import code
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
import confluent.tasks as tasks
|
||||
|
||||
#this will ultimately fill the role of the 'backdoor' of eventlet
|
||||
|
||||
# since we have to asyncio up the input and output, we use InteractiveInterpreter and handle the
|
||||
# input ourselves, since code is not asyncio friendly in and of itself
|
||||
#code.InteractiveConsole().interact()
|
||||
|
||||
|
||||
|
||||
async def interact(cloop, cnn):
|
||||
prompt = b'>>> '
|
||||
somecode = ''
|
||||
itr = code.InteractiveInterpreter()
|
||||
confile = cnn.makefile('rw')
|
||||
while True:
|
||||
await cloop.sock_sendall(cnn, prompt)
|
||||
prompt = b'... '
|
||||
newinput = b''
|
||||
while b'\n' not in newinput:
|
||||
rcv = await cloop.sock_recv(cnn, 4)
|
||||
if not rcv:
|
||||
return
|
||||
newinput += rcv
|
||||
somecode += newinput.decode()
|
||||
if newinput.startswith(b' '):
|
||||
prompt = b'... '
|
||||
continue
|
||||
try:
|
||||
compcode = code.compile_command(somecode)
|
||||
except SyntaxError as e:
|
||||
await cloop.sock_sendall(cnn, repr(e).encode('utf8'))
|
||||
await cloop.sock_sendall(cnn, b'\n')
|
||||
compcode = None
|
||||
somecode = ''
|
||||
prompt = b'>>> '
|
||||
if compcode:
|
||||
saved = sys.stdin, sys.stderr, sys.stdout
|
||||
try:
|
||||
cnn.settimeout(10)
|
||||
confile = cnn.makefile('rw')
|
||||
sys.stderr = sys.stdout = confile
|
||||
itr.runcode(compcode)
|
||||
confile.flush()
|
||||
finally:
|
||||
sys.stdin, sys.stderr, sys.stdout = saved
|
||||
cnn.settimeout(0)
|
||||
somecode = ''
|
||||
prompt = b'>>> '
|
||||
|
||||
|
||||
async def srv_debug(sock):
|
||||
cloop = asyncio.get_event_loop()
|
||||
while True:
|
||||
cnn, addr = await cloop.sock_accept(sock)
|
||||
tasks.spawn(interact(cloop, cnn))
|
||||
|
||||
|
||||
def start_dbgif():
|
||||
unixsocket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
unixsocket.settimeout(0)
|
||||
try:
|
||||
os.remove("/var/run/confluent/dbg.sock")
|
||||
except OSError: # if file does not exist, no big deal
|
||||
pass
|
||||
if not os.path.isdir("/var/run/confluent"):
|
||||
os.makedirs('/var/run/confluent', 0o755)
|
||||
oumask = os.umask(0o077)
|
||||
unixsocket.bind("/var/run/confluent/dbg.sock")
|
||||
unixsocket.listen(12)
|
||||
os.chmod("/var/run/confluent/dbg.sock",
|
||||
0o600)
|
||||
os.umask(oumask)
|
||||
tasks.spawn(srv_debug(unixsocket))
|
||||
@@ -61,6 +61,7 @@
|
||||
# retry until uppercase, lowercase, digit, and symbol all present)
|
||||
# - Apply defined configuration to endpoint
|
||||
|
||||
import asyncio
|
||||
import base64
|
||||
import confluent.config.configmanager as cfm
|
||||
import confluent.collective.manager as collective
|
||||
@@ -82,22 +83,18 @@ import confluent.log as log
|
||||
import confluent.messages as msg
|
||||
import confluent.networking.macmap as macmap
|
||||
import confluent.noderange as noderange
|
||||
import confluent.tasks as tasks
|
||||
import confluent.util as util
|
||||
import inspect
|
||||
import json
|
||||
import eventlet
|
||||
import traceback
|
||||
import shlex
|
||||
import struct
|
||||
import eventlet.green.socket as socket
|
||||
import socket
|
||||
import socket as nsocket
|
||||
import eventlet.green.subprocess as subprocess
|
||||
webclient = eventlet.import_patched('pyghmi.util.webclient')
|
||||
import aiohmi.util.webclient as webclient
|
||||
|
||||
|
||||
import eventlet
|
||||
import eventlet.greenpool
|
||||
import eventlet.semaphore
|
||||
|
||||
autosensors = set()
|
||||
scanner = None
|
||||
|
||||
@@ -106,6 +103,11 @@ try:
|
||||
except NameError:
|
||||
unicode = str
|
||||
|
||||
try:
|
||||
import cryptography.x509.verification as verification
|
||||
except ImportError:
|
||||
verification = None
|
||||
|
||||
class nesteddict(dict):
|
||||
|
||||
def __missing__(self, key):
|
||||
@@ -165,7 +167,6 @@ servicebyname = {
|
||||
'lenovo-tsm': 'service:lenovo-tsm',
|
||||
}
|
||||
|
||||
discopool = eventlet.greenpool.GreenPool(500)
|
||||
runningevals = {}
|
||||
# Passive-only auto-detection protocols:
|
||||
# PXE
|
||||
@@ -526,13 +527,13 @@ def save_subscriptions(subs):
|
||||
dso.write(json.dumps(subs))
|
||||
|
||||
|
||||
def register_remote_addrs(addresses, configmanager):
|
||||
def register_remote_addr(addr):
|
||||
async def register_remote_addrs(addresses, configmanager):
|
||||
async def register_remote_addr(addr):
|
||||
nd = {
|
||||
'addresses': [(addr, 443)]
|
||||
}
|
||||
try:
|
||||
sd = ssdp.check_fish(('/DeviceDescription.json', nd))
|
||||
sd = await ssdp.check_fish(('/DeviceDescription.json', nd))
|
||||
if not sd:
|
||||
return addr, False
|
||||
if 'macaddress' in sd['attributes']:
|
||||
@@ -543,22 +544,21 @@ def register_remote_addrs(addresses, configmanager):
|
||||
nh = xcc3.NodeHandler(sd, configmanager)
|
||||
elif 'lenovo-xcc' in sd['services']:
|
||||
nh = xcc.NodeHandler(sd, configmanager)
|
||||
nh.scan()
|
||||
detected(nh.info)
|
||||
await nh.scan()
|
||||
await detected(nh.info)
|
||||
except Exception:
|
||||
return addr, False
|
||||
return addr, True
|
||||
rpool = eventlet.greenpool.GreenPool(512)
|
||||
for count in iterate_addrs(addresses, True):
|
||||
yield msg.ConfluentResourceCount(count)
|
||||
for result in rpool.imap(register_remote_addr, iterate_addrs(addresses)):
|
||||
async for result in tasks.task_imap(register_remote_addr, iterate_addrs(addresses), max_concurrent=512):
|
||||
if result[1]:
|
||||
yield msg.CreatedResource(result[0])
|
||||
else:
|
||||
yield msg.ConfluentResourceNotFound(result[0])
|
||||
|
||||
|
||||
def handle_api_request(configmanager, inputdata, operation, pathcomponents):
|
||||
async def handle_api_request(configmanager, inputdata, operation, pathcomponents):
|
||||
if pathcomponents == ['discovery', 'autosense']:
|
||||
return handle_autosense_config(operation, inputdata)
|
||||
if operation == 'retrieve' and pathcomponents[:2] == ['discovery', 'subscriptions']:
|
||||
@@ -572,7 +572,7 @@ def handle_api_request(configmanager, inputdata, operation, pathcomponents):
|
||||
pathcomponents == ['discovery', 'rescan']):
|
||||
if inputdata != {'rescan': 'start'}:
|
||||
raise exc.InvalidArgumentException()
|
||||
rescan()
|
||||
await rescan()
|
||||
return (msg.KeyValueData({'rescan': 'started'}),)
|
||||
elif operation in ('update', 'create') and pathcomponents[:2] == ['discovery', 'subscriptions']:
|
||||
target = pathcomponents[2]
|
||||
@@ -593,7 +593,7 @@ def handle_api_request(configmanager, inputdata, operation, pathcomponents):
|
||||
if pathcomponents == ['discovery', 'register']:
|
||||
if 'addresses' not in inputdata:
|
||||
raise exc.InvalidArgumentException('Missing address in input')
|
||||
return register_remote_addrs(inputdata['addresses'], configmanager)
|
||||
return await register_remote_addrs(inputdata['addresses'], configmanager)
|
||||
if 'node' not in inputdata:
|
||||
raise exc.InvalidArgumentException('Missing node name in input')
|
||||
mac = _get_mac_from_query(pathcomponents)
|
||||
@@ -604,7 +604,7 @@ def handle_api_request(configmanager, inputdata, operation, pathcomponents):
|
||||
'/'.join(pathcomponents)))
|
||||
handler = info['handler'].NodeHandler(info, configmanager)
|
||||
try:
|
||||
eval_node(configmanager, handler, info, inputdata['node'],
|
||||
await eval_node(configmanager, handler, info, inputdata['node'],
|
||||
manual=True)
|
||||
except Exception as e:
|
||||
# or... incorrect passworod provided..
|
||||
@@ -674,17 +674,17 @@ def detected_models():
|
||||
yield info['modelnumber']
|
||||
|
||||
|
||||
def _recheck_nodes(nodeattribs, configmanager):
|
||||
async def _recheck_nodes(nodeattribs, configmanager):
|
||||
if not cfm.config_is_ready():
|
||||
return
|
||||
if rechecklock.locked():
|
||||
# if already in progress, don't run again
|
||||
# it may make sense to schedule a repeat, but will try the easier and less redundant way first
|
||||
return
|
||||
with rechecklock:
|
||||
return _recheck_nodes_backend(nodeattribs, configmanager)
|
||||
async with rechecklock:
|
||||
return await _recheck_nodes_backend(nodeattribs, configmanager)
|
||||
|
||||
def _recheck_nodes_backend(nodeattribs, configmanager):
|
||||
async def _recheck_nodes_backend(nodeattribs, configmanager):
|
||||
global rechecker
|
||||
_map_unique_ids(nodeattribs)
|
||||
# for the nodes whose attributes have changed, consider them as potential
|
||||
@@ -700,7 +700,7 @@ def _recheck_nodes_backend(nodeattribs, configmanager):
|
||||
# Now we go through ones we did not find earlier
|
||||
for mac in list(unknown_info):
|
||||
try:
|
||||
_recheck_single_unknown(configmanager, mac)
|
||||
await _recheck_single_unknown(configmanager, mac)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
continue
|
||||
@@ -712,19 +712,19 @@ def _recheck_nodes_backend(nodeattribs, configmanager):
|
||||
if info['handler'] is None:
|
||||
next
|
||||
handler = info['handler'].NodeHandler(info, configmanager)
|
||||
discopool.spawn_n(eval_node, configmanager, handler, info, nodename)
|
||||
tasks.spawn(eval_node(configmanager, handler, info, nodename))
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
log.log({'error': 'Unexpected error during discovery of {0}, check debug '
|
||||
'logs'.format(nodename)})
|
||||
|
||||
|
||||
def _recheck_single_unknown(configmanager, mac):
|
||||
async def _recheck_single_unknown(configmanager, mac):
|
||||
info = unknown_info.get(mac, None)
|
||||
_recheck_single_unknown_info(configmanager, info)
|
||||
await _recheck_single_unknown_info(configmanager, info)
|
||||
|
||||
|
||||
def _recheck_single_unknown_info(configmanager, info):
|
||||
async def _recheck_single_unknown_info(configmanager, info):
|
||||
global rechecker
|
||||
global rechecktime
|
||||
if not info or info['handler'] is None:
|
||||
@@ -755,12 +755,12 @@ def _recheck_single_unknown_info(configmanager, info):
|
||||
if rechecker is not None and rechecktime > util.monotonic_time() + 300:
|
||||
rechecker.cancel()
|
||||
# if cancel did not result in dead, then we are in progress
|
||||
if rechecker is None or rechecker.dead:
|
||||
if rechecker is None or rechecker.done():
|
||||
rechecktime = util.monotonic_time() + 300
|
||||
rechecker = eventlet.spawn_after(300, _periodic_recheck,
|
||||
rechecker = tasks.spawn_task_after(300, _periodic_recheck,
|
||||
configmanager)
|
||||
return
|
||||
nodename, info['maccount'] = get_nodename(configmanager, handler, info)
|
||||
nodename, info['maccount'] = await get_nodename(configmanager, handler, info)
|
||||
if nodename:
|
||||
if handler.https_supported:
|
||||
dp = configmanager.get_node_attributes([nodename],
|
||||
@@ -772,7 +772,7 @@ def _recheck_single_unknown_info(configmanager, info):
|
||||
known_nodes[nodename][info['hwaddr']] = info
|
||||
info['discostatus'] = 'discovered'
|
||||
return # already known, no need for more
|
||||
discopool.spawn_n(eval_node, configmanager, handler, info, nodename)
|
||||
tasks.spawn(eval_node(configmanager, handler, info, nodename))
|
||||
|
||||
|
||||
def safe_detected(info):
|
||||
@@ -781,18 +781,18 @@ def safe_detected(info):
|
||||
if info['hwaddr'] in runningevals:
|
||||
# Do not evaluate the same mac multiple times at once
|
||||
return
|
||||
runningevals[info['hwaddr']] = discopool.spawn(eval_detected, info)
|
||||
runningevals[info['hwaddr']] = tasks.spawn_task(eval_detected(info))
|
||||
|
||||
|
||||
def eval_detected(info):
|
||||
async def eval_detected(info):
|
||||
try:
|
||||
detected(info)
|
||||
await detected(info)
|
||||
except Exception as e:
|
||||
traceback.print_exc()
|
||||
del runningevals[info['hwaddr']]
|
||||
|
||||
|
||||
def detected(info):
|
||||
async def detected(info):
|
||||
global rechecker
|
||||
global rechecktime
|
||||
if not cfm.config_is_ready():
|
||||
@@ -810,7 +810,7 @@ def detected(info):
|
||||
return
|
||||
if (handler and not handler.NodeHandler.adequate(info) and
|
||||
info.get('protocol', None)):
|
||||
eventlet.spawn_after(10, info['protocol'].fix_info, info,
|
||||
tasks.spawn_after(10, info['protocol'].fix_info, info,
|
||||
safe_detected)
|
||||
return
|
||||
if info['hwaddr'] in known_info and 'addresses' in info:
|
||||
@@ -843,7 +843,9 @@ def detected(info):
|
||||
cfg = cfm.ConfigManager(None)
|
||||
if handler:
|
||||
handler = handler.NodeHandler(info, cfg)
|
||||
handler.scan()
|
||||
res = handler.scan()
|
||||
if inspect.isawaitable(res):
|
||||
await res
|
||||
try:
|
||||
if 'modelnumber' not in info:
|
||||
info['modelnumber'] = info['attributes']['enclosure-machinetype-model'][0]
|
||||
@@ -885,15 +887,15 @@ def detected(info):
|
||||
)})
|
||||
if rechecker is not None and rechecktime > util.monotonic_time() + 300:
|
||||
rechecker.cancel()
|
||||
if rechecker is None or rechecker.dead:
|
||||
if rechecker is None or rechecker.done():
|
||||
rechecktime = util.monotonic_time() + 300
|
||||
rechecker = eventlet.spawn_after(300, _periodic_recheck, cfg)
|
||||
rechecker = tasks.spawn_task_after(300, _periodic_recheck, cfg)
|
||||
unknown_info[info['hwaddr']] = info
|
||||
info['discostatus'] = 'unidentfied'
|
||||
#TODO, eventlet spawn after to recheck sooner, or somehow else
|
||||
#TODO, spawn after to recheck sooner, or somehow else
|
||||
# influence periodic recheck to shorten delay?
|
||||
return
|
||||
nodename, info['maccount'] = get_nodename(cfg, handler, info)
|
||||
nodename, info['maccount'] = await get_nodename(cfg, handler, info)
|
||||
if nodename and handler and handler.https_supported:
|
||||
dp = cfg.get_node_attributes([nodename],
|
||||
('pubkeys.tls_hardwaremanager', 'id.uuid', 'discovery.policy'))
|
||||
@@ -920,7 +922,7 @@ def detected(info):
|
||||
#for now defer probe until inside eval_node. We might not have
|
||||
#a nodename without probe in the future.
|
||||
if nodename and handler:
|
||||
eval_node(cfg, handler, info, nodename)
|
||||
await eval_node(cfg, handler, info, nodename)
|
||||
elif handler:
|
||||
#log.log(
|
||||
# {'info': 'Detected unknown {0} with hwaddr {1} at '
|
||||
@@ -959,7 +961,7 @@ def get_enclosure_chain_head(nodename, cfg):
|
||||
return nodename
|
||||
|
||||
|
||||
def get_chained_smm_name(nodename, cfg, handler, nl=None, checkswitch=True):
|
||||
async def get_chained_smm_name(nodename, cfg, handler, nl=None, checkswitch=True):
|
||||
# nodename is the head of the chain, cfg is a configmanager, handler
|
||||
# is the handler of the current candidate, nl is optional indication
|
||||
# of the next link in the chain, checkswitch can disable the switch
|
||||
@@ -995,7 +997,7 @@ def get_chained_smm_name(nodename, cfg, handler, nl=None, checkswitch=True):
|
||||
if pkey:
|
||||
cv = util.TLSCertVerifier(
|
||||
cfg, nodename, 'pubkeys.tls_hardwaremanager').verify_cert
|
||||
for fprint in get_smm_neighbor_fingerprints(smmaddr, cv):
|
||||
async for fprint in get_smm_neighbor_fingerprints(smmaddr, cv):
|
||||
if util.cert_matches(fprint, mycert):
|
||||
# a trusted chain member vouched for the cert
|
||||
# so it's validated
|
||||
@@ -1007,12 +1009,12 @@ def get_chained_smm_name(nodename, cfg, handler, nl=None, checkswitch=True):
|
||||
return None, False
|
||||
|
||||
|
||||
def get_smm_neighbor_fingerprints(smmaddr, cv):
|
||||
async def get_smm_neighbor_fingerprints(smmaddr, cv):
|
||||
if ':' in smmaddr:
|
||||
smmaddr = '[{0}]'.format(smmaddr)
|
||||
wc = webclient.SecureHTTPConnection(smmaddr, verifycallback=cv)
|
||||
wc = webclient.WebConnection(smmaddr, verifycallback=cv)
|
||||
try:
|
||||
neighs = wc.grab_json_response('/scripts/neighdata.json')
|
||||
neighs = await wc.grab_json_response('/scripts/neighdata.json')
|
||||
except Exception:
|
||||
log.log({'error': 'Failure getting LLDP information from {}'.format(smmaddr)})
|
||||
return
|
||||
@@ -1059,13 +1061,17 @@ def get_nodename_sysdisco(cfg, handler, info):
|
||||
return currnode
|
||||
else:
|
||||
baynum = info['bay']
|
||||
nl = cfg.filter_node_attributes('enclosure.manager=' + currnode)
|
||||
nl = list(cfg.filter_node_attributes('enclosure.bay={0}'.format(baynum), nl))
|
||||
alphabaynum = '{}{}'.format((int(baynum) + 1) // 2, 'ab'[(int(baynum) - 1) % 2])
|
||||
onl = cfg.filter_node_attributes('enclosure.manager=' + currnode)
|
||||
nl = list(cfg.filter_node_attributes('enclosure.bay={}'.format(baynum), onl))
|
||||
if len(nl) == 1:
|
||||
return nl[0]
|
||||
nl = list(cfg.filter_node_attributes('enclosure.bay={}'.format(alphabaynum), onl))
|
||||
if len(nl) == 1:
|
||||
return nl[0]
|
||||
|
||||
|
||||
def get_nodename(cfg, handler, info):
|
||||
async def get_nodename(cfg, handler, info):
|
||||
nodename = None
|
||||
maccount = None
|
||||
info['verified'] = False
|
||||
@@ -1107,7 +1113,7 @@ def get_nodename(cfg, handler, info):
|
||||
if not nodename: # as a last resort, search switches for info
|
||||
# This is the slowest potential operation, so we hope for the
|
||||
# best to occur prior to this
|
||||
nodename, macinfo = macmap.find_nodeinfo_by_mac(info['hwaddr'], cfg)
|
||||
nodename, macinfo = await macmap.find_nodeinfo_by_mac(info['hwaddr'], cfg)
|
||||
maccount = macinfo['maccount']
|
||||
if nodename:
|
||||
if handler.devname in ('SMM', 'SMM3'):
|
||||
@@ -1182,18 +1188,25 @@ def get_nodename_from_enclosures(cfg, info):
|
||||
encl = nodes_by_uuid[cuuid]
|
||||
bay = info.get('enclosure.bay', None)
|
||||
if bay:
|
||||
tnl = cfg.filter_node_attributes('enclosure.manager=' + encl)
|
||||
otnl = cfg.filter_node_attributes('enclosure.manager=' + encl)
|
||||
tnl = list(
|
||||
cfg.filter_node_attributes('enclosure.bay={0}'.format(bay),
|
||||
tnl))
|
||||
cfg.filter_node_attributes('enclosure.bay={}'.format(bay), otnl))
|
||||
if len(tnl) == 1:
|
||||
# This is not a secure assurance, because it's by
|
||||
# uuid instead of a key
|
||||
nodename = tnl[0]
|
||||
else:
|
||||
alphabay = '{}{}'.format((int(bay) + 1) // 2, 'ab'[(int(bay) - 1) % 2])
|
||||
tnl = list(
|
||||
cfg.filter_node_attributes('enclosure.bay={}'.format(alphabay), otnl))
|
||||
if len(tnl) == 1:
|
||||
# Fallback alpha-bay mapping resolved to a single node
|
||||
nodename = tnl[0]
|
||||
|
||||
return nodename
|
||||
|
||||
|
||||
def search_smms_by_cert(currsmm, cert, cfg):
|
||||
async def search_smms_by_cert(currsmm, cert, cfg):
|
||||
neighs = []
|
||||
cv = util.TLSCertVerifier(
|
||||
cfg, currsmm, 'pubkeys.tls_hardwaremanager').verify_cert
|
||||
@@ -1203,8 +1216,8 @@ def search_smms_by_cert(currsmm, cert, cfg):
|
||||
smmaddr = cd.get(currsmm, {}).get('hardwaremanagement.manager', {}).get('value', None)
|
||||
if not smmaddr:
|
||||
smmaddr = currsmm
|
||||
wc = webclient.SecureHTTPConnection(smmaddr, verifycallback=cv)
|
||||
neighs = wc.grab_json_response('/scripts/neighdata.json')
|
||||
wc = webclient.WebConnection(smmaddr, verifycallback=cv)
|
||||
neighs = await wc.grab_json_response('/scripts/neighdata.json')
|
||||
except Exception:
|
||||
return None
|
||||
for neigh in neighs:
|
||||
@@ -1215,24 +1228,29 @@ def search_smms_by_cert(currsmm, cert, cfg):
|
||||
port = neigh.get('port', None)
|
||||
if port is not None:
|
||||
bay = port + 1
|
||||
nl = list(
|
||||
onl = list(
|
||||
cfg.filter_node_attributes('enclosure.manager=' + currsmm))
|
||||
nl = list(
|
||||
cfg.filter_node_attributes('enclosure.bay={}'.format(bay), nl))
|
||||
cfg.filter_node_attributes('enclosure.bay={}'.format(bay), onl))
|
||||
if len(nl) == 1:
|
||||
return currsmm, bay, nl[0]
|
||||
alphabay = '{}{}'.format((bay + 1) // 2, 'ab'[(bay - 1) % 2])
|
||||
nl = list(
|
||||
cfg.filter_node_attributes('enclosure.bay={}'.format(alphabay), onl))
|
||||
if len(nl) == 1:
|
||||
return currsmm, bay, nl[0]
|
||||
return currsmm, bay, None
|
||||
exnl = list(cfg.filter_node_attributes('enclosure.extends=' + currsmm))
|
||||
if len(exnl) == 1:
|
||||
return search_smms_by_cert(exnl[0], cert, cfg)
|
||||
return await search_smms_by_cert(exnl[0], cert, cfg)
|
||||
|
||||
|
||||
def eval_node(cfg, handler, info, nodename, manual=False):
|
||||
async def eval_node(cfg, handler, info, nodename, manual=False):
|
||||
try:
|
||||
handler.probe() # unicast interrogation as possible to get more data
|
||||
# switch concurrently
|
||||
# do some preconfig, for example, to bring a SMM online if applicable
|
||||
handler.preconfig(nodename)
|
||||
await handler.preconfig(nodename)
|
||||
except Exception as e:
|
||||
unknown_info[info['hwaddr']] = info
|
||||
info['discostatus'] = 'unidentified'
|
||||
@@ -1261,12 +1279,12 @@ def eval_node(cfg, handler, info, nodename, manual=False):
|
||||
# The specified node is an enclosure (has nodes mapped to it), but
|
||||
# what we are talking to is *not* an enclosure
|
||||
# might be ambiguous, need to match chassis-uuid as well..
|
||||
match = search_smms_by_cert(nodename, handler.https_cert, cfg)
|
||||
match = await search_smms_by_cert(nodename, handler.https_cert, cfg)
|
||||
if match:
|
||||
info['verfied'] = True
|
||||
info['enclosure.bay'] = match[1]
|
||||
if match[2]:
|
||||
if not discover_node(cfg, handler, info, match[2], manual):
|
||||
if not await discover_node(cfg, handler, info, match[2], manual):
|
||||
pending_nodes[match[2]] = info
|
||||
return
|
||||
if 'enclosure.bay' not in info:
|
||||
@@ -1311,8 +1329,15 @@ def eval_node(cfg, handler, info, nodename, manual=False):
|
||||
return
|
||||
# search for nodes fitting our description using filters
|
||||
# lead with the most specific to have a small second pass
|
||||
nl = list(cfg.filter_node_attributes(
|
||||
'enclosure.bay={0}'.format(info['enclosure.bay']), nl))
|
||||
baynum = info.get('enclosure.bay', None)
|
||||
if baynum:
|
||||
nnl = list(cfg.filter_node_attributes(
|
||||
'enclosure.bay={}'.format(baynum), nl))
|
||||
if len(nnl) == 0:
|
||||
alphabaynum = '{}{}'.format((int(baynum) + 1) // 2, 'ab'[(int(baynum) - 1) % 2])
|
||||
nnl = list(cfg.filter_node_attributes(
|
||||
'enclosure.bay={}'.format(alphabaynum), nl))
|
||||
nl = nnl
|
||||
if len(nl) != 1:
|
||||
info['discofailure'] = 'ambigconfig'
|
||||
if len(nl):
|
||||
@@ -1323,7 +1348,7 @@ def eval_node(cfg, handler, info, nodename, manual=False):
|
||||
errorstr = 'The {0} in enclosure {1} bay {2} does not ' \
|
||||
'seem to be a defined node ({3})'.format(
|
||||
handler.devname, nodename,
|
||||
info['enclosure.bay'],
|
||||
baynum,
|
||||
handler.ipaddr,
|
||||
)
|
||||
if manual:
|
||||
@@ -1333,7 +1358,7 @@ def eval_node(cfg, handler, info, nodename, manual=False):
|
||||
info['discostatus'] = 'unidentified'
|
||||
return
|
||||
nodename = nl[0]
|
||||
if not discover_node(cfg, handler, info, nodename, manual):
|
||||
if not await discover_node(cfg, handler, info, nodename, manual):
|
||||
# store it as pending, assuming blocked on enclosure
|
||||
# assurance...
|
||||
pending_nodes[nodename] = info
|
||||
@@ -1353,7 +1378,7 @@ def eval_node(cfg, handler, info, nodename, manual=False):
|
||||
fprints = macmap.get_node_fingerprints(nodename, cfg)
|
||||
for fprint in fprints:
|
||||
if util.cert_matches(fprint[0], handler.https_cert):
|
||||
if not discover_node(cfg, handler, info,
|
||||
if not await discover_node(cfg, handler, info,
|
||||
nodename, manual):
|
||||
pending_nodes[nodename] = info
|
||||
return
|
||||
@@ -1366,11 +1391,11 @@ def eval_node(cfg, handler, info, nodename, manual=False):
|
||||
'defined nodes for the enclosure'.format(nodename, handler.devname)
|
||||
log.log({'error': errorstr})
|
||||
return
|
||||
if not discover_node(cfg, handler, info, nodename, manual):
|
||||
if not await discover_node(cfg, handler, info, nodename, manual):
|
||||
pending_nodes[nodename] = info
|
||||
|
||||
|
||||
def discover_node(cfg, handler, info, nodename, manual):
|
||||
async def discover_node(cfg, handler, info, nodename, manual):
|
||||
if manual:
|
||||
if not cfg.is_node(nodename):
|
||||
raise exc.InvalidArgumentException(
|
||||
@@ -1430,7 +1455,7 @@ def discover_node(cfg, handler, info, nodename, manual):
|
||||
elif manual or not util.cert_matches(lastfp, handler.https_cert):
|
||||
# only 'discover' if it is not the same as last time
|
||||
try:
|
||||
handler.config(nodename)
|
||||
await handler.config(nodename)
|
||||
except Exception as e:
|
||||
info['discofailure'] = 'bug'
|
||||
if manual:
|
||||
@@ -1468,26 +1493,29 @@ def discover_node(cfg, handler, info, nodename, manual):
|
||||
for checkattr in newnodeattribs:
|
||||
checkval = currattrs.get(nodename, {}).get(checkattr, {}).get('value', None)
|
||||
if checkval != newnodeattribs[checkattr]:
|
||||
cfg.set_node_attributes({nodename: newnodeattribs})
|
||||
await cfg.set_node_attributes({nodename: newnodeattribs})
|
||||
break
|
||||
log.log({'info': 'Discovered {0} ({1})'.format(nodename,
|
||||
handler.devname)})
|
||||
if nodeconfig:
|
||||
if nodeconfig or handler.current_cert_self_signed():
|
||||
bmcaddr = cfg.get_node_attributes(nodename, 'hardwaremanagement.manager')
|
||||
bmcaddr = bmcaddr.get(nodename, {}).get('hardwaremanagement.manager', {}).get('value', '')
|
||||
if not bmcaddr:
|
||||
log.log({'error': 'Unable to get BMC address for {0]'.format(nodename)})
|
||||
else:
|
||||
bmcaddr = bmcaddr.split('/', 1)[0]
|
||||
wait_for_connection(bmcaddr)
|
||||
await wait_for_connection(bmcaddr)
|
||||
socket.getaddrinfo(bmcaddr, 443)
|
||||
subprocess.check_call(['/opt/confluent/bin/nodeconfig', nodename] + nodeconfig)
|
||||
if nodeconfig:
|
||||
await util.check_call(['/opt/confluent/bin/nodeconfig', nodename] + nodeconfig)
|
||||
log.log({'info': 'Configured {0} ({1})'.format(nodename,
|
||||
handler.devname)})
|
||||
if verification and handler.current_cert_self_signed():
|
||||
await handler.autosign_certificate()
|
||||
|
||||
info['discostatus'] = 'discovered'
|
||||
for i in pending_by_uuid.get(curruuid, []):
|
||||
eventlet.spawn_n(_recheck_single_unknown_info, cfg, i)
|
||||
tasks.spawn(_recheck_single_unknown_info(cfg, i))
|
||||
try:
|
||||
del pending_by_uuid[curruuid]
|
||||
except KeyError:
|
||||
@@ -1508,10 +1536,11 @@ def discover_node(cfg, handler, info, nodename, manual):
|
||||
info['discofailure'] = 'policy'
|
||||
return False
|
||||
|
||||
def wait_for_connection(bmcaddr):
|
||||
async def wait_for_connection(bmcaddr):
|
||||
cloop = asyncio.get_running_loop()
|
||||
expiry = 75 + util.monotonic_time()
|
||||
while util.monotonic_time() < expiry:
|
||||
for addrinf in socket.getaddrinfo(bmcaddr, 443, proto=socket.IPPROTO_TCP):
|
||||
for addrinf in await cloop.getaddrinfo(bmcaddr, 443, proto=socket.IPPROTO_TCP):
|
||||
try:
|
||||
tsock = socket.socket(addrinf[0])
|
||||
tsock.settimeout(1)
|
||||
@@ -1519,7 +1548,7 @@ def wait_for_connection(bmcaddr):
|
||||
return
|
||||
except OSError:
|
||||
continue
|
||||
eventlet.sleep(1)
|
||||
await asyncio.sleep(1)
|
||||
|
||||
def do_pxe_discovery(cfg, handler, info, manual, nodename, policies):
|
||||
# use uuid based scheme in lieu of tls cert, ideally only
|
||||
@@ -1568,19 +1597,19 @@ nodeaddhandler = None
|
||||
needaddhandled = False
|
||||
|
||||
|
||||
def _handle_nodelist_change(configmanager):
|
||||
async def _handle_nodelist_change(configmanager):
|
||||
global needaddhandled
|
||||
global nodeaddhandler
|
||||
macmap.vintage = 0 # the current mac map is probably inaccurate
|
||||
_recheck_nodes((), configmanager)
|
||||
await _recheck_nodes((), configmanager)
|
||||
if needaddhandled:
|
||||
needaddhandled = False
|
||||
nodeaddhandler = eventlet.spawn(_handle_nodelist_change, configmanager)
|
||||
nodeaddhandler = tasks.spawn_task(_handle_nodelist_change(configmanager))
|
||||
else:
|
||||
nodeaddhandler = None
|
||||
|
||||
|
||||
def newnodes(added, deleting, renamed, configmanager):
|
||||
async def newnodes(added, deleting, renamed, configmanager):
|
||||
global attribwatcher
|
||||
global needaddhandled
|
||||
global nodeaddhandler
|
||||
@@ -1603,20 +1632,20 @@ def newnodes(added, deleting, renamed, configmanager):
|
||||
if nodeaddhandler:
|
||||
needaddhandled = True
|
||||
else:
|
||||
nodeaddhandler = eventlet.spawn(_handle_nodelist_change, configmanager)
|
||||
nodeaddhandler = tasks.spawn_task(_handle_nodelist_change(configmanager))
|
||||
|
||||
|
||||
|
||||
rechecker = None
|
||||
rechecktime = None
|
||||
rechecklock = eventlet.semaphore.Semaphore()
|
||||
rechecklock = asyncio.Lock()
|
||||
|
||||
def _periodic_recheck(configmanager):
|
||||
async def _periodic_recheck(configmanager):
|
||||
global rechecker
|
||||
global rechecktime
|
||||
rechecker = None
|
||||
try:
|
||||
_recheck_nodes((), configmanager)
|
||||
await _recheck_nodes((), configmanager)
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
log.log({'error': 'Unexpected error during discovery, check debug '
|
||||
@@ -1625,35 +1654,36 @@ def _periodic_recheck(configmanager):
|
||||
# for rechecker was requested in the course of recheck_nodes
|
||||
if rechecker is None:
|
||||
rechecktime = util.monotonic_time() + 900
|
||||
rechecker = eventlet.spawn_after(900, _periodic_recheck,
|
||||
rechecker = tasks.spawn_task_after(900, _periodic_recheck,
|
||||
configmanager)
|
||||
|
||||
|
||||
def rescan():
|
||||
async def rescan():
|
||||
_map_unique_ids()
|
||||
global scanner
|
||||
if scanner:
|
||||
return
|
||||
else:
|
||||
scanner = eventlet.spawn(blocking_scan)
|
||||
remotescan()
|
||||
scanner = tasks.spawn_task(blocking_scan())
|
||||
await remotescan()
|
||||
|
||||
def remotescan():
|
||||
async def remotescan():
|
||||
mycfm = cfm.ConfigManager(None)
|
||||
myname = collective.get_myname()
|
||||
for remagent in get_subscriptions():
|
||||
try:
|
||||
affluent.renotify_me(remagent, mycfm, myname)
|
||||
await affluent.renotify_me(remagent, mycfm, myname)
|
||||
except Exception as e:
|
||||
log.log({'error': 'Unexpected problem asking {} for discovery notifications'.format(remagent)})
|
||||
|
||||
|
||||
def blocking_scan():
|
||||
async def blocking_scan():
|
||||
global scanner
|
||||
slpscan = eventlet.spawn(slp.active_scan, safe_detected, slp)
|
||||
ssdpscan = eventlet.spawn(ssdp.active_scan, safe_detected, ssdp)
|
||||
slpscan.wait()
|
||||
ssdpscan.wait()
|
||||
slpscan = tasks.spawn_task(slp.active_scan(safe_detected, slp))
|
||||
ssdpscan = tasks.spawn_task(ssdp.active_scan(safe_detected, ssdp))
|
||||
await slpscan
|
||||
await ssdpscan
|
||||
#ssdpscan.wait()
|
||||
scanner = None
|
||||
|
||||
def start_detection():
|
||||
@@ -1673,19 +1703,18 @@ def start_detection():
|
||||
start_autosense()
|
||||
if rechecker is None:
|
||||
rechecktime = util.monotonic_time() + 900
|
||||
rechecker = eventlet.spawn_after(900, _periodic_recheck, cfg)
|
||||
eventlet.spawn_n(ssdp.snoop, safe_detected, None, ssdp, get_node_by_uuid_or_mac)
|
||||
rechecker = tasks.spawn_task_after(900, _periodic_recheck, cfg)
|
||||
tasks.spawn(ssdp.snoop(safe_detected, None, ssdp, get_node_by_uuid_or_mac))
|
||||
|
||||
def stop_autosense():
|
||||
for watcher in list(autosensors):
|
||||
watcher.kill()
|
||||
watcher.cancel()
|
||||
autosensors.discard(watcher)
|
||||
|
||||
def start_autosense():
|
||||
autosensors.add(eventlet.spawn(slp.snoop, safe_detected, slp))
|
||||
#autosensors.add(eventlet.spawn(mdns.snoop, safe_detected, mdns))
|
||||
autosensors.add(eventlet.spawn(pxe.snoop, safe_detected, pxe, get_node_guess_by_uuid))
|
||||
eventlet.spawn(remotescan)
|
||||
autosensors.add(tasks.spawn_task(slp.snoop(safe_detected, slp)))
|
||||
tasks.spawn(pxe.snoop(safe_detected, pxe, get_node_guess_by_uuid))
|
||||
tasks.spawn(remotescan())
|
||||
|
||||
|
||||
nodes_by_fprint = {}
|
||||
@@ -1729,8 +1758,10 @@ def _map_unique_ids(nodes=None):
|
||||
nodes_by_fprint[fprint] = node
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
async def main():
|
||||
start_detection()
|
||||
while True:
|
||||
eventlet.sleep(30)
|
||||
await asyncio.sleep(30)
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.get_event_loop().run_until_complete(main())
|
||||
|
||||
@@ -15,24 +15,19 @@
|
||||
import confluent.discovery.handlers.generic as generic
|
||||
import confluent.exceptions as exc
|
||||
import confluent.netutil as netutil
|
||||
import eventlet.support.greendns
|
||||
|
||||
# Provide foundation for general IPMI device configuration
|
||||
|
||||
import pyghmi.exceptions as pygexc
|
||||
ipmicommand = eventlet.import_patched('pyghmi.ipmi.command')
|
||||
ipmicommand.session.select = eventlet.green.select
|
||||
ipmicommand.session.threading = eventlet.green.threading
|
||||
ipmicommand.session.socket.getaddrinfo = eventlet.support.greendns.getaddrinfo
|
||||
getaddrinfo = eventlet.support.greendns.getaddrinfo
|
||||
|
||||
import aiohmi.exceptions as pygexc
|
||||
import aiohmi.ipmi.command as ipmicommand
|
||||
|
||||
import socket
|
||||
|
||||
class NodeHandler(generic.NodeHandler):
|
||||
DEFAULT_USER = 'USERID'
|
||||
DEFAULT_PASS = 'PASSW0RD'
|
||||
|
||||
def _get_ipmicmd(self, user=None, password=None):
|
||||
async def _get_ipmicmd(self, user=None, password=None):
|
||||
priv = None
|
||||
if user is None or password is None:
|
||||
if self.trieddefault:
|
||||
@@ -42,7 +37,7 @@ class NodeHandler(generic.NodeHandler):
|
||||
user = self.DEFAULT_USER
|
||||
if password is None:
|
||||
password = self.DEFAULT_PASS
|
||||
return ipmicommand.Command(self.ipaddr, user, password,
|
||||
return await ipmicommand.create(self.ipaddr, user, password,
|
||||
privlevel=priv, keepalive=False)
|
||||
|
||||
def __init__(self, info, configmanager):
|
||||
@@ -56,7 +51,7 @@ class NodeHandler(generic.NodeHandler):
|
||||
def config(self, nodename, reset=False):
|
||||
self._bmcconfig(nodename, reset)
|
||||
|
||||
def _bmcconfig(self, nodename, reset=False, customconfig=None, vc=None):
|
||||
async def _bmcconfig(self, nodename, reset=False, customconfig=None, vc=None):
|
||||
# TODO(jjohnson2): set ip parameters, user/pass, alert cfg maybe
|
||||
# In general, try to use https automation, to make it consistent
|
||||
# between hypothetical secure path and today.
|
||||
@@ -69,7 +64,7 @@ class NodeHandler(generic.NodeHandler):
|
||||
passwd = creds.get(nodename, {}).get(
|
||||
'secret.hardwaremanagementpassword', {}).get('value', None)
|
||||
try:
|
||||
ic = self._get_ipmicmd()
|
||||
ic = await self._get_ipmicmd()
|
||||
passwd = self.DEFAULT_PASS
|
||||
except pygexc.IpmiException as pi:
|
||||
havecustomcreds = False
|
||||
@@ -82,14 +77,14 @@ class NodeHandler(generic.NodeHandler):
|
||||
else:
|
||||
passwd = self.DEFAULT_PASS
|
||||
if havecustomcreds:
|
||||
ic = self._get_ipmicmd(user, passwd)
|
||||
ic = await self._get_ipmicmd(user, passwd)
|
||||
else:
|
||||
raise
|
||||
if vc:
|
||||
ic.register_key_handler(vc)
|
||||
currusers = ic.get_users()
|
||||
lanchan = ic.get_network_channel()
|
||||
userdata = ic.xraw_command(netfn=6, command=0x44, data=(lanchan,
|
||||
currusers = await ic.get_users()
|
||||
lanchan = await ic.get_network_channel()
|
||||
userdata = await ic.xraw_command(netfn=6, command=0x44, data=(lanchan,
|
||||
1))
|
||||
userdata = bytearray(userdata['data'])
|
||||
maxusers = userdata[0] & 0b111111
|
||||
@@ -114,7 +109,7 @@ class NodeHandler(generic.NodeHandler):
|
||||
newuserslot = uid
|
||||
if newpass != passwd: # don't mess with existing if no change
|
||||
ic.set_user_password(newuserslot, password=newpass)
|
||||
ic = self._get_ipmicmd(user, passwd)
|
||||
ic = await self._get_ipmicmd(user, passwd)
|
||||
if vc:
|
||||
ic.register_key_handler(vc)
|
||||
break
|
||||
@@ -126,7 +121,7 @@ class NodeHandler(generic.NodeHandler):
|
||||
ic.set_user_password(newuserslot, password=newpass)
|
||||
ic.set_user_name(newuserslot, newuser)
|
||||
if havecustomcreds:
|
||||
ic = self._get_ipmicmd(user, passwd)
|
||||
ic = await self._get_ipmicmd(user, passwd)
|
||||
if vc:
|
||||
ic.register_key_handler(vc)
|
||||
#We are remote operating on the account we are
|
||||
@@ -161,13 +156,13 @@ class NodeHandler(generic.NodeHandler):
|
||||
'fe80::')):
|
||||
newip = cd['hardwaremanagement.manager']['value']
|
||||
newip = newip.split('/', 1)[0]
|
||||
newipinfo = getaddrinfo(newip, 0)[0]
|
||||
newipinfo = socket.getaddrinfo(newip, 0)[0]
|
||||
# This getaddrinfo is repeated in get_nic_config, could be
|
||||
# optimized, albeit with a more convoluted api..
|
||||
newip = newipinfo[-1][0]
|
||||
if ':' in newip:
|
||||
raise exc.NotImplementedException('IPv6 remote config TODO')
|
||||
netconfig = netutil.get_nic_config(cfg, nodename, ip=newip)
|
||||
netconfig = await netutil.get_nic_config(cfg, nodename, ip=newip)
|
||||
plen = netconfig['prefix']
|
||||
newip = '{0}/{1}'.format(newip, plen)
|
||||
currcfg = ic.get_net_configuration()
|
||||
|
||||
@@ -13,13 +13,13 @@
|
||||
# limitations under the License.
|
||||
|
||||
import confluent.discovery.handlers.bmc as bmchandler
|
||||
import eventlet
|
||||
import confluent.util as util
|
||||
try:
|
||||
from urllib import urlencode
|
||||
except ImportError:
|
||||
from urllib.parse import urlencode
|
||||
webclient = eventlet.import_patched('pyghmi.util.webclient')
|
||||
import aiohmi.util.webclient as webclient
|
||||
|
||||
|
||||
class NodeHandler(bmchandler.NodeHandler):
|
||||
DEFAULT_USER = 'admin'
|
||||
@@ -33,13 +33,13 @@ class NodeHandler(bmchandler.NodeHandler):
|
||||
fprint = util.get_fingerprint(self.https_cert)
|
||||
return util.cert_matches(fprint, certificate)
|
||||
|
||||
def get_webclient(self, user, passwd, newuser, newpass):
|
||||
wc = webclient.SecureHTTPConnection(self.ipaddr, 443,
|
||||
async def get_webclient(self, user, passwd, newuser, newpass):
|
||||
wc = webclient.WebConnection(self.ipaddr, 443,
|
||||
verifycallback=self.validate_cert)
|
||||
wc.connect()
|
||||
await wc.connect()
|
||||
authdata = urlencode({'username': user, 'password': passwd,
|
||||
'weblogsign': 1})
|
||||
res = wc.grab_json_response_with_status('/api/session', authdata)
|
||||
res = await wc.grab_json_response_with_status('/api/session', authdata)
|
||||
if res[1] == 200:
|
||||
if res[0].get('force_password', 1) == 0:
|
||||
# Need to handle password change
|
||||
@@ -51,12 +51,12 @@ class NodeHandler(bmchandler.NodeHandler):
|
||||
'privilege': 4,
|
||||
}
|
||||
passchange = urlencode(passchange)
|
||||
rsp = wc.grab_json_response_with_status('/api/reset-pass',
|
||||
rsp = await wc.grab_json_response_with_status('/api/reset-pass',
|
||||
passchange)
|
||||
rsp = wc.grab_json_response_with_status('/api/session',
|
||||
rsp = await wc.grab_json_response_with_status('/api/session',
|
||||
method='DELETE')
|
||||
|
||||
def config(self, nodename, reset=False):
|
||||
async def config(self, nodename, reset=False):
|
||||
self.nodename = nodename
|
||||
creds = self.configmanager.get_node_attributes(
|
||||
self.nodename, ['secret.hardwaremanagementuser',
|
||||
@@ -65,6 +65,6 @@ class NodeHandler(bmchandler.NodeHandler):
|
||||
user, passwd, isdefault = self.get_node_credentials(
|
||||
nodename, creds, 'admin', 'admin')
|
||||
if not isdefault:
|
||||
self.get_webclient(self.DEFAULT_USER, self.DEFAULT_PASS, user,
|
||||
await self.get_webclient(self.DEFAULT_USER, self.DEFAULT_PASS, user,
|
||||
passwd)
|
||||
self._bmcconfig(nodename, False)
|
||||
await self._bmcconfig(nodename, False)
|
||||
|
||||
@@ -14,9 +14,12 @@
|
||||
|
||||
import confluent.util as util
|
||||
import errno
|
||||
import eventlet
|
||||
import socket
|
||||
webclient = eventlet.import_patched('pyghmi.util.webclient')
|
||||
import aiohmi.util.webclient as webclient
|
||||
try:
|
||||
import cryptography.x509 as x509
|
||||
except ImportError:
|
||||
x509 = None
|
||||
|
||||
class NodeHandler(object):
|
||||
https_supported = True
|
||||
@@ -37,6 +40,7 @@ class NodeHandler(object):
|
||||
self.relay_server = None
|
||||
self.web_ip = None
|
||||
self.web_port = None
|
||||
self.https_cert = None
|
||||
# if this is a remote registered component, prefer to use the agent forwarder
|
||||
if info.get('forwarder_url', False):
|
||||
self.relay_url = info['forwarder_url']
|
||||
@@ -59,6 +63,40 @@ class NodeHandler(object):
|
||||
# may occur against the target in a short while
|
||||
return True
|
||||
|
||||
def current_cert_self_signed(self):
|
||||
if not x509:
|
||||
return
|
||||
if not self._ipaddr:
|
||||
return
|
||||
try:
|
||||
wc = webclient.WebConnection(self._ipaddr, verifycallback=self._savecert, port=443)
|
||||
wc.connect()
|
||||
wc.close()
|
||||
if not self._fp:
|
||||
return False
|
||||
# Check if certificate is self-signed by comparing issuer and subject
|
||||
cert = self._fp
|
||||
certobj = x509.load_der_x509_certificate(cert)
|
||||
skid = None
|
||||
akid = None
|
||||
for ext in certobj.extensions:
|
||||
if ext.oid == x509.ExtensionOID.SUBJECT_KEY_IDENTIFIER:
|
||||
skid = ext.value
|
||||
elif ext.oid == x509.ExtensionOID.AUTHORITY_KEY_IDENTIFIER:
|
||||
akid = ext.value
|
||||
if akid:
|
||||
if skid.digest == akid.key_identifier:
|
||||
return True
|
||||
elif certobj.issuer == certobj.subject:
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
|
||||
async def autosign_certificate(self):
|
||||
# A no-op by default
|
||||
return
|
||||
|
||||
def scan(self):
|
||||
# Do completely passive things to enhance data.
|
||||
# Probe is permitted to for example attempt a login
|
||||
@@ -70,7 +108,7 @@ class NodeHandler(object):
|
||||
# serial number and uuid to flesh out data as needed
|
||||
return
|
||||
|
||||
def preconfig(self, possiblenode):
|
||||
async def preconfig(self, possiblenode):
|
||||
return
|
||||
|
||||
def discoverable_by_switch(self, macs):
|
||||
@@ -114,14 +152,13 @@ class NodeHandler(object):
|
||||
elif self._certfailreason == 2:
|
||||
return 'unreachable'
|
||||
|
||||
@property
|
||||
def https_cert(self):
|
||||
async def get_https_cert(self):
|
||||
if self._fp:
|
||||
return self._fp
|
||||
ip, port = self.get_web_port_and_ip()
|
||||
wc = webclient.SecureHTTPConnection(ip, verifycallback=self._savecert, port=port)
|
||||
ip, port = await self.get_web_port_and_ip()
|
||||
wc = webclient.WebConnection(ip, verifycallback=self._savecert, port=port)
|
||||
try:
|
||||
wc.connect()
|
||||
await wc.request('GET', '/')
|
||||
except IOError as ie:
|
||||
if ie.errno == errno.ECONNREFUSED:
|
||||
self._certfailreason = 1
|
||||
@@ -134,16 +171,17 @@ class NodeHandler(object):
|
||||
except Exception:
|
||||
self._certfailreason = 2
|
||||
return None
|
||||
self.https_cert = self._fp
|
||||
return self._fp
|
||||
|
||||
def get_web_port_and_ip(self):
|
||||
async def get_web_port_and_ip(self):
|
||||
if self.web_ip:
|
||||
return self.web_ip, self.web_port
|
||||
# get target ip and port, either direct or relay as applicable
|
||||
if self.relay_url:
|
||||
kv = util.TLSCertVerifier(self.configmanager, self.relay_server,
|
||||
'pubkeys.tls_hardwaremanager').verify_cert
|
||||
w = webclient.SecureHTTPConnection(self.relay_server, verifycallback=kv)
|
||||
w = webclient.WebConnection(self.relay_server, verifycallback=kv)
|
||||
relaycreds = self.configmanager.get_node_attributes(self.relay_server, 'secret.*', decrypt=True)
|
||||
relaycreds = relaycreds.get(self.relay_server, {})
|
||||
relayuser = relaycreds.get('secret.hardwaremanagementuser', {}).get('value', None)
|
||||
@@ -151,8 +189,7 @@ class NodeHandler(object):
|
||||
if not relayuser or not relaypass:
|
||||
raise Exception('No credentials for {0}'.format(self.relay_server))
|
||||
w.set_basic_credentials(relayuser, relaypass)
|
||||
w.connect()
|
||||
w.request('GET', self.relay_url)
|
||||
await w.request('GET', self.relay_url)
|
||||
r = w.getresponse()
|
||||
rb = r.read()
|
||||
if r.code != 302:
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
|
||||
import codecs
|
||||
import confluent.discovery.handlers.bmc as bmchandler
|
||||
import pyghmi.exceptions as pygexc
|
||||
import pyghmi.ipmi.private.util as pygutil
|
||||
import aiohmi.exceptions as pygexc
|
||||
import aiohmi.ipmi.private.util as pygutil
|
||||
import confluent.util as util
|
||||
import struct
|
||||
|
||||
@@ -73,7 +73,7 @@ class NodeHandler(bmchandler.NodeHandler):
|
||||
if slot != 0:
|
||||
self.info['enclosure.bay'] = slot
|
||||
|
||||
def probe(self):
|
||||
async def probe(self):
|
||||
if self.info.get('enclosure.bay', 0) == 0:
|
||||
self.scan()
|
||||
if self.info.get('enclosure.bay', 0) != 0:
|
||||
@@ -85,8 +85,8 @@ class NodeHandler(bmchandler.NodeHandler):
|
||||
try:
|
||||
# we are a dense platform, but the SLP data did not give us slot
|
||||
# attempt to probe using IPMI
|
||||
ipmicmd = self._get_ipmicmd()
|
||||
guiddata = ipmicmd.xraw_command(netfn=6, command=8)
|
||||
ipmicmd = await self._get_ipmicmd()
|
||||
guiddata = await ipmicmd.xraw_command(netfn=6, command=8)
|
||||
self.info['uuid'] = pygutil.decode_wireformat_uuid(
|
||||
guiddata['data']).lower()
|
||||
ipmicmd.oem_init()
|
||||
|
||||
@@ -12,12 +12,9 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import asyncio
|
||||
|
||||
import confluent.discovery.handlers.redfishbmc as redfishbmc
|
||||
import eventlet.support.greendns
|
||||
|
||||
|
||||
getaddrinfo = eventlet.support.greendns.getaddrinfo
|
||||
|
||||
|
||||
class NodeHandler(redfishbmc.NodeHandler):
|
||||
|
||||
@@ -25,19 +22,19 @@ class NodeHandler(redfishbmc.NodeHandler):
|
||||
return ('admin', 'admin')
|
||||
|
||||
|
||||
def remote_nodecfg(nodename, cfm):
|
||||
async def remote_nodecfg(nodename, cfm):
|
||||
cfg = cfm.get_node_attributes(
|
||||
nodename, 'hardwaremanagement.manager')
|
||||
ipaddr = cfg.get(nodename, {}).get('hardwaremanagement.manager', {}).get(
|
||||
'value', None)
|
||||
ipaddr = ipaddr.split('/', 1)[0]
|
||||
ipaddr = getaddrinfo(ipaddr, 0)[0][-1]
|
||||
ipaddr = (await asyncio.get_event_loop().getaddrinfo(ipaddr, 0))[0][-1]
|
||||
if not ipaddr:
|
||||
raise Exception('Cannot remote configure a system without known '
|
||||
'address')
|
||||
info = {'addresses': [ipaddr]}
|
||||
nh = NodeHandler(info, cfm)
|
||||
nh.config(nodename)
|
||||
await nh.config(nodename)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -12,32 +12,28 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import asyncio
|
||||
import confluent.discovery.handlers.generic as generic
|
||||
import confluent.exceptions as exc
|
||||
import confluent.netutil as netutil
|
||||
import confluent.util as util
|
||||
import eventlet
|
||||
import eventlet.support.greendns
|
||||
import json
|
||||
try:
|
||||
from urllib import urlencode
|
||||
except ImportError:
|
||||
from urllib.parse import urlencode
|
||||
|
||||
getaddrinfo = eventlet.support.greendns.getaddrinfo
|
||||
|
||||
webclient = eventlet.import_patched('pyghmi.util.webclient')
|
||||
from socket import getaddrinfo
|
||||
|
||||
def get_host_interface_urls(wc, mginfo):
|
||||
import aiohmi.util.webclient as webclient
|
||||
|
||||
async def get_host_interface_urls(wc, mginfo):
|
||||
returls = []
|
||||
hifurl = mginfo.get('HostInterfaces', {}).get('@odata.id', None)
|
||||
if not hifurl:
|
||||
return []
|
||||
hifinfo = wc.grab_json_response(hifurl)
|
||||
hifinfo = await wc.grab_json_response(hifurl)
|
||||
hifurls = hifinfo.get('Members', [])
|
||||
for hifurl in hifurls:
|
||||
hifurl = hifurl['@odata.id']
|
||||
hifinfo = wc.grab_json_response(hifurl)
|
||||
hifinfo = await wc.grab_json_response(hifurl)
|
||||
acturl = hifinfo.get('ManagerEthernetInterface', {}).get('@odata.id', None)
|
||||
if acturl:
|
||||
returls.append(acturl)
|
||||
@@ -61,35 +57,36 @@ class NodeHandler(generic.NodeHandler):
|
||||
self._mgrinfo = None
|
||||
super(NodeHandler, self).__init__(info, configmanager)
|
||||
|
||||
def srvroot(self, wc):
|
||||
async def srvroot(self, wc):
|
||||
if not self._srvroot:
|
||||
srvroot, status = wc.grab_json_response_with_status('/redfish/v1/')
|
||||
srvroot, status = await wc.grab_json_response_with_status('/redfish/v1/')
|
||||
if status == 200:
|
||||
self._srvroot = srvroot
|
||||
return self._srvroot
|
||||
|
||||
def get_manager_url(self, wc):
|
||||
mgrs = self.srvroot(wc).get('Managers', {}).get('@odata.id', None)
|
||||
async def get_manager_url(self, wc):
|
||||
mgrs = (await self.srvroot(wc)).get('Managers', {}).get('@odata.id', None)
|
||||
if not mgrs:
|
||||
raise Exception("No Managers resource on BMC")
|
||||
rsp = wc.grab_json_response(mgrs)
|
||||
rsp = await wc.grab_json_response(mgrs)
|
||||
if len(rsp.get('Members', [])) != 1:
|
||||
raise Exception("Can not handle multiple Managers")
|
||||
mgrurl = rsp['Members'][0]['@odata.id']
|
||||
return mgrurl
|
||||
|
||||
def mgrinfo(self, wc):
|
||||
async def mgrinfo(self, wc):
|
||||
if not self._mgrinfo:
|
||||
self._mgrinfo = wc.grab_json_response(self.get_manager_url(wc))
|
||||
self._mgrinfo = await wc.grab_json_response(await self.get_manager_url(wc))
|
||||
return self._mgrinfo
|
||||
|
||||
|
||||
def get_firmware_default_account_info(self):
|
||||
raise Exception('This must be subclassed')
|
||||
|
||||
def scan(self):
|
||||
c = webclient.SecureHTTPConnection(self.ipaddr, 443, verifycallback=self.validate_cert)
|
||||
i = c.grab_json_response('/redfish/v1/')
|
||||
async def scan(self):
|
||||
await self.get_https_cert()
|
||||
c = webclient.WebConnection(self.ipaddr, 443, verifycallback=self.validate_cert)
|
||||
i = await c.grab_json_response('/redfish/v1/')
|
||||
uuid = i.get('UUID', None)
|
||||
if uuid:
|
||||
self.info['uuid'] = uuid.lower()
|
||||
@@ -100,18 +97,19 @@ class NodeHandler(generic.NodeHandler):
|
||||
fprint = util.get_fingerprint(self.https_cert)
|
||||
return util.cert_matches(fprint, certificate)
|
||||
|
||||
def enable_ipmi(self, wc):
|
||||
npu = self.mgrinfo(wc).get(
|
||||
async def enable_ipmi(self, wc):
|
||||
mgrinfo = await self.mgrinfo(wc)
|
||||
npu =mgrinfo.get(
|
||||
'NetworkProtocol', {}).get('@odata.id', None)
|
||||
if not npu:
|
||||
raise Exception('Cannot enable IPMI, no NetworkProtocol on BMC')
|
||||
npi = wc.grab_json_response(npu)
|
||||
npi = await wc.grab_json_response(npu)
|
||||
if not npi.get('IPMI', {}).get('ProtocolEnabled'):
|
||||
wc.set_header('If-Match', '*')
|
||||
wc.grab_json_response_with_status(
|
||||
await wc.grab_json_response_with_status(
|
||||
npu, {'IPMI': {'ProtocolEnabled': True}}, method='PATCH')
|
||||
acctinfo = wc.grab_json_response_with_status(
|
||||
self.target_account_url(wc))
|
||||
acctinfo = await wc.grab_json_response_with_status(
|
||||
await self.target_account_url(wc))
|
||||
acctinfo = acctinfo[0]
|
||||
actypes = acctinfo['AccountTypes']
|
||||
candidates = acctinfo.get('AccountTypes@Redfish.AllowableValues', [])
|
||||
@@ -121,18 +119,19 @@ class NodeHandler(generic.NodeHandler):
|
||||
'AccountTypes': actypes,
|
||||
'Password': self.currpass,
|
||||
}
|
||||
rsp = wc.grab_json_response_with_status(
|
||||
self.target_account_url(wc), acctupd, method='PATCH')
|
||||
rsp = await wc.grab_json_response_with_status(
|
||||
await self.target_account_url(wc), acctupd, method='PATCH')
|
||||
|
||||
def _get_wc(self):
|
||||
async def _get_wc(self):
|
||||
await self.get_https_cert()
|
||||
defuser, defpass = self.get_firmware_default_account_info()
|
||||
wc = webclient.SecureHTTPConnection(self.ipaddr, 443, verifycallback=self.validate_cert)
|
||||
wc = webclient.WebConnection(self.ipaddr, 443, verifycallback=self.validate_cert)
|
||||
wc.set_basic_credentials(defuser, defpass)
|
||||
wc.set_header('Content-Type', 'application/json')
|
||||
wc.set_header('Accept', 'application/json')
|
||||
authmode = 0
|
||||
if not self.trieddefault:
|
||||
rsp, status = wc.grab_json_response_with_status('/redfish/v1/Managers')
|
||||
rsp, status = await wc.grab_json_response_with_status('/redfish/v1/Managers')
|
||||
if status == 403:
|
||||
self.trieddefault = True
|
||||
chgurl = None
|
||||
@@ -156,30 +155,30 @@ class NodeHandler(generic.NodeHandler):
|
||||
if self.targpass == defpass:
|
||||
raise Exception("Must specify a non-default password to onboard this BMC")
|
||||
wc.set_header('If-Match', '*')
|
||||
cpr = wc.grab_json_response_with_status(chgurl, {'Password': self.targpass}, method='PATCH')
|
||||
cpr = await wc.grab_json_response_with_status(chgurl, {'Password': self.targpass}, method='PATCH')
|
||||
if cpr[1] >= 200 and cpr[1] < 300:
|
||||
self.curruser = defuser
|
||||
self.currpass = self.targpass
|
||||
wc.set_basic_credentials(self.curruser, self.currpass)
|
||||
_, status = wc.grab_json_response_with_status('/redfish/v1/Managers')
|
||||
_, status = await wc.grab_json_response_with_status('/redfish/v1/Managers')
|
||||
tries = 10
|
||||
while status >= 300 and tries:
|
||||
eventlet.sleep(1)
|
||||
_, status = wc.grab_json_response_with_status('/redfish/v1/Managers')
|
||||
await asyncio.sleep(1)
|
||||
_, status = await wc.grab_json_response_with_status('/redfish/v1/Managers')
|
||||
return wc
|
||||
|
||||
if status > 400:
|
||||
self.trieddefault = True
|
||||
if status == 401:
|
||||
wc.set_basic_credentials(defuser, self.targpass)
|
||||
rsp, status = wc.grab_json_response_with_status('/redfish/v1/Managers')
|
||||
rsp, status = await wc.grab_json_response_with_status('/redfish/v1/Managers')
|
||||
if status == 200: # Default user still, but targpass
|
||||
self.currpass = self.targpass
|
||||
self.curruser = defuser
|
||||
return wc
|
||||
elif self.targuser != defuser:
|
||||
wc.set_basic_credentials(self.targuser, self.targpass)
|
||||
rsp, status = wc.grab_json_response_with_status('/redfish/v1/Managers')
|
||||
rsp, status = await wc.grab_json_response_with_status('/redfish/v1/Managers')
|
||||
if status != 200:
|
||||
raise Exception("Target BMC does not recognize firmware default credentials nor the confluent stored credential")
|
||||
else:
|
||||
@@ -188,28 +187,29 @@ class NodeHandler(generic.NodeHandler):
|
||||
return wc
|
||||
if self.curruser:
|
||||
wc.set_basic_credentials(self.curruser, self.currpass)
|
||||
rsp, status = wc.grab_json_response_with_status('/redfish/v1/Managers')
|
||||
rsp, status = await wc.grab_json_response_with_status('/redfish/v1/Managers')
|
||||
if status != 200:
|
||||
return None
|
||||
return wc
|
||||
wc.set_basic_credentials(self.targuser, self.targpass)
|
||||
rsp, status = wc.grab_json_response_with_status('/redfish/v1/Managers')
|
||||
rsp, status = await wc.grab_json_response_with_status('/redfish/v1/Managers')
|
||||
if status != 200:
|
||||
return None
|
||||
self.curruser = self.targuser
|
||||
self.currpass = self.targpass
|
||||
return wc
|
||||
|
||||
def target_account_url(self, wc):
|
||||
asrv = self.srvroot(wc).get('AccountService', {}).get('@odata.id')
|
||||
rsp, status = wc.grab_json_response_with_status(asrv)
|
||||
async def target_account_url(self, wc):
|
||||
srvroot = await self.srvroot(wc)
|
||||
asrv = srvroot.get('AccountService', {}).get('@odata.id')
|
||||
rsp, status = await wc.grab_json_response_with_status(asrv)
|
||||
accts = rsp.get('Accounts', {}).get('@odata.id')
|
||||
rsp, status = wc.grab_json_response_with_status(accts)
|
||||
rsp, status = await wc.grab_json_response_with_status(accts)
|
||||
accts = rsp.get('Members', [])
|
||||
for accturl in accts:
|
||||
accturl = accturl.get('@odata.id', '')
|
||||
if accturl:
|
||||
rsp, status = wc.grab_json_response_with_status(accturl)
|
||||
rsp, status = await wc.grab_json_response_with_status(accturl)
|
||||
if rsp.get('UserName', None) == self.curruser:
|
||||
targaccturl = accturl
|
||||
break
|
||||
@@ -217,7 +217,7 @@ class NodeHandler(generic.NodeHandler):
|
||||
raise Exception("Unable to identify Account URL to modify on this BMC")
|
||||
return targaccturl
|
||||
|
||||
def config(self, nodename):
|
||||
async def config(self, nodename):
|
||||
mgrs = None
|
||||
self.nodename = nodename
|
||||
creds = self.configmanager.get_node_attributes(
|
||||
@@ -235,7 +235,7 @@ class NodeHandler(generic.NodeHandler):
|
||||
passwd = util.stringify(passwd)
|
||||
self.targuser = user
|
||||
self.targpass = passwd
|
||||
wc = self._get_wc()
|
||||
wc = await self._get_wc()
|
||||
curruserinfo = {}
|
||||
authupdate = {}
|
||||
wc.set_header('Content-Type', 'application/json')
|
||||
@@ -244,23 +244,23 @@ class NodeHandler(generic.NodeHandler):
|
||||
if passwd != self.currpass:
|
||||
authupdate['Password'] = passwd
|
||||
if authupdate:
|
||||
targaccturl = self.target_account_url(wc)
|
||||
rsp, status = wc.grab_json_response_with_status(targaccturl, authupdate, method='PATCH')
|
||||
targaccturl = await self.target_account_url(wc)
|
||||
rsp, status = await wc.grab_json_response_with_status(targaccturl, authupdate, method='PATCH')
|
||||
if status >= 300:
|
||||
raise Exception("Failed attempting to update credentials on BMC")
|
||||
self.curruser = user
|
||||
self.currpass = passwd
|
||||
wc.set_basic_credentials(user, passwd)
|
||||
_, status = wc.grab_json_response_with_status('/redfish/v1/Managers')
|
||||
_, status = await wc.grab_json_response_with_status('/redfish/v1/Managers')
|
||||
tries = 10
|
||||
while tries and status >= 300:
|
||||
tries -= 1
|
||||
eventlet.sleep(1.0)
|
||||
_, status = wc.grab_json_response_with_status(
|
||||
await asyncio.sleep(1.0)
|
||||
_, status = await wc.grab_json_response_with_status(
|
||||
'/redfish/v1/Managers')
|
||||
if (cd.get('hardwaremanagement.method', {}).get('value', 'ipmi') != 'redfish'
|
||||
or cd.get('console.method', {}).get('value', None) == 'ipmi'):
|
||||
self.enable_ipmi(wc)
|
||||
await self.enable_ipmi(wc)
|
||||
if ('hardwaremanagement.manager' in cd and
|
||||
cd['hardwaremanagement.manager']['value'] and
|
||||
not cd['hardwaremanagement.manager']['value'].startswith(
|
||||
@@ -271,9 +271,9 @@ class NodeHandler(generic.NodeHandler):
|
||||
newip = newipinfo[-1][0]
|
||||
if ':' in newip:
|
||||
raise exc.NotImplementedException('IPv6 remote config TODO')
|
||||
hifurls = get_host_interface_urls(wc, self.mgrinfo(wc))
|
||||
hifurls = await get_host_interface_urls(wc, self.mgrinfo(wc))
|
||||
mgtnicinfo = self.mgrinfo(wc)['EthernetInterfaces']['@odata.id']
|
||||
mgtnicinfo = wc.grab_json_response(mgtnicinfo)
|
||||
mgtnicinfo = await wc.grab_json_response(mgtnicinfo)
|
||||
mgtnics = [x['@odata.id'] for x in mgtnicinfo.get('Members', [])]
|
||||
actualnics = []
|
||||
for candnic in mgtnics:
|
||||
@@ -289,7 +289,7 @@ class NodeHandler(generic.NodeHandler):
|
||||
ipkey = 'IPv4Addresses'
|
||||
actualnic = None
|
||||
for curractnic in actualnics:
|
||||
currnicinfo = wc.grab_json_response(curractnic)
|
||||
currnicinfo = await wc.grab_json_response(curractnic)
|
||||
for targipaddr in currnicinfo.get(ipkey, []):
|
||||
targipaddr = targipaddr.get('Address', 'Z')
|
||||
if compip == targipaddr:
|
||||
@@ -300,8 +300,8 @@ class NodeHandler(generic.NodeHandler):
|
||||
else:
|
||||
raise Exception("Unable to detect active NIC of multi-nic bmc")
|
||||
actualnics = [actualnic]
|
||||
currnet = wc.grab_json_response(actualnics[0])
|
||||
netconfig = netutil.get_nic_config(self.configmanager, nodename, ip=newip)
|
||||
currnet = await wc.grab_json_response(actualnics[0])
|
||||
netconfig = await netutil.get_nic_config(self.configmanager, nodename, ip=newip)
|
||||
newconfig = {
|
||||
"Address": newip,
|
||||
"SubnetMask": netutil.cidr_to_mask(netconfig['prefix']),
|
||||
@@ -316,18 +316,29 @@ class NodeHandler(generic.NodeHandler):
|
||||
break
|
||||
else:
|
||||
wc.set_header('If-Match', '*')
|
||||
rsp, status = wc.grab_json_response_with_status(actualnics[0], {
|
||||
rsp, status = await wc.grab_json_response_with_status(actualnics[0], {
|
||||
'DHCPv4': {'DHCPEnabled': False},
|
||||
'IPv4StaticAddresses': [newconfig]}, method='PATCH')
|
||||
elif self.ipaddr.startswith('fe80::'):
|
||||
self.configmanager.set_node_attributes(
|
||||
await self.configmanager.set_node_attributes(
|
||||
{nodename: {'hardwaremanagement.manager': self.ipaddr}})
|
||||
else:
|
||||
raise exc.TargetEndpointUnreachable(
|
||||
'hardwaremanagement.manager must be set to desired address (No IPv6 Link Local detected)')
|
||||
|
||||
async def autosign_certificate(self):
|
||||
nodename = self.nodename
|
||||
hwmgt_method = self.configmanager.get_node_attributes(
|
||||
nodename, 'hardwaremanagement.method').get(
|
||||
nodename, {}).get('hardwaremanagement.method', {}).get('value', 'ipmi')
|
||||
if hwmgt_method != 'redfish':
|
||||
return
|
||||
proc = await asyncio.create_subprocess_exec(
|
||||
'/opt/confluent/bin/nodecertutil', nodename, 'signbmccert', '--days', '47'
|
||||
)
|
||||
await proc.wait()
|
||||
|
||||
def remote_nodecfg(nodename, cfm):
|
||||
async def remote_nodecfg(nodename, cfm):
|
||||
cfg = cfm.get_node_attributes(
|
||||
nodename, 'hardwaremanagement.manager')
|
||||
ipaddr = cfg.get(nodename, {}).get('hardwaremanagement.manager', {}).get(
|
||||
@@ -339,7 +350,7 @@ def remote_nodecfg(nodename, cfm):
|
||||
'address')
|
||||
info = {'addresses': [ipaddr]}
|
||||
nh = NodeHandler(info, cfm)
|
||||
nh.config(nodename)
|
||||
await nh.config(nodename)
|
||||
|
||||
if __name__ == '__main__':
|
||||
import confluent.config.configmanager as cfm
|
||||
|
||||
@@ -12,20 +12,19 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import asyncio
|
||||
import codecs
|
||||
import confluent.discovery.handlers.bmc as bmchandler
|
||||
import confluent.exceptions as exc
|
||||
import eventlet
|
||||
webclient = eventlet.import_patched('pyghmi.util.webclient')
|
||||
import aiohmi.util.webclient as webclient
|
||||
|
||||
import struct
|
||||
try:
|
||||
from urllib import urlencode
|
||||
except ImportError:
|
||||
from urllib.parse import urlencode
|
||||
import eventlet.support.greendns
|
||||
import confluent.netutil as netutil
|
||||
import confluent.util as util
|
||||
getaddrinfo = eventlet.support.greendns.getaddrinfo
|
||||
|
||||
from xml.etree.ElementTree import fromstring as rfromstring
|
||||
|
||||
@@ -91,7 +90,7 @@ class NodeHandler(bmchandler.NodeHandler):
|
||||
wc.request('POST', '/data', apirequest)
|
||||
wc.getresponse().read()
|
||||
|
||||
def _webconfignet(self, wc, nodename):
|
||||
async def _webconfignet(self, wc, nodename):
|
||||
cfg = self.configmanager
|
||||
if 'service:lenovo-smm2' in self.info.get('services', []):
|
||||
# need to enable ipmi for now..
|
||||
@@ -107,7 +106,7 @@ class NodeHandler(bmchandler.NodeHandler):
|
||||
if smmip:
|
||||
smmip = smmip.split('/', 1)[0]
|
||||
if smmip and ':' not in smmip:
|
||||
smmip = getaddrinfo(smmip, 0)[0]
|
||||
smmip = await asyncio.get_event_loop().getaddrinfo(smmip, 0)[0]
|
||||
smmip = smmip[-1][0]
|
||||
if smmip and ':' in smmip:
|
||||
raise exc.NotImplementedException('IPv6 not supported')
|
||||
@@ -118,7 +117,7 @@ class NodeHandler(bmchandler.NodeHandler):
|
||||
'ifConfig').find('v4IPAddr').text
|
||||
if currip == smmip:
|
||||
return
|
||||
netconfig = netutil.get_nic_config(cfg, nodename, ip=smmip)
|
||||
netconfig = await netutil.get_nic_config(cfg, nodename, ip=smmip)
|
||||
netmask = netutil.cidr_to_mask(netconfig['prefix'])
|
||||
setdata = 'set=ifIndex:0,v4DHCPEnabled:0,v4IPAddr:{0},v4NetMask:{1}'.format(smmip, netmask)
|
||||
gateway = netconfig.get('ipv4_gateway', None)
|
||||
@@ -138,7 +137,7 @@ class NodeHandler(bmchandler.NodeHandler):
|
||||
|
||||
def _webconfigcreds(self, username, password):
|
||||
ip, port = self.get_web_port_and_ip()
|
||||
wc = webclient.SecureHTTPConnection(ip, port, verifycallback=self._validate_cert)
|
||||
wc = webclient.WebConnection(ip, port, verifycallback=self._validate_cert)
|
||||
wc.connect()
|
||||
authdata = { # start by trying factory defaults
|
||||
'user': 'USERID',
|
||||
@@ -217,7 +216,7 @@ class NodeHandler(bmchandler.NodeHandler):
|
||||
wc.set_header('ST2', st2)
|
||||
return wc
|
||||
|
||||
def config(self, nodename):
|
||||
async def config(self, nodename):
|
||||
# SMM for now has to reset to assure configuration applies
|
||||
cd = self.configmanager.get_node_attributes(
|
||||
nodename, ['secret.hardwaremanagementuser',
|
||||
@@ -255,7 +254,7 @@ class NodeHandler(bmchandler.NodeHandler):
|
||||
# Switch to full web based configuration, to mitigate risks with the SMM
|
||||
wc = self._webconfigcreds(username, passwd)
|
||||
self._webconfigrules(wc)
|
||||
self._webconfignet(wc, nodename)
|
||||
await self._webconfignet(wc, nodename)
|
||||
|
||||
|
||||
# notes for smm:
|
||||
|
||||
@@ -12,16 +12,11 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import asyncio
|
||||
|
||||
import confluent.discovery.handlers.redfishbmc as redfishbmc
|
||||
import eventlet.support.greendns
|
||||
import confluent.util as util
|
||||
|
||||
webclient = eventlet.import_patched('pyghmi.util.webclient')
|
||||
|
||||
|
||||
|
||||
getaddrinfo = eventlet.support.greendns.getaddrinfo
|
||||
|
||||
|
||||
class NodeHandler(redfishbmc.NodeHandler):
|
||||
devname = 'SMM3'
|
||||
@@ -45,19 +40,19 @@ class NodeHandler(redfishbmc.NodeHandler):
|
||||
return ('USERID', 'PASSW0RD')
|
||||
|
||||
|
||||
def remote_nodecfg(nodename, cfm):
|
||||
async def remote_nodecfg(nodename, cfm):
|
||||
cfg = cfm.get_node_attributes(
|
||||
nodename, 'hardwaremanagement.manager')
|
||||
ipaddr = cfg.get(nodename, {}).get('hardwaremanagement.manager', {}).get(
|
||||
'value', None)
|
||||
ipaddr = ipaddr.split('/', 1)[0]
|
||||
ipaddr = getaddrinfo(ipaddr, 0)[0][-1]
|
||||
ipaddr = await asyncio.get_event_loop().getaddrinfo(ipaddr, 0)[0][-1]
|
||||
if not ipaddr:
|
||||
raise Exception('Cannot remote configure a system without known '
|
||||
'address')
|
||||
info = {'addresses': [ipaddr]}
|
||||
nh = NodeHandler(info, cfm)
|
||||
nh.config(nodename)
|
||||
await nh.config(nodename)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
@@ -67,5 +62,5 @@ if __name__ == '__main__':
|
||||
info = {'addresses': [[sys.argv[1]]]}
|
||||
print(repr(info))
|
||||
testr = NodeHandler(info, c)
|
||||
testr.config(sys.argv[2])
|
||||
asyncio.run(testr.config(sys.argv[2]))
|
||||
|
||||
|
||||
@@ -12,21 +12,16 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import asyncio
|
||||
import confluent.discovery.handlers.generic as generic
|
||||
import confluent.exceptions as exc
|
||||
import confluent.netutil as netutil
|
||||
import confluent.util as util
|
||||
import eventlet
|
||||
import eventlet.support.greendns
|
||||
import json
|
||||
try:
|
||||
from urllib import urlencode
|
||||
except ImportError:
|
||||
from urllib.parse import urlencode
|
||||
import socket
|
||||
from urllib.parse import urlencode
|
||||
|
||||
getaddrinfo = eventlet.support.greendns.getaddrinfo
|
||||
|
||||
webclient = eventlet.import_patched('pyghmi.util.webclient')
|
||||
import aiohmi.util.webclient as webclient
|
||||
|
||||
class NodeHandler(generic.NodeHandler):
|
||||
devname = 'TSM'
|
||||
@@ -45,9 +40,11 @@ class NodeHandler(generic.NodeHandler):
|
||||
self.atdefault = True
|
||||
super(NodeHandler, self).__init__(info, configmanager)
|
||||
|
||||
def scan(self):
|
||||
c = webclient.SecureHTTPConnection(self.ipaddr, 443, verifycallback=self.validate_cert)
|
||||
i = c.grab_json_response('/redfish/v1/')
|
||||
async def scan(self):
|
||||
await self.get_https_cert()
|
||||
c = webclient.WebConnection(
|
||||
self.ipaddr, 443, verifycallback=self.validate_cert)
|
||||
i = await c.grab_json_response('/redfish/v1/')
|
||||
uuid = i.get('UUID', None)
|
||||
if uuid:
|
||||
self.info['uuid'] = uuid.lower()
|
||||
@@ -58,20 +55,21 @@ class NodeHandler(generic.NodeHandler):
|
||||
fprint = util.get_fingerprint(self.https_cert)
|
||||
return util.cert_matches(fprint, certificate)
|
||||
|
||||
def _get_wc(self):
|
||||
async def _get_wc(self):
|
||||
authdata = { # start by trying factory defaults
|
||||
'username': self.DEFAULT_USER,
|
||||
'password': self.DEFAULT_PASS,
|
||||
}
|
||||
wc = webclient.SecureHTTPConnection(self.ipaddr, 443, verifycallback=self.validate_cert)
|
||||
await self.get_https_cert()
|
||||
wc = webclient.WebConnection(self.ipaddr, 443, verifycallback=self.validate_cert)
|
||||
wc.set_header('Content-Type', 'application/json')
|
||||
authmode = 0
|
||||
if not self.trieddefault:
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', authdata)
|
||||
rsp, status = await wc.grab_json_response_with_status('/api/session', authdata)
|
||||
if status == 403:
|
||||
wc.set_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||
authmode = 1
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', urlencode(authdata))
|
||||
rsp, status = await wc.grab_json_response_with_status('/api/session', urlencode(authdata))
|
||||
else:
|
||||
authmode = 2
|
||||
if status > 400:
|
||||
@@ -89,31 +87,31 @@ class NodeHandler(generic.NodeHandler):
|
||||
rpasschange = {
|
||||
'Password': self.targpass,
|
||||
}
|
||||
rwc = webclient.SecureHTTPConnection(
|
||||
rwc = webclient.WebConnection(
|
||||
self.ipaddr, 443,
|
||||
verifycallback=self.validate_cert)
|
||||
rwc.set_basic_credentials(authdata['username'],
|
||||
authdata['password'])
|
||||
rwc.set_header('If-Match', '*')
|
||||
rwc.set_header('Content-Type', 'application/json')
|
||||
rsp, status = rwc.grab_json_response_with_status(
|
||||
rsp, status = await rwc.grab_json_response_with_status(
|
||||
'/redfish/v1/AccountService/Accounts/1',
|
||||
rpasschange, method='PATCH')
|
||||
if status >= 200 and status < 300:
|
||||
authdata['password'] = self.targpass
|
||||
eventlet.sleep(10)
|
||||
await asyncio.sleep(10)
|
||||
else:
|
||||
if b'[web.lua] Error in RequestHandler, thread' in rsp:
|
||||
rsp, status = wc.grab_json_response_with_status('/api/reset-pass', passchange)
|
||||
rsp, status = await wc.grab_json_response_with_status('/api/reset-pass', passchange)
|
||||
else:
|
||||
raise Exception("Redfish may not have been ready yet" + repr(rsp))
|
||||
else:
|
||||
rsp, status = wc.grab_json_response_with_status('/api/reset-pass', urlencode(passchange))
|
||||
rsp, status = await wc.grab_json_response_with_status('/api/reset-pass', urlencode(passchange))
|
||||
authdata['password'] = self.targpass
|
||||
if authmode == 2:
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', authdata)
|
||||
rsp, status = await wc.grab_json_response_with_status('/api/session', authdata)
|
||||
else:
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', urlencode(authdata))
|
||||
rsp, status = await wc.grab_json_response_with_status('/api/session', urlencode(authdata))
|
||||
self.csrftok = rsp['CSRFToken']
|
||||
self.channel = rsp['channel']
|
||||
self.curruser = self.DEFAULT_USER
|
||||
@@ -129,10 +127,10 @@ class NodeHandler(generic.NodeHandler):
|
||||
authdata['username'] = self.curruser
|
||||
authdata['password'] = self.currpass
|
||||
if authmode != 1:
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', authdata)
|
||||
rsp, status = await wc.grab_json_response_with_status('/api/session', authdata)
|
||||
if authmode == 1 or status == 403:
|
||||
wc.set_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', urlencode(authdata))
|
||||
rsp, status = await wc.grab_json_response_with_status('/api/session', urlencode(authdata))
|
||||
if status != 200:
|
||||
return None
|
||||
self.csrftok = rsp['CSRFToken']
|
||||
@@ -141,10 +139,10 @@ class NodeHandler(generic.NodeHandler):
|
||||
authdata['username'] = self.targuser
|
||||
authdata['password'] = self.targpass
|
||||
if authmode != 1:
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', authdata)
|
||||
rsp, status = await wc.grab_json_response_with_status('/api/session', authdata)
|
||||
if authmode == 1 or status == 403:
|
||||
wc.set_header('Content-Type', 'application/x-www-form-urlencoded')
|
||||
rsp, status = wc.grab_json_response_with_status('/api/session', urlencode(authdata))
|
||||
rsp, status = await wc.grab_json_response_with_status('/api/session', urlencode(authdata))
|
||||
if status != 200:
|
||||
return None
|
||||
self.curruser = self.targuser
|
||||
@@ -153,7 +151,7 @@ class NodeHandler(generic.NodeHandler):
|
||||
self.channel = rsp['channel']
|
||||
return wc
|
||||
|
||||
def config(self, nodename):
|
||||
async def config(self, nodename):
|
||||
self.nodename = nodename
|
||||
creds = self.configmanager.get_node_attributes(
|
||||
nodename, ['secret.hardwaremanagementuser',
|
||||
@@ -167,7 +165,7 @@ class NodeHandler(generic.NodeHandler):
|
||||
passwd = util.stringify(passwd)
|
||||
self.targuser = user
|
||||
self.targpass = passwd
|
||||
wc = self._get_wc()
|
||||
wc = await self._get_wc()
|
||||
wc.set_header('X-CSRFTOKEN', self.csrftok)
|
||||
curruserinfo = {}
|
||||
authupdate = False
|
||||
@@ -202,7 +200,7 @@ class NodeHandler(generic.NodeHandler):
|
||||
'fe80::')):
|
||||
newip = cd['hardwaremanagement.manager']['value']
|
||||
newip = newip.split('/', 1)[0]
|
||||
newipinfo = getaddrinfo(newip, 0)[0]
|
||||
newipinfo = socket.getaddrinfo(newip, 0)[0]
|
||||
newip = newipinfo[-1][0]
|
||||
if ':' in newip:
|
||||
raise exc.NotImplementedException('IPv6 remote config TODO')
|
||||
@@ -214,7 +212,7 @@ class NodeHandler(generic.NodeHandler):
|
||||
if net['channel_number'] == self.channel:
|
||||
# we have found the interface to potentially manipulate
|
||||
if net['ipv4_address'] != newip:
|
||||
netconfig = netutil.get_nic_config(self.configmanager, nodename, ip=newip)
|
||||
netconfig = await netutil.get_nic_config(self.configmanager, nodename, ip=newip)
|
||||
newmask = netutil.cidr_to_mask(netconfig['prefix'])
|
||||
net['ipv4_address'] = newip
|
||||
net['ipv4_subnet'] = newmask
|
||||
@@ -239,7 +237,7 @@ def remote_nodecfg(nodename, cfm):
|
||||
ipaddr = cfg.get(nodename, {}).get('hardwaremanagement.manager', {}).get(
|
||||
'value', None)
|
||||
ipaddr = ipaddr.split('/', 1)[0]
|
||||
ipaddr = getaddrinfo(ipaddr, 0)[0][-1]
|
||||
ipaddr = socket.getaddrinfo(ipaddr, 0)[0][-1]
|
||||
if not ipaddr:
|
||||
raise Exception('Cannot remote configure a system without known '
|
||||
'address')
|
||||
@@ -254,4 +252,4 @@ if __name__ == '__main__':
|
||||
info = {'addresses': [[sys.argv[1]]] }
|
||||
print(repr(info))
|
||||
testr = NodeHandler(info, c)
|
||||
testr.config(sys.argv[2])
|
||||
testr.config(sys.argv[2])
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import asyncio
|
||||
import base64
|
||||
import codecs
|
||||
import confluent.discovery.handlers.imm as immhandler
|
||||
@@ -20,15 +21,12 @@ import confluent.exceptions as exc
|
||||
import confluent.netutil as netutil
|
||||
import confluent.util as util
|
||||
import errno
|
||||
import eventlet
|
||||
import eventlet.support.greendns
|
||||
import json
|
||||
import os
|
||||
import pyghmi.exceptions as pygexc
|
||||
import eventlet.green.socket as socket
|
||||
webclient = eventlet.import_patched('pyghmi.util.webclient')
|
||||
import aiohmi.exceptions as pygexc
|
||||
import socket
|
||||
import aiohmi.util.webclient as webclient
|
||||
import struct
|
||||
getaddrinfo = eventlet.support.greendns.getaddrinfo
|
||||
|
||||
|
||||
def fixuuid(baduuid):
|
||||
@@ -40,7 +38,8 @@ def fixuuid(baduuid):
|
||||
uuid = (a[:8], a[8:12], a[12:16], baduuid[19:23], baduuid[24:])
|
||||
return '-'.join(uuid).lower()
|
||||
|
||||
class LockedUserException(Exception):
|
||||
|
||||
class LockedUserException(BaseException):
|
||||
pass
|
||||
|
||||
|
||||
@@ -58,19 +57,19 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
self._currcreds = (None, None)
|
||||
super(NodeHandler, self).__init__(info, configmanager)
|
||||
|
||||
@property
|
||||
def ipaddr(self):
|
||||
async def get_ipaddr(self):
|
||||
if not self._ipaddr:
|
||||
cloop = asyncio.get_running_loop()
|
||||
lla = self.info.get('linklocal', '')
|
||||
tmplla = None
|
||||
if lla:
|
||||
for idx in util.list_interface_indexes():
|
||||
tmplla = '{0}%{1}'.format(lla, idx)
|
||||
addr = socket.getaddrinfo(tmplla, 443, 0, socket.SOCK_STREAM)[0][4]
|
||||
addr = (await cloop.getaddrinfo(tmplla, 443, 0, socket.SOCK_STREAM))[0][4]
|
||||
try:
|
||||
tsock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
|
||||
tsock.settimeout(1)
|
||||
tsock.connect(addr)
|
||||
tsock.setblocking(0)
|
||||
await asyncio.wait_for(cloop.sock_connect(tsock, addr), timeout=1)
|
||||
tsock.close()
|
||||
break
|
||||
except Exception:
|
||||
@@ -89,12 +88,13 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
def probe(self):
|
||||
return None
|
||||
|
||||
def scan(self):
|
||||
ip, port = self.get_web_port_and_ip()
|
||||
c = webclient.SecureHTTPConnection(ip, port,
|
||||
async def scan(self):
|
||||
ip, port = await self.get_web_port_and_ip()
|
||||
await self.get_https_cert()
|
||||
c = webclient.WebConnection(ip, port,
|
||||
verifycallback=self.validate_cert)
|
||||
try:
|
||||
i = c.grab_json_response('/api/providers/logoninfo')
|
||||
i = await c.grab_json_response('/api/providers/logoninfo')
|
||||
except Exception:
|
||||
return
|
||||
modelname = i.get('items', [{}])[0].get('machine_name', None)
|
||||
@@ -135,7 +135,7 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
if slot != 0:
|
||||
self.info['enclosure.bay'] = slot
|
||||
|
||||
def preconfig(self, possiblenode):
|
||||
async def preconfig(self, possiblenode):
|
||||
self.tmpnodename = possiblenode
|
||||
ff = self.info.get('attributes', {}).get('enclosure-form-factor', '')
|
||||
if ff not in ('dense-computing', [u'dense-computing']):
|
||||
@@ -156,17 +156,17 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
disableipmi = False
|
||||
if currfirm >= 3:
|
||||
# IPMI is disabled and we need it, also we need to go to *some* password
|
||||
wc = self.wc
|
||||
wc = await self.get_wc()
|
||||
if not wc:
|
||||
# We cannot try to enable SMM here without risking real credentials
|
||||
# on the wire to untrusted parties
|
||||
return
|
||||
wc.grab_json_response('/api/providers/logout')
|
||||
await wc.grab_json_response('/api/providers/logout')
|
||||
wc.set_basic_credentials(self._currcreds[0], self._currcreds[1])
|
||||
rsp = wc.grab_json_response('/redfish/v1/Managers/1/NetworkProtocol')
|
||||
rsp = await wc.grab_json_response('/redfish/v1/Managers/1/NetworkProtocol')
|
||||
if not rsp.get('IPMI', {}).get('ProtocolEnabled', True):
|
||||
disableipmi = True
|
||||
_, _ = wc.grab_json_response_with_status(
|
||||
_, _ = await wc.grab_json_response_with_status(
|
||||
'/redfish/v1/Managers/1/NetworkProtocol',
|
||||
{'IPMI': {'ProtocolEnabled': True}}, method='PATCH')
|
||||
ipmicmd = None
|
||||
@@ -179,13 +179,13 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
str(e) != 'Session no longer connected'):
|
||||
# raise an issue if anything other than to be expected
|
||||
if disableipmi:
|
||||
_, _ = wc.grab_json_response_with_status(
|
||||
_, _ = await wc.grab_json_response_with_status(
|
||||
'/redfish/v1/Managers/1/NetworkProtocol',
|
||||
{'IPMI': {'ProtocolEnabled': False}}, method='PATCH')
|
||||
raise
|
||||
self.trieddefault = True
|
||||
if disableipmi:
|
||||
_, _ = wc.grab_json_response_with_status(
|
||||
_, _ = await wc.grab_json_response_with_status(
|
||||
'/redfish/v1/Managers/1/NetworkProtocol',
|
||||
{'IPMI': {'ProtocolEnabled': False}}, method='PATCH')
|
||||
#TODO: decide how to clean out if important
|
||||
@@ -199,122 +199,105 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
fprint = util.get_fingerprint(self.https_cert)
|
||||
return util.cert_matches(fprint, certificate)
|
||||
|
||||
def get_webclient(self, username, password, newpassword):
|
||||
async def get_webclient(self, username, password, newpassword):
|
||||
wc = self._wc.dupe()
|
||||
try:
|
||||
wc.connect()
|
||||
except socket.error as se:
|
||||
if se.errno != errno.ECONNREFUSED:
|
||||
raise
|
||||
return (None, None)
|
||||
pwdchanged = False
|
||||
adata = json.dumps({'username': util.stringify(username),
|
||||
'password': util.stringify(password)
|
||||
})
|
||||
adata = {'username': util.stringify(username),
|
||||
'password': util.stringify(password)}
|
||||
headers = {'Connection': 'keep-alive',
|
||||
'Content-Type': 'application/json'}
|
||||
rsp, status = wc.grab_json_response_with_status('/api/providers/get_nonce', {})
|
||||
rsp, status = await wc.grab_json_response_with_status('/api/providers/get_nonce', {})
|
||||
nonce = None
|
||||
if status == 200:
|
||||
nonce = rsp.get('nonce', None)
|
||||
headers['Content-Security-Policy'] = 'nonce={0}'.format(nonce)
|
||||
wc.request('POST', '/api/login', adata, headers)
|
||||
rsp = wc.getresponse()
|
||||
try:
|
||||
rspdata = json.loads(rsp.read())
|
||||
except Exception:
|
||||
rspdata = {}
|
||||
if rsp.status != 200 and password == 'PASSW0RD':
|
||||
rspdata, status = await wc.grab_json_response_with_status('/api/login', adata, headers=headers)
|
||||
if status != 200 and password == 'PASSW0RD':
|
||||
rspdata = json.loads(rspdata)
|
||||
if rspdata.get('locktime', 0) > 0:
|
||||
raise LockedUserException(
|
||||
'The user "{0}" has been locked out for too many incorrect password attempts'.format(username))
|
||||
adata = json.dumps({
|
||||
adata = {
|
||||
'username': username,
|
||||
'password': newpassword,
|
||||
})
|
||||
}
|
||||
headers = {'Connection': 'keep-alive',
|
||||
'Content-Type': 'application/json'}
|
||||
if nonce:
|
||||
wc.request('POST', '/api/providers/get_nonce', '{}')
|
||||
rsp = wc.getresponse()
|
||||
tokbody = rsp.read()
|
||||
if rsp.status == 200:
|
||||
rsp = json.loads(tokbody)
|
||||
nonce = rsp.get('nonce', None)
|
||||
rsp = await wc.grab_json_response('/api/providers/get_nonce', {})
|
||||
nonce = rsp.get('nonce', None)
|
||||
if nonce:
|
||||
headers['Content-Security-Policy'] = 'nonce={0}'.format(nonce)
|
||||
wc.request('POST', '/api/login', adata, headers)
|
||||
rsp = wc.getresponse()
|
||||
try:
|
||||
rspdata = json.loads(rsp.read())
|
||||
except Exception:
|
||||
rspdata = {}
|
||||
if rsp.status == 200:
|
||||
rspdata = await wc.grab_json_response('/api/login', adata, headers=headers)
|
||||
if rspdata:
|
||||
pwdchanged = True
|
||||
password = newpassword
|
||||
wc.set_header('Authorization', 'Bearer ' + rspdata['access_token'])
|
||||
if '_csrf_token' in wc.cookies:
|
||||
wc.set_header('X-XSRF-TOKEN', wc.cookies['_csrf_token'])
|
||||
wc.grab_json_response_with_status('/api/providers/logout')
|
||||
for cookie in wc.cookies:
|
||||
if cookie.key.lower() == '_csrf_token':
|
||||
wc.set_header('X-XSRF-TOKEN', cookie.value)
|
||||
await wc.grab_json_response_with_status('/api/providers/logout')
|
||||
else:
|
||||
if rspdata.get('locktime', 0) > 0:
|
||||
raise LockedUserException(
|
||||
'The user "{0}" has been locked out for too many incorrect password attempts'.format(username))
|
||||
return (None, rspdata)
|
||||
if rsp.status == 200:
|
||||
if status == 200:
|
||||
self._currcreds = (username, password)
|
||||
wc.set_basic_credentials(username, password)
|
||||
wc.set_header('Content-Type', 'application/json')
|
||||
wc.set_header('Authorization', 'Bearer ' + rspdata['access_token'])
|
||||
if '_csrf_token' in wc.cookies:
|
||||
wc.set_header('X-XSRF-TOKEN', wc.cookies['_csrf_token'])
|
||||
for cookie in wc.cookies:
|
||||
if cookie.key.lower() == '_csrf_token':
|
||||
wc.set_header('X-XSRF-TOKEN', cookie.value)
|
||||
if rspdata.get('pwchg_required', None) == 'true':
|
||||
if newpassword is None:
|
||||
# a normal login hit expired condition
|
||||
tmppassword = 'Tmp42' + password[5:]
|
||||
wc.request('POST', '/api/function', json.dumps(
|
||||
{'USER_UserPassChange': '1,{0}'.format(tmppassword)}))
|
||||
rsp = wc.getresponse()
|
||||
rsp.read()
|
||||
await wc.grab_json_response(
|
||||
'/api/function',
|
||||
{'USER_UserPassChange': '1,{0}'.format(tmppassword)})
|
||||
# We must step down change interval and reusecycle to restore password
|
||||
wc.grab_json_response('/api/dataset', {'USER_GlobalMinPassChgInt': '0', 'USER_GlobalMinPassReuseCycle': '0'})
|
||||
wc.request('POST', '/api/function', json.dumps(
|
||||
{'USER_UserPassChange': '1,{0}'.format(password)}))
|
||||
rsp = wc.getresponse()
|
||||
rsp.read()
|
||||
await wc.grab_json_response(
|
||||
'/api/dataset',
|
||||
{'USER_GlobalMinPassChgInt': '0', 'USER_GlobalMinPassReuseCycle': '0'})
|
||||
await wc.grab_json_response(
|
||||
'/api/function',
|
||||
{'USER_UserPassChange': '1,{0}'.format(password)})
|
||||
return (wc, {})
|
||||
wc.request('POST', '/api/function', json.dumps(
|
||||
{'USER_UserPassChange': '1,{0}'.format(newpassword)}))
|
||||
rsp = wc.getresponse()
|
||||
rsp.read()
|
||||
if rsp.status != 200:
|
||||
rsp, status = await wc.grab_json_response_with_status(
|
||||
'/api/function',
|
||||
{'USER_UserPassChange': '1,{0}'.format(newpassword)})
|
||||
if status != 200:
|
||||
return (None, None)
|
||||
wc.grab_json_response_with_status('/api/providers/logout')
|
||||
await wc.grab_json_response_with_status('/api/providers/logout')
|
||||
self._currcreds = (username, newpassword)
|
||||
wc.set_basic_credentials(username, newpassword)
|
||||
pwdchanged = True
|
||||
if '_csrf_token' in wc.cookies:
|
||||
wc.set_header('X-XSRF-TOKEN', wc.cookies['_csrf_token'])
|
||||
for cookie in wc.cookies:
|
||||
if cookie.key.lower() == '_csrf_token':
|
||||
wc.set_header('X-XSRF-TOKEN', cookie.value)
|
||||
if pwdchanged:
|
||||
# Remove the minimum change interval, to allow sane
|
||||
# password changes after provisional changes
|
||||
wc = self.wc
|
||||
self.set_password_policy('', wc)
|
||||
wc = await self.get_wc()
|
||||
await self.set_password_policy('', wc)
|
||||
return (wc, pwdchanged)
|
||||
elif rspdata.get('locktime', 0) > 0:
|
||||
raise LockedUserException(
|
||||
'The user "{0}" has been locked out by too many incorrect password attempts'.format(username))
|
||||
return (None, rspdata)
|
||||
|
||||
@property
|
||||
def wc(self):
|
||||
async def get_wc(self):
|
||||
passwd = None
|
||||
isdefault = True
|
||||
errinfo = {}
|
||||
if self._wc is None:
|
||||
ip, port = self.get_web_port_and_ip()
|
||||
self._wc = webclient.SecureHTTPConnection(
|
||||
ip, port = await self.get_web_port_and_ip()
|
||||
await self.get_https_cert()
|
||||
self._wc = webclient.WebConnection(
|
||||
ip, port, verifycallback=self.validate_cert)
|
||||
self._wc.connect()
|
||||
# self._wc.connect()
|
||||
nodename = None
|
||||
if self.nodename:
|
||||
nodename = self.nodename
|
||||
@@ -323,7 +306,7 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
nodename = None
|
||||
inpreconfig = True
|
||||
if self._currcreds[0] is not None:
|
||||
wc, pwdchanged = self.get_webclient(self._currcreds[0], self._currcreds[1], None)
|
||||
wc, pwdchanged = await self.get_webclient(self._currcreds[0], self._currcreds[1], None)
|
||||
if wc:
|
||||
return wc
|
||||
if nodename:
|
||||
@@ -346,7 +329,7 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
# (TempW0rd42)
|
||||
passwd = 'TempW0rd42'
|
||||
try:
|
||||
wc, pwdchanged = self.get_webclient('USERID', 'PASSW0RD', passwd)
|
||||
wc, pwdchanged = await self.get_webclient('USERID', 'PASSW0RD', passwd)
|
||||
except LockedUserException as lue:
|
||||
wc = None
|
||||
pwdchanged = 'The user "USERID" has been locked out by too many incorrect password attempts'
|
||||
@@ -367,11 +350,11 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
if self.tmppasswd:
|
||||
if savedexc:
|
||||
raise savedexc
|
||||
wc, errinfo = self.get_webclient('USERID', self.tmppasswd, passwd)
|
||||
wc, errinfo = await self.get_webclient('USERID', self.tmppasswd, passwd)
|
||||
else:
|
||||
if user == 'USERID' and savedexc:
|
||||
raise savedexc
|
||||
wc, errinfo = self.get_webclient(user, passwd, None)
|
||||
wc, errinfo = await self.get_webclient(user, passwd, None)
|
||||
if wc:
|
||||
return wc
|
||||
else:
|
||||
@@ -379,7 +362,7 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
raise Exception('The stored confluent password for user "{}" was not accepted by the XCC'.format(user))
|
||||
raise Exception('Error connecting to webservice: ' + repr(errinfo))
|
||||
|
||||
def set_password_policy(self, strruleset, wc):
|
||||
async def set_password_policy(self, strruleset, wc):
|
||||
ruleset = {'USER_GlobalMinPassChgInt': '0'}
|
||||
for rule in strruleset.split(','):
|
||||
if '=' not in rule:
|
||||
@@ -400,20 +383,20 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
if name.lower() == 'reuse':
|
||||
ruleset['USER_GlobalMinPassReuseCycle'] = value
|
||||
try:
|
||||
wc.grab_json_response('/api/dataset', ruleset)
|
||||
await wc.grab_json_response('/api/dataset', ruleset)
|
||||
except Exception as e:
|
||||
print(repr(e))
|
||||
pass
|
||||
|
||||
def _get_next_userid(self, wc):
|
||||
userinfo = wc.grab_json_response('/api/dataset/imm_users')
|
||||
async def _get_next_userid(self, wc):
|
||||
userinfo = await wc.grab_json_response('/api/dataset/imm_users')
|
||||
userinfo = userinfo['items'][0]['users']
|
||||
for user in userinfo:
|
||||
if user['users_user_name'] == '':
|
||||
return user['users_user_id']
|
||||
|
||||
def create_tmp_account(self, wc):
|
||||
rsp, status = wc.grab_json_response_with_status('/redfish/v1/AccountService/Accounts')
|
||||
async def create_tmp_account(self, wc):
|
||||
rsp, status = await wc.grab_json_response_with_status('/redfish/v1/AccountService/Accounts')
|
||||
if status != 200:
|
||||
raise Exception("Unable to list current accounts")
|
||||
usednames = set([])
|
||||
@@ -423,25 +406,25 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
for acct in rsp.get("Members", []):
|
||||
url = acct.get("@odata.id", None)
|
||||
if url:
|
||||
uinfo = wc.grab_json_response(url)
|
||||
uinfo = await wc.grab_json_response(url)
|
||||
usednames.add(uinfo.get('UserName', None))
|
||||
if tmpnam in usednames:
|
||||
raise Exception("Tmp account already exists")
|
||||
rsp, status = wc.grab_json_response_with_status(
|
||||
rsp, status = await wc.grab_json_response_with_status(
|
||||
'/redfish/v1/AccountService/Accounts',
|
||||
{'UserName': tmpnam, 'Password': tpass, 'RoleId': 'Administrator'})
|
||||
if status >= 300:
|
||||
raise Exception("Failure creating tmp account: " + repr(rsp))
|
||||
tmpurl = rsp['@odata.id']
|
||||
wc.set_basic_credentials(tmpnam, tpass)
|
||||
rsp, status = wc.grab_json_response_with_status(
|
||||
rsp, status = await wc.grab_json_response_with_status(
|
||||
tmpurl, {'Password': ntpass}, method='PATCH')
|
||||
wc.set_basic_credentials(tmpnam, ntpass)
|
||||
return tmpurl
|
||||
|
||||
|
||||
def _setup_xcc_account(self, username, passwd, wc):
|
||||
userinfo = wc.grab_json_response('/api/dataset/imm_users')
|
||||
async def _setup_xcc_account(self, username, passwd, wc):
|
||||
userinfo = await wc.grab_json_response('/api/dataset/imm_users')
|
||||
uid = None
|
||||
for user in userinfo['items'][0]['users']:
|
||||
if user['users_user_name'] == username:
|
||||
@@ -456,64 +439,63 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
raise Exception("XCC has neither the default user nor configured user")
|
||||
# The following will work if the password is force change or normal..
|
||||
if self._needpasswordchange and self.tmppasswd != passwd:
|
||||
wc.grab_json_response('/api/function',
|
||||
await wc.grab_json_response('/api/function',
|
||||
{'USER_UserPassChange': '{0},{1}'.format(uid, passwd)})
|
||||
if username != 'USERID':
|
||||
rsp, status = wc.grab_json_response_with_status(
|
||||
rsp, status = await wc.grab_json_response_with_status(
|
||||
'/api/function',
|
||||
{'USER_UserModify': '{0},{1},,1,4,0,0,0,0,,8,'.format(uid, username)})
|
||||
if status == 200 and rsp.get('return', 0) == 762:
|
||||
rsp, status = wc.grab_json_response_with_status(
|
||||
rsp, status = await wc.grab_json_response_with_status(
|
||||
'/api/function',
|
||||
{'USER_UserModify': '{0},{1},,1,Administrator,0,0,0,0,,8,'.format(uid, username)})
|
||||
elif status == 200 and rsp.get('return', 0) == 13:
|
||||
rsp, status = wc.grab_json_response_with_status(
|
||||
rsp, status = await wc.grab_json_response_with_status(
|
||||
'/api/function',
|
||||
{'USER_UserModify': '{0},{1},,1,4,0,0,0,0,,8,,,'.format(uid, username)})
|
||||
if status == 200 and rsp.get('return', 0) == 13:
|
||||
wc.grab_json_response('/api/providers/logout')
|
||||
await wc.grab_json_response('/api/providers/logout')
|
||||
wc.set_basic_credentials(self._currcreds[0], self._currcreds[1])
|
||||
status = 503
|
||||
tries = 2
|
||||
tmpaccount = None
|
||||
while status != 200:
|
||||
tries -= 1
|
||||
rsp, status = wc.grab_json_response_with_status(
|
||||
rsp, status = await wc.grab_json_response_with_status(
|
||||
'/redfish/v1/AccountService/Accounts/{0}'.format(uid))
|
||||
if status >= 500:
|
||||
if tries < 0:
|
||||
raise Exception('Redfish account management failure')
|
||||
eventlet.sleep(30)
|
||||
await asyncio.sleep(30)
|
||||
continue
|
||||
rsp, status = wc.grab_json_response_with_status(
|
||||
rsp, status = await wc.grab_json_response_with_status(
|
||||
'/redfish/v1/AccountService/Accounts/{0}'.format(uid),
|
||||
{'UserName': username}, method='PATCH')
|
||||
if status != 200:
|
||||
rsp = json.loads(rsp)
|
||||
if rsp.get('error', {}).get('code', 'Unknown') in ('Base.1.8.GeneralError', 'Base.1.12.GeneralError', 'Base.1.14.GeneralError', 'Base.1.18.GeneralError', 'Base.1.21.GeneralError'):
|
||||
if tries:
|
||||
eventlet.sleep(4)
|
||||
await asyncio.sleep(4)
|
||||
elif tmpaccount:
|
||||
wc.grab_json_response_with_status(tmpaccount, method='DELETE')
|
||||
await wc.grab_json_response_with_status(tmpaccount, method='DELETE')
|
||||
raise Exception('Failed renaming main account')
|
||||
else:
|
||||
tmpaccount = self.create_tmp_account(wc)
|
||||
tmpaccount = await self.create_tmp_account(wc)
|
||||
tries = 8
|
||||
else:
|
||||
break
|
||||
if tmpaccount:
|
||||
wc.set_basic_credentials(username, passwd)
|
||||
wc.grab_json_response_with_status(tmpaccount, method='DELETE')
|
||||
await wc.grab_json_response_with_status(tmpaccount, method='DELETE')
|
||||
self.tmppasswd = None
|
||||
self._currcreds = (username, passwd)
|
||||
return
|
||||
self.tmppasswd = None
|
||||
wc.grab_json_response('/api/providers/logout')
|
||||
await wc.grab_json_response('/api/providers/logout')
|
||||
self._currcreds = (username, passwd)
|
||||
|
||||
def _convert_sha256account(self, user, passwd, wc):
|
||||
async def _convert_sha256account(self, user, passwd, wc):
|
||||
# First check if the specified user is sha256...
|
||||
userinfo = wc.grab_json_response('/api/dataset/imm_users')
|
||||
userinfo = await wc.grab_json_response('/api/dataset/imm_users')
|
||||
curruser = None
|
||||
uid = None
|
||||
user = util.stringify(user)
|
||||
@@ -524,62 +506,59 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
break
|
||||
if curruser and curruser.get('users_pass_is_sha256', 0):
|
||||
self._wc = None
|
||||
wc = self.wc
|
||||
wc = await self.get_wc()
|
||||
nwc = wc.dupe()
|
||||
# Have to convert it for being useful with most Lenovo automation tools
|
||||
# This requires deleting the account entirely and trying again
|
||||
tmpuid = self._get_next_userid(wc)
|
||||
tmpuid = await self._get_next_userid(wc)
|
||||
try:
|
||||
tpass = base64.b64encode(os.urandom(9)) + 'Iw47$'
|
||||
userparams = "{0},6pmu0ezczzcp,{1},1,4,0,0,0,0,,8,".format(tmpuid, tpass)
|
||||
result = wc.grab_json_response('/api/function', {'USER_UserCreate': userparams})
|
||||
wc.grab_json_response('/api/providers/logout')
|
||||
adata = json.dumps({
|
||||
result = await wc.grab_json_response('/api/function', {'USER_UserCreate': userparams})
|
||||
await wc.grab_json_response('/api/providers/logout')
|
||||
adata = {
|
||||
'username': '6pmu0ezczzcp',
|
||||
'password': tpass,
|
||||
})
|
||||
}
|
||||
headers = {'Connection': 'keep-alive', 'Content-Type': 'application/json'}
|
||||
wc.request('POST', '/api/providers/get_nonce', '{}')
|
||||
rsp = wc.getresponse()
|
||||
tokbody = rsp.read()
|
||||
if rsp.status == 200:
|
||||
rsp = json.loads(tokbody)
|
||||
rsp, status = await wc.grab_json_response('/api_providers/get_nonce', {})
|
||||
if status == 200:
|
||||
nonce = rsp.get('nonce', None)
|
||||
headers['Content-Security-Policy'] = 'nonce={0}'.format(nonce)
|
||||
nwc.request('POST', '/api/login', adata, headers)
|
||||
rsp = nwc.getresponse()
|
||||
if rsp.status == 200:
|
||||
rspdata = json.loads(rsp.read())
|
||||
rsp, status = await nwc.grab_json_response_with_status('/api/login', adata, headers=headers)
|
||||
if status == 200:
|
||||
rspdata = rsp
|
||||
nwc.set_header('Content-Type', 'application/json')
|
||||
nwc.set_header('Authorization', 'Bearer ' + rspdata['access_token'])
|
||||
if '_csrf_token' in wc.cookies:
|
||||
nwc.set_header('X-XSRF-TOKEN', wc.cookies['_csrf_token'])
|
||||
for cookie in wc.cookies:
|
||||
if cookie.key.lower() == '_csrf_token':
|
||||
nwc.set_header('X-XSRF-TOKEN', cookie.value)
|
||||
if rspdata.get('reason', False):
|
||||
newpass = base64.b64encode(os.urandom(9)) + 'q4J$'
|
||||
nwc.grab_json_response(
|
||||
await nwc.grab_json_response(
|
||||
'/api/function',
|
||||
{'USER_UserPassChange': '{0},{1}'.format(tmpuid, newpass)})
|
||||
nwc.grab_json_response('/api/function', {'USER_UserDelete': "{0},{1}".format(curruser['users_user_id'], user)})
|
||||
await nwc.grab_json_response('/api/function', {'USER_UserDelete': "{0},{1}".format(curruser['users_user_id'], user)})
|
||||
userparams = "{0},{1},{2},1,4,0,0,0,0,,8,".format(curruser['users_user_id'], user, tpass)
|
||||
nwc.grab_json_response('/api/function', {'USER_UserCreate': userparams})
|
||||
nwc.grab_json_response('/api/providers/logout')
|
||||
nwc, pwdchanged = self.get_webclient(user, tpass, passwd)
|
||||
await nwc.grab_json_response('/api/function', {'USER_UserCreate': userparams})
|
||||
await nwc.grab_json_response('/api/providers/logout')
|
||||
nwc, pwdchanged = await self.get_webclient(user, tpass, passwd)
|
||||
if not nwc:
|
||||
if not pwdchanged:
|
||||
pwdchanged = 'Unknown'
|
||||
raise Exception('Error converting from sha356account: ' + repr(pwdchanged))
|
||||
if not pwdchanged:
|
||||
nwc.grab_json_response(
|
||||
await nwc.grab_json_response(
|
||||
'/api/function',
|
||||
{'USER_UserPassChange': '{0},{1}'.format(curruser['users_user_id'], passwd)})
|
||||
nwc.grab_json_response('/api/providers/logout')
|
||||
await nwc.grab_json_response('/api/providers/logout')
|
||||
finally:
|
||||
self._wc = None
|
||||
wc = self.wc
|
||||
wc.grab_json_response('/api/function', {'USER_UserDelete': "{0},{1}".format(tmpuid, '6pmu0ezczzcp')})
|
||||
wc.grab_json_response('/api/providers/logout')
|
||||
wc = await self.get_wc()
|
||||
await wc.grab_json_response('/api/function', {'USER_UserDelete': "{0},{1}".format(tmpuid, '6pmu0ezczzcp')})
|
||||
await wc.grab_json_response('/api/providers/logout')
|
||||
|
||||
def config(self, nodename, reset=False):
|
||||
async def config(self, nodename, reset=False):
|
||||
self.nodename = nodename
|
||||
cd = self.configmanager.get_node_attributes(
|
||||
nodename, ['secret.hardwaremanagementuser',
|
||||
@@ -588,7 +567,8 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
True)
|
||||
cd = cd.get(nodename, {})
|
||||
targbmc = cd.get('hardwaremanagement.manager', {}).get('value', '')
|
||||
if not self.ipaddr.startswith('fe80::') and (targbmc.startswith('fe80::') or not targbmc):
|
||||
myipaddr = await self.get_ipaddr()
|
||||
if not myipaddr.startswith('fe80::') and (targbmc.startswith('fe80::') or not targbmc):
|
||||
raise exc.TargetEndpointUnreachable(
|
||||
'hardwaremanagement.manager must be set to desired address (No IPv6 Link Local detected)')
|
||||
# TODO(jjohnson2): set ip parameters, user/pass, alert cfg maybe
|
||||
@@ -598,32 +578,32 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
nodename, 'discovery.passwordrules')
|
||||
strruleset = dpp.get(nodename, {}).get(
|
||||
'discovery.passwordrules', {}).get('value', '')
|
||||
wc = self.wc
|
||||
wc = await self.get_wc()
|
||||
creds = self.configmanager.get_node_attributes(
|
||||
self.nodename, ['secret.hardwaremanagementuser',
|
||||
'secret.hardwaremanagementpassword'], decrypt=True)
|
||||
user, passwd, isdefault = self.get_node_credentials(nodename, creds, 'USERID', 'PASSW0RD')
|
||||
self.set_password_policy(strruleset, wc)
|
||||
await self.set_password_policy(strruleset, wc)
|
||||
if self._atdefaultcreds:
|
||||
if isdefault and self.tmppasswd:
|
||||
raise Exception(
|
||||
'Request to use default credentials, but refused by target after it has been changed to {0}'.format(self.tmppasswd))
|
||||
if not isdefault:
|
||||
self._setup_xcc_account(user, passwd, wc)
|
||||
wc = self.wc
|
||||
self._convert_sha256account(user, passwd, wc)
|
||||
await self._setup_xcc_account(user, passwd, wc)
|
||||
wc = await self.get_wc()
|
||||
await self._convert_sha256account(user, passwd, wc)
|
||||
if (cd.get('hardwaremanagement.method', {}).get('value', 'ipmi') != 'redfish'
|
||||
or cd.get('console.method', {}).get('value', None) == 'ipmi'):
|
||||
nwc = wc.dupe()
|
||||
nwc.set_basic_credentials(self._currcreds[0], self._currcreds[1])
|
||||
rsp = nwc.grab_json_response('/redfish/v1/Managers/1/NetworkProtocol')
|
||||
rsp = await nwc.grab_json_response('/redfish/v1/Managers/1/NetworkProtocol')
|
||||
if not rsp.get('IPMI', {}).get('ProtocolEnabled', True):
|
||||
# User has indicated IPMI support, but XCC is currently disabled
|
||||
# change XCC to be consistent
|
||||
_, _ = nwc.grab_json_response_with_status(
|
||||
_, _ = await nwc.grab_json_response_with_status(
|
||||
'/redfish/v1/Managers/1/NetworkProtocol',
|
||||
{'IPMI': {'ProtocolEnabled': True}}, method='PATCH')
|
||||
rsp, status = nwc.grab_json_response_with_status(
|
||||
rsp, status = await nwc.grab_json_response_with_status(
|
||||
'/redfish/v1/AccountService/Accounts/1')
|
||||
if status == 200:
|
||||
allowable = rsp.get('AccountTypes@Redfish.AllowableValues', [])
|
||||
@@ -634,35 +614,36 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
'AccountTypes': current,
|
||||
'Password': self._currcreds[1]
|
||||
}
|
||||
rsp, status = nwc.grab_json_response_with_status(
|
||||
rsp, status = await nwc.grab_json_response_with_status(
|
||||
'/redfish/v1/AccountService/Accounts/1',
|
||||
updateinf, method='PATCH')
|
||||
if targbmc and not targbmc.startswith('fe80::'):
|
||||
attribsuffix = ''
|
||||
newip = targbmc.split('/', 1)[0]
|
||||
newipinfo = getaddrinfo(newip, 0)[0]
|
||||
newip = newipinfo[-1][0]
|
||||
cloop = asyncio.get_running_loop()
|
||||
newipinfo = await cloop.getaddrinfo(newip, 0)
|
||||
newip = newipinfo[0][-1][0]
|
||||
if ':' in newip:
|
||||
raise exc.NotImplementedException('IPv6 remote config TODO')
|
||||
netconfig = netutil.get_nic_config(self.configmanager, nodename, ip=targbmc)
|
||||
netconfig = await netutil.get_nic_config(self.configmanager, nodename, ip=targbmc)
|
||||
newmask = netutil.cidr_to_mask(netconfig['prefix'])
|
||||
currinfo = wc.grab_json_response('/api/providers/logoninfo')
|
||||
currinfo = await wc.grab_json_response('/api/providers/logoninfo')
|
||||
currip = currinfo.get('items', [{}])[0].get('ipv4_address', '')
|
||||
curreth1 = wc.grab_json_response('/api/dataset/imm_ethernet')
|
||||
curreth1 = await wc.grab_json_response('/api/dataset/imm_ethernet')
|
||||
if curreth1:
|
||||
if self.ipaddr.startswith('fe80::'):
|
||||
if myipaddr.startswith('fe80::'):
|
||||
ipkey = 'ipv6_link_local_address'
|
||||
elif '.' in self.ipaddr:
|
||||
elif '.' in myipaddr:
|
||||
ipkey = 'ipv4_address'
|
||||
else:
|
||||
raise Exception('Non-Link-Local IPv6 TODO')
|
||||
nic1ip = curreth1.get('items', [{}])[0].get(ipkey, None)
|
||||
if nic1ip != self.ipaddr:
|
||||
if nic1ip != myipaddr:
|
||||
# check second nic instead
|
||||
curreth2 = wc.grab_json_response('/api/dataset/imm_ethernet_2')
|
||||
curreth2 = await wc.grab_json_response('/api/dataset/imm_ethernet_2')
|
||||
if curreth2 and curreth2.get('items', [{}])[0].get('if_second_port_exist', 0):
|
||||
nic2ip = curreth2.get('items', [{}])[0].get(ipkey + '_2', None)
|
||||
if nic2ip != self.ipaddr:
|
||||
if nic2ip != myipaddr:
|
||||
raise Exception("Unable to determine which NIC is active")
|
||||
# ok, second nic is active, target it
|
||||
currip = curreth2.get('items', [{}])[0].get("ipv4_address", None)
|
||||
@@ -675,21 +656,21 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
}
|
||||
if netconfig['ipv4_gateway']:
|
||||
statargs['ENET_IPv4GatewayIPAddr'] = netconfig['ipv4_gateway']
|
||||
elif not netutil.address_is_local(newip):
|
||||
elif not await netutil.address_is_local(newip):
|
||||
raise exc.InvalidArgumentException('Will not remotely configure a device with no gateway')
|
||||
if attribsuffix:
|
||||
for currkey in list(statargs):
|
||||
statargs[currkey + attribsuffix] = statargs[currkey]
|
||||
del statargs[currkey]
|
||||
netset, status = wc.grab_json_response_with_status('/api/dataset', statargs)
|
||||
netset, status = await wc.grab_json_response_with_status('/api/dataset', statargs)
|
||||
|
||||
elif self.ipaddr.startswith('fe80::'):
|
||||
self.configmanager.set_node_attributes(
|
||||
{nodename: {'hardwaremanagement.manager': self.ipaddr}})
|
||||
elif myipaddr.startswith('fe80::'):
|
||||
await self.configmanager.set_node_attributes(
|
||||
{nodename: {'hardwaremanagement.manager': myipaddr}})
|
||||
else:
|
||||
raise exc.TargetEndpointUnreachable(
|
||||
'hardwaremanagement.manager must be set to desired address (No IPv6 Link Local detected)')
|
||||
wc.grab_json_response('/api/providers/logout')
|
||||
await wc.grab_json_response('/api/providers/logout')
|
||||
ff = self.info.get('attributes', {}).get('enclosure-form-factor', '')
|
||||
if ff not in ('dense-computing', [u'dense-computing']):
|
||||
return
|
||||
@@ -702,27 +683,38 @@ class NodeHandler(immhandler.NodeHandler):
|
||||
'value', None)
|
||||
# ok, set the uuid of the manager...
|
||||
if em:
|
||||
self.configmanager.set_node_attributes(
|
||||
await self.configmanager.set_node_attributes(
|
||||
{em: {'id.uuid': enclosureuuid}})
|
||||
async def autosign_certificate(self):
|
||||
nodename = self.nodename
|
||||
hwmgt_method = self.configmanager.get_node_attributes(
|
||||
nodename, 'hardwaremanagement.method').get(
|
||||
nodename, {}).get('hardwaremanagement.method', {}).get('value', 'ipmi')
|
||||
if hwmgt_method != 'redfish':
|
||||
return
|
||||
await util.check_call('/opt/confluent/bin/nodecertutil', nodename, 'signbmccert', '--days', '47')
|
||||
|
||||
def remote_nodecfg(nodename, cfm):
|
||||
|
||||
async def remote_nodecfg(nodename, cfm):
|
||||
cfg = cfm.get_node_attributes(
|
||||
nodename, 'hardwaremanagement.manager')
|
||||
ipaddr = cfg.get(nodename, {}).get('hardwaremanagement.manager', {}).get(
|
||||
'value', None)
|
||||
ipaddr = ipaddr.split('/', 1)[0]
|
||||
ipaddr = getaddrinfo(ipaddr, 0)[0][-1]
|
||||
cloop = asyncio.get_running_loop()
|
||||
newipinfo = await cloop.getaddrinfo(ipaddr, 0)
|
||||
ipaddr = newipinfo[0][-1][0]
|
||||
if not ipaddr:
|
||||
raise Exception('Cannot remote configure a system without known '
|
||||
'address')
|
||||
info = {'addresses': [ipaddr]}
|
||||
ipaddr = ipaddr[0]
|
||||
wc = webclient.SecureHTTPConnection(
|
||||
wc = webclient.WebConnection(
|
||||
ipaddr, 443, verifycallback=lambda x: True)
|
||||
rsp = wc.grab_json_response('/DeviceDescription.json')
|
||||
rsp = await wc.grab_json_response('/DeviceDescription.json')
|
||||
if isinstance(rsp, list):
|
||||
nh = NodeHandler(info, cfm)
|
||||
else:
|
||||
nh = xcc3handler.NodeHandler(info, cfm)
|
||||
nh.config(nodename)
|
||||
await nh.config(nodename)
|
||||
|
||||
|
||||
@@ -13,14 +13,9 @@
|
||||
# limitations under the License.
|
||||
|
||||
import confluent.discovery.handlers.redfishbmc as redfishbmc
|
||||
import eventlet.support.greendns
|
||||
import confluent.util as util
|
||||
|
||||
webclient = eventlet.import_patched('pyghmi.util.webclient')
|
||||
|
||||
|
||||
|
||||
getaddrinfo = eventlet.support.greendns.getaddrinfo
|
||||
import socket
|
||||
import aiohmi.util.webclient as webclient
|
||||
|
||||
|
||||
class NodeHandler(redfishbmc.NodeHandler):
|
||||
@@ -32,12 +27,13 @@ class NodeHandler(redfishbmc.NodeHandler):
|
||||
def get_manager_url(self, wc):
|
||||
return '/redfish/v1/Managers/1'
|
||||
|
||||
def scan(self):
|
||||
ip, port = self.get_web_port_and_ip()
|
||||
c = webclient.SecureHTTPConnection(ip, port,
|
||||
async def scan(self):
|
||||
ip, port = await self.get_web_port_and_ip()
|
||||
await self.get_https_cert()
|
||||
c = webclient.WebConnection(ip, port,
|
||||
verifycallback=self.validate_cert)
|
||||
c.set_header('Accept', 'application/json')
|
||||
i = c.grab_json_response('/api/providers/logoninfo')
|
||||
i = await c.grab_json_response('/api/providers/logoninfo')
|
||||
modelname = i.get('items', [{}])[0].get('machine_name', None)
|
||||
if modelname:
|
||||
self.info['modelname'] = modelname
|
||||
@@ -87,7 +83,7 @@ def remote_nodecfg(nodename, cfm):
|
||||
ipaddr = cfg.get(nodename, {}).get('hardwaremanagement.manager', {}).get(
|
||||
'value', None)
|
||||
ipaddr = ipaddr.split('/', 1)[0]
|
||||
ipaddr = getaddrinfo(ipaddr, 0)[0][-1]
|
||||
ipaddr = socket.getaddrinfo(ipaddr, 0)[0][-1]
|
||||
if not ipaddr:
|
||||
raise Exception('Cannot remote configure a system without known '
|
||||
'address')
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
# NTS: ssdp:alive
|
||||
|
||||
|
||||
import asyncio
|
||||
import confluent.config.configmanager as cfm
|
||||
import confluent.collective.manager as collective
|
||||
import confluent.neighutil as neighutil
|
||||
@@ -35,16 +36,15 @@ import confluent.noderange as noderange
|
||||
import confluent.util as util
|
||||
import confluent.log as log
|
||||
import confluent.netutil as netutil
|
||||
import eventlet
|
||||
import eventlet.green.select as select
|
||||
import eventlet.green.socket as socket
|
||||
import eventlet.greenpool as gp
|
||||
import confluent.tasks as tasks
|
||||
import socket
|
||||
import os
|
||||
import time
|
||||
import struct
|
||||
import traceback
|
||||
|
||||
webclient = eventlet.import_patched('pyghmi.util.webclient')
|
||||
import aiohmi.util.webclient as webclient
|
||||
|
||||
mcastv4addr = '224.0.0.251'
|
||||
mcastv6addr = 'ff02::fb'
|
||||
|
||||
@@ -94,14 +94,14 @@ def _process_snoop(peer, rsp, mac, known_peers, newmacs, peerbymacaddress, byeha
|
||||
}
|
||||
if sdata.get('ttl', 0) == 0:
|
||||
if byehandler:
|
||||
eventlet.spawn_n(check_fish_handler, byehandler, peerdata, known_peers, newmacs, peerbymacaddress, machandlers, mac, peer)
|
||||
tasks.spawn(check_fish_handler(byehandler, peerdata, known_peers, newmacs, peerbymacaddress, machandlers, mac, peer))
|
||||
return 1
|
||||
if handler:
|
||||
eventlet.spawn_n(check_fish_handler, handler, peerdata, known_peers, newmacs, peerbymacaddress, machandlers, mac, peer)
|
||||
tasks.spawn(check_fish_handler(handler, peerdata, known_peers, newmacs, peerbymacaddress, machandlers, mac, peer))
|
||||
return 2
|
||||
|
||||
def check_fish_handler(handler, peerdata, known_peers, newmacs, peerbymacaddress, machandlers, mac, peer):
|
||||
retdata = check_fish(('/redfish/v1/', peerdata))
|
||||
async def check_fish_handler(handler, peerdata, known_peers, newmacs, peerbymacaddress, machandlers, mac, peer):
|
||||
retdata = await check_fish(('/redfish/v1/', peerdata))
|
||||
if retdata:
|
||||
known_peers.add(peer)
|
||||
newmacs.add(mac)
|
||||
@@ -109,7 +109,7 @@ def check_fish_handler(handler, peerdata, known_peers, newmacs, peerbymacaddress
|
||||
machandlers[mac] = handler
|
||||
|
||||
|
||||
def snoop(handler, byehandler=None, protocol=None, uuidlookup=None):
|
||||
async def snoop(handler, byehandler=None, protocol=None, uuidlookup=None):
|
||||
"""Watch for unsolicited mDNS answers
|
||||
|
||||
The handler shall be called on any service coming online.
|
||||
@@ -131,7 +131,7 @@ def snoop(handler, byehandler=None, protocol=None, uuidlookup=None):
|
||||
net6.bind(('', 5353))
|
||||
net4.bind(('', 5353))
|
||||
try:
|
||||
active_scan(handler, protocol)
|
||||
await active_scan(handler, protocol)
|
||||
except Exception as e:
|
||||
tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
|
||||
event=log.Events.stacktrace)
|
||||
@@ -151,43 +151,58 @@ def snoop(handler, byehandler=None, protocol=None, uuidlookup=None):
|
||||
# errno 98 can happen if aliased, skip for now
|
||||
raise
|
||||
peerbymacaddress = {}
|
||||
newmacs = set([])
|
||||
deferrednotifies = []
|
||||
machandlers = {}
|
||||
pktq = asyncio.Queue()
|
||||
cloop = asyncio.get_running_loop()
|
||||
cloop.add_reader(net4, _relay_pkt, net4, pktq)
|
||||
cloop.add_reader(net6, _relay_pkt, net6, pktq)
|
||||
while True:
|
||||
try:
|
||||
newmacs = set([])
|
||||
deferrednotifies = []
|
||||
machandlers = {}
|
||||
r = select.select((net4, net6), (), (), 60)
|
||||
if r:
|
||||
r = r[0]
|
||||
recent_peers = set([])
|
||||
while r and len(deferrednotifies) < 256:
|
||||
for s in r:
|
||||
(rsp, peer) = s.recvfrom(9000)
|
||||
if peer in recent_peers:
|
||||
newmacs.clear()
|
||||
deferrednotifies.clear()
|
||||
machandlers.clear()
|
||||
timeout = None
|
||||
srp = await pktq.get()
|
||||
recent_peers.clear()
|
||||
while srp and len(deferrednotifies) < 256:
|
||||
srp = None
|
||||
if timeout is None:
|
||||
srp = await pktq.get()
|
||||
else:
|
||||
try:
|
||||
srp = await asyncio.wait_for(pktq.get(), timeout=timeout)
|
||||
except asyncio.exceptions.TimeoutError:
|
||||
break
|
||||
timeout = 0.2
|
||||
s, rsp, peer = srp
|
||||
if peer in recent_peers:
|
||||
continue
|
||||
mac = await neighutil.get_hwaddr(peer[0])
|
||||
if mac == False:
|
||||
continue
|
||||
if not mac:
|
||||
probepeer = (peer[0], struct.unpack('H', os.urandom(2))[0] | 1025) + peer[2:]
|
||||
try:
|
||||
s.setblocking(1)
|
||||
s.sendto(b'\x00', probepeer)
|
||||
except Exception:
|
||||
continue
|
||||
mac = neighutil.get_hwaddr(peer[0])
|
||||
if not mac:
|
||||
probepeer = (peer[0], struct.unpack('H', os.urandom(2))[0] | 1025) + peer[2:]
|
||||
try:
|
||||
s.sendto(b'\x00', probepeer)
|
||||
except Exception:
|
||||
continue
|
||||
deferrednotifies.append((peer, rsp))
|
||||
datum = _process_snoop(peer, rsp, mac, known_peers, newmacs, peerbymacaddress, byehandler, machandlers, handler)
|
||||
if datum == 2:
|
||||
recent_peers.add(peer)
|
||||
r = select.select((net4, net6), (), (), 1.5)
|
||||
if r:
|
||||
r = r[0]
|
||||
deferrednotifies.append((peer, rsp))
|
||||
continue
|
||||
datum = _process_snoop(peer, rsp, mac, known_peers, newmacs, peerbymacaddress, byehandler, machandlers, handler)
|
||||
if datum == 2:
|
||||
recent_peers.add(peer)
|
||||
if deferrednotifies:
|
||||
eventlet.sleep(2.2)
|
||||
await asyncio.sleep(2.2)
|
||||
for peerrsp in deferrednotifies:
|
||||
peer, rsp = peerrsp
|
||||
mac = neighutil.get_hwaddr(peer[0])
|
||||
mac = await neighutil.get_hwaddr(peer[0])
|
||||
if not mac:
|
||||
continue
|
||||
_process_snoop(peer, rsp, mac, known_peers, newmacs, peerbymacaddress, byehandler, machandlers, handler)
|
||||
for mac in newmacs:
|
||||
for mac in list(newmacs):
|
||||
thehandler = machandlers.get(mac, None)
|
||||
if thehandler:
|
||||
thehandler(peerbymacaddress[mac])
|
||||
@@ -211,8 +226,16 @@ def get_sockets():
|
||||
net4 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
net4.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
return net4, net6
|
||||
|
||||
def active_scan(handler, protocol=None):
|
||||
|
||||
def _relay_pkt(sock, pktq):
|
||||
sock.setblocking(0)
|
||||
try:
|
||||
rsp, peer = sock.recvfrom(9000)
|
||||
except socket.error:
|
||||
return
|
||||
pktq.put_nowait((sock, rsp, peer))
|
||||
|
||||
async def active_scan(handler, protocol=None):
|
||||
net4, net6 = get_sockets()
|
||||
for idx in util.list_interface_indexes():
|
||||
net6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_MULTICAST_IF,
|
||||
@@ -234,31 +257,40 @@ def active_scan(handler, protocol=None):
|
||||
if se.errno != 101 and se.errno != 1:
|
||||
raise
|
||||
deadline = util.monotonic_time() + 2
|
||||
r, _, _ = select.select((net4, net6), (), (), 2)
|
||||
pktq = asyncio.Queue()
|
||||
cloop = asyncio.get_running_loop()
|
||||
cloop.add_reader(net4, _relay_pkt, net4, pktq)
|
||||
cloop.add_reader(net6, _relay_pkt, net6, pktq)
|
||||
peerdata = {}
|
||||
deferparse = []
|
||||
while r:
|
||||
for s in r:
|
||||
(rsp, peer) = s.recvfrom(9000)
|
||||
if not neighutil.get_hwaddr(peer[0]):
|
||||
probepeer = (peer[0], struct.unpack('H', os.urandom(2))[0] | 1025) + peer[2:]
|
||||
try:
|
||||
s.sendto(b'\x00', probepeer)
|
||||
except Exception:
|
||||
continue
|
||||
deferparse.append((rsp, peer))
|
||||
srp = True
|
||||
timeout = 2
|
||||
while timeout and srp and len(deferparse) < 256:
|
||||
try:
|
||||
srp = await asyncio.wait_for(pktq.get(), timeout)
|
||||
except asyncio.exceptions.TimeoutError:
|
||||
break
|
||||
s, rsp, peer = srp
|
||||
if not await neighutil.get_hwaddr(peer[0]):
|
||||
probepeer = (peer[0], struct.unpack('H', os.urandom(2))[0] | 1025) + peer[2:]
|
||||
try:
|
||||
s.setblocking(1)
|
||||
s.sendto(b'\x00', probepeer)
|
||||
except Exception:
|
||||
srp = True
|
||||
continue
|
||||
_parse_mdns(peer, rsp, peerdata, '_obmc_console._tcp.local')
|
||||
deferparse.append((rsp, peer))
|
||||
srp = True
|
||||
continue
|
||||
await _parse_mdns(peer, rsp, peerdata, '_obmc_console._tcp.local')
|
||||
timeout = deadline - util.monotonic_time()
|
||||
if timeout < 0:
|
||||
timeout = 0
|
||||
r, _, _ = select.select((net4, net6), (), (), timeout)
|
||||
if deferparse:
|
||||
eventlet.sleep(2.2)
|
||||
await asyncio.sleep(2.2)
|
||||
for dp in deferparse:
|
||||
rsp, peer = dp
|
||||
_parse_mdns(peer, rsp, peerdata, '_obmc_console._tcp.local')
|
||||
querypool = gp.GreenPool()
|
||||
await _parse_mdns(peer, rsp, peerdata, '_obmc_console._tcp.local')
|
||||
pooltargs = []
|
||||
for nid in peerdata:
|
||||
if '/redfish/v1/' not in peerdata[nid].get('urls', ()) and '/redfish/v1' not in peerdata[nid].get('urls', ()):
|
||||
@@ -271,17 +303,24 @@ def active_scan(handler, protocol=None):
|
||||
# or we drop support for those devices
|
||||
#else:
|
||||
# pooltargs.append(('/redfish/v1/', peerdata[nid]))
|
||||
for pi in querypool.imap(check_fish, pooltargs):
|
||||
if pi is not None:
|
||||
handler(pi)
|
||||
tsks = []
|
||||
for targ in pooltargs:
|
||||
tsks.append(tasks.spawn_task(check_fish(targ)))
|
||||
while tsks:
|
||||
done, tsks = await asyncio.wait(tsks, return_when=asyncio.FIRST_COMPLETED)
|
||||
for dt in done:
|
||||
dt = await dt
|
||||
if dt is None:
|
||||
continue
|
||||
handler(dt)
|
||||
|
||||
def check_fish(urldata, port=443, verifycallback=None):
|
||||
async def check_fish(urldata, port=443, verifycallback=None):
|
||||
if not verifycallback:
|
||||
verifycallback = lambda x: True
|
||||
url, data = urldata
|
||||
try:
|
||||
wc = webclient.SecureHTTPConnection(_get_svrip(data), port, verifycallback=verifycallback, timeout=1.5)
|
||||
peerinfo = wc.grab_json_response(url)
|
||||
wc = webclient.WebConnection(_get_svrip(data), port, verifycallback=verifycallback, timeout=1.5)
|
||||
peerinfo = await wc.grab_json_response(url)
|
||||
except socket.error:
|
||||
return None
|
||||
if url == '/DeviceDescription.json':
|
||||
@@ -370,14 +409,14 @@ def _mdns_to_dict(rsp):
|
||||
return retval
|
||||
|
||||
|
||||
def _parse_mdns(peer, rsp, peerdata, srvname):
|
||||
async def _parse_mdns(peer, rsp, peerdata, srvname):
|
||||
parsed = _mdns_to_dict(rsp)
|
||||
if not parsed:
|
||||
return
|
||||
if parsed.get('ttl', 0) == 0:
|
||||
return
|
||||
nid = peer[0]
|
||||
mac = neighutil.get_hwaddr(peer[0])
|
||||
mac = await neighutil.get_hwaddr(peer[0])
|
||||
if mac:
|
||||
nid = mac
|
||||
if nid in peerdata:
|
||||
@@ -396,10 +435,10 @@ def _parse_mdns(peer, rsp, peerdata, srvname):
|
||||
peerdata[nid] = peerdatum
|
||||
|
||||
|
||||
def _parse_ssdp(peer, rsp, peerdata):
|
||||
async def _parse_ssdp(peer, rsp, peerdata):
|
||||
nid = peer[0]
|
||||
mac = None
|
||||
mac = neighutil.get_hwaddr(peer[0])
|
||||
mac = await neighutil.get_hwaddr(peer[0])
|
||||
if mac:
|
||||
nid = mac
|
||||
headlines = rsp.split(b'\r\n')
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
# option 97 = UUID (wireformat)
|
||||
|
||||
import asyncio
|
||||
import base64
|
||||
import confluent.config.conf as inifile
|
||||
import confluent.config.configmanager as cfm
|
||||
@@ -31,21 +32,20 @@ import confluent.neighutil as neighutil
|
||||
import confluent.log as log
|
||||
import confluent.netutil as netutil
|
||||
import confluent.util as util
|
||||
import confluent.tasks as tasks
|
||||
import ctypes
|
||||
import ctypes.util
|
||||
import eventlet
|
||||
import eventlet.green.socket as socket
|
||||
import eventlet.green.select as select
|
||||
try:
|
||||
import psutil
|
||||
except ImportError:
|
||||
psutil = None
|
||||
import netifaces
|
||||
import os
|
||||
import socket
|
||||
import struct
|
||||
import time
|
||||
import traceback
|
||||
import uuid
|
||||
import confluent.tasks as tasks
|
||||
|
||||
libc = ctypes.CDLL(ctypes.util.find_library('c'))
|
||||
|
||||
@@ -82,47 +82,13 @@ class sockaddr_ll(ctypes.Structure):
|
||||
('sll_halen', ctypes.c_ubyte),
|
||||
('sll_addr', ctypes.c_ubyte * 20)]
|
||||
|
||||
class iovec(ctypes.Structure): # from uio.h
|
||||
_fields_ = [('iov_base', ctypes.c_void_p),
|
||||
('iov_len', ctypes.c_size_t)]
|
||||
|
||||
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)]
|
||||
|
||||
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)]
|
||||
# ignore the __extension__
|
||||
|
||||
class in_addr(ctypes.Structure):
|
||||
_fields_ = [('s_addr', ctypes.c_uint32)]
|
||||
|
||||
class in_pktinfo(ctypes.Structure): # from bits/in.h
|
||||
_fields_ = [('ipi_ifindex', ctypes.c_int),
|
||||
('ipi_spec_dst', in_addr),
|
||||
('ipi_addr', in_addr)]
|
||||
|
||||
class sockaddr_in(ctypes.Structure):
|
||||
_fields_ = [('sin_family', ctypes.c_ushort), # per bits/sockaddr.h
|
||||
('sin_port', ctypes.c_uint16), # per netinet/in.h
|
||||
('sin_addr', in_addr)]
|
||||
|
||||
|
||||
sendto = libc.sendto
|
||||
sendto.argtypes = [ctypes.c_int, ctypes.c_void_p, ctypes.c_size_t,
|
||||
ctypes.c_int, ctypes.POINTER(sockaddr_ll),
|
||||
ctypes.c_size_t]
|
||||
sendto.restype = ctypes.c_size_t
|
||||
recvmsg = libc.recvmsg
|
||||
recvmsg.argtypes = [ctypes.c_int, ctypes.POINTER(msghdr), ctypes.c_int]
|
||||
recvmsg.restype = ctypes.c_size_t
|
||||
|
||||
|
||||
pkttype = ctypes.c_char * 2048
|
||||
|
||||
@@ -154,20 +120,6 @@ def get_bcastaddr(idx):
|
||||
IP_PKTINFO = 8
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
cmsgtype = ctypes.c_char * CMSG_SPACE(ctypes.sizeof(in_pktinfo)).value
|
||||
cmsgsize = CMSG_SPACE(ctypes.sizeof(in_pktinfo)).value
|
||||
|
||||
pxearchs = {
|
||||
b'\x00\x00': 'bios-x86',
|
||||
b'\x00\x07': 'uefi-x64',
|
||||
@@ -301,58 +253,49 @@ def opts_to_dict(rq, optidx, expectype=1):
|
||||
def ipfromint(numb):
|
||||
return socket.inet_ntoa(struct.pack('I', numb))
|
||||
|
||||
def proxydhcp(handler, nodeguess):
|
||||
def relay_proxydhcp(sock, pktq):
|
||||
sock.setblocking(0)
|
||||
data, cmsgs, flags, peer = sock.recvmsg(9000, 9000)
|
||||
if len(data) < 240:
|
||||
return
|
||||
try:
|
||||
optidx = data.index(b'\x63\x82\x53\x63') + 4
|
||||
except ValueError:
|
||||
return
|
||||
for cmsg in cmsgs:
|
||||
level, typ, cdata = cmsg
|
||||
if level == socket.IPPROTO_IP and typ == IP_PKTINFO:
|
||||
idx, recv = struct.unpack('II', cdata[:8])
|
||||
recv = ipfromint(recv)
|
||||
break
|
||||
else:
|
||||
return
|
||||
rq = memoryview(data)
|
||||
hwlen = rq[2]
|
||||
opts, disco = opts_to_dict(rq, optidx, 3)
|
||||
disco['hwaddr'] = ':'.join(['{0:02x}'.format(x) for x in rq[28:28+hwlen]])
|
||||
node = None
|
||||
if disco.get('hwaddr', None) in macmap:
|
||||
node = macmap[disco['hwaddr']]
|
||||
elif disco.get('uuid', None) in uuidmap:
|
||||
node = uuidmap[disco['uuid']]
|
||||
myipn = myipbypeer.get(data[28:28+hwlen], None)
|
||||
skiplogging = True
|
||||
pktq.put_nowait((disco, peer, myipn, idx, recv, node, opts, data))
|
||||
|
||||
|
||||
async def proxydhcp(handler, nodeguess):
|
||||
net4011 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
net4011.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
net4011.setsockopt(socket.IPPROTO_IP, IP_PKTINFO, 1)
|
||||
net4011.bind(('', 4011))
|
||||
rp = bytearray(300)
|
||||
rpv = memoryview(rp)
|
||||
rq = bytearray(2048)
|
||||
data = pkttype.from_buffer(rq)
|
||||
msg = msghdr()
|
||||
cmsgarr = bytearray(cmsgsize)
|
||||
cmsg = cmsgtype.from_buffer(cmsgarr)
|
||||
iov = iovec()
|
||||
iov.iov_base = ctypes.addressof(data)
|
||||
iov.iov_len = 2048
|
||||
msg.msg_iov = ctypes.pointer(iov)
|
||||
msg.msg_iovlen = 1
|
||||
msg.msg_control = ctypes.addressof(cmsg)
|
||||
msg.msg_controllen = ctypes.sizeof(cmsg)
|
||||
clientaddr = sockaddr_in()
|
||||
msg.msg_name = ctypes.addressof(clientaddr)
|
||||
msg.msg_namelen = ctypes.sizeof(clientaddr)
|
||||
cloop = asyncio.get_running_loop()
|
||||
pktq = asyncio.Queue()
|
||||
cloop.add_reader(net4011, relay_proxydhcp, net4011, pktq)
|
||||
cfg = cfm.ConfigManager(None)
|
||||
while True:
|
||||
try:
|
||||
ready = select.select([net4011], [], [], None)
|
||||
if not ready or not ready[0]:
|
||||
continue
|
||||
i = recvmsg(net4011.fileno(), ctypes.pointer(msg), 0)
|
||||
#nb, client = net4011.recvfrom_into(rq)
|
||||
if i < 240:
|
||||
continue
|
||||
rqv = memoryview(rq)[:i]
|
||||
client = (ipfromint(clientaddr.sin_addr.s_addr), socket.htons(clientaddr.sin_port))
|
||||
_, level, typ = struct.unpack('QII', cmsgarr[:16])
|
||||
if level == socket.IPPROTO_IP and typ == IP_PKTINFO:
|
||||
idx, recv = struct.unpack('II', cmsgarr[16:24])
|
||||
recv = ipfromint(recv)
|
||||
try:
|
||||
optidx = rqv.tobytes().index(b'\x63\x82\x53\x63') + 4
|
||||
except ValueError:
|
||||
continue
|
||||
hwlen = rqv[2]
|
||||
opts, disco = opts_to_dict(rqv, optidx, 3)
|
||||
disco['hwaddr'] = ':'.join(['{0:02x}'.format(x) for x in rqv[28:28+hwlen]])
|
||||
node = None
|
||||
if disco.get('hwaddr', None) in macmap:
|
||||
node = macmap[disco['hwaddr']]
|
||||
elif disco.get('uuid', None) in uuidmap:
|
||||
node = uuidmap[disco['uuid']]
|
||||
myipn = myipbypeer.get(rqv[28:28+hwlen].tobytes(), None)
|
||||
skiplogging = True
|
||||
disco, client, myipn, idx, recv, node, opts, data = await pktq.get()
|
||||
netaddr = disco['hwaddr']
|
||||
if time.time() > ignoredisco.get(netaddr, 0) + 90:
|
||||
skiplogging = False
|
||||
@@ -406,6 +349,9 @@ def proxydhcp(handler, nodeguess):
|
||||
'for this boot method.'.format(
|
||||
node, profile, len(bootfile) - 127)})
|
||||
continue
|
||||
rp = bytearray(300)
|
||||
rpv = memoryview(rp)
|
||||
rqv = memoryview(data)
|
||||
rpv[:240] = rqv[:240].tobytes()
|
||||
rpv[0:1] = b'\x02'
|
||||
rpv[108:108 + len(bootfile)] = bootfile
|
||||
@@ -420,12 +366,38 @@ def proxydhcp(handler, nodeguess):
|
||||
# tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
|
||||
# event=log.Events.stacktrace)
|
||||
|
||||
ignorenics = None
|
||||
|
||||
def start_proxydhcp(handler, nodeguess=None):
|
||||
eventlet.spawn_n(proxydhcp, handler, nodeguess)
|
||||
tasks.spawn(proxydhcp(handler, nodeguess))
|
||||
|
||||
ignorenics = None
|
||||
def snoop(handler, protocol=None, nodeguess=None):
|
||||
|
||||
def new_dhcp_packet(handler, nodeguess, cfg, net4):
|
||||
data, cmsgs, flags, client = net4.recvmsg(9000, 9000)
|
||||
if len(data) < 64:
|
||||
return
|
||||
for cmsg in cmsgs:
|
||||
level, typ, cdata = cmsg
|
||||
if level == socket.IPPROTO_IP and typ == IP_PKTINFO:
|
||||
idx, recv = struct.unpack('II', cdata[:8])
|
||||
recv = ipfromint(recv)
|
||||
rqv = memoryview(data)
|
||||
if rqv[0] == 1:
|
||||
tasks.spawn(process_dhcp4req(handler, nodeguess, cfg, net4, idx, recv, rqv, client))
|
||||
|
||||
|
||||
def new_dhcp6_packet(handler, net6, cfg, nodeguess):
|
||||
recv = 'ff02::1:2'
|
||||
pkt, addr = net6.recvfrom(2048)
|
||||
idx = addr[-1]
|
||||
if len(pkt) < 64:
|
||||
return
|
||||
rqv = memoryview(pkt)
|
||||
if rqv[0] in (1, 3):
|
||||
tasks.spawn(process_dhcp6req(handler, rqv, addr, net6, cfg, nodeguess))
|
||||
|
||||
|
||||
async def snoop(handler, protocol=None, nodeguess=None):
|
||||
global ignorenics
|
||||
#TODO(jjohnson2): ipv6 socket and multicast for DHCPv6, should that be
|
||||
#prominent
|
||||
@@ -437,6 +409,7 @@ def snoop(handler, protocol=None, nodeguess=None):
|
||||
ignorenics = ignorenics.encode()
|
||||
ignorenics = ignorenics.split(b',')
|
||||
start_proxydhcp(handler, nodeguess)
|
||||
global tracelog
|
||||
tracelog = log.Logger('trace')
|
||||
global attribwatcher
|
||||
cfg = cfm.ConfigManager(None)
|
||||
@@ -459,78 +432,18 @@ def snoop(handler, protocol=None, nodeguess=None):
|
||||
v6grp = v6addr + struct.pack('=I', ifidx)
|
||||
net6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_JOIN_GROUP, v6grp)
|
||||
net6.bind(('', 547))
|
||||
clientaddr = sockaddr_in()
|
||||
rawbuffer = bytearray(2048)
|
||||
data = pkttype.from_buffer(rawbuffer)
|
||||
msg = msghdr()
|
||||
cmsgarr = bytearray(cmsgsize)
|
||||
cmsg = cmsgtype.from_buffer(cmsgarr)
|
||||
iov = iovec()
|
||||
iov.iov_base = ctypes.addressof(data)
|
||||
iov.iov_len = 2048
|
||||
msg.msg_iov = ctypes.pointer(iov)
|
||||
msg.msg_iovlen = 1
|
||||
msg.msg_control = ctypes.addressof(cmsg)
|
||||
msg.msg_controllen = ctypes.sizeof(cmsg)
|
||||
msg.msg_name = ctypes.addressof(clientaddr)
|
||||
msg.msg_namelen = ctypes.sizeof(clientaddr)
|
||||
# We'll leave name and namelen blank for now
|
||||
while True:
|
||||
try:
|
||||
# Just need some delay, picked a prime number so that overlap with other
|
||||
# timers might be reduced, though it really is probably nothing
|
||||
ready = select.select([net4, net6], [], [], 1)
|
||||
for txid in list(_recent_txids):
|
||||
if _recent_txids[txid] < time.time():
|
||||
del _recent_txids[txid]
|
||||
if not ready or not ready[0]:
|
||||
continue
|
||||
for netc in ready[0]:
|
||||
idx = None
|
||||
if netc == net4:
|
||||
i = recvmsg(netc.fileno(), ctypes.pointer(msg), 0)
|
||||
# if we have a small packet, just skip, it can't possible hold enough
|
||||
# data and avoids some downstream IndexErrors that would be messy
|
||||
# with try/except
|
||||
if i < 64:
|
||||
continue
|
||||
if rawbuffer[0] == 1: # Boot request
|
||||
_, level, typ = struct.unpack('QII', cmsgarr[:16])
|
||||
if level == socket.IPPROTO_IP and typ == IP_PKTINFO:
|
||||
idx, recv = struct.unpack('II', cmsgarr[16:24])
|
||||
if ignorenics:
|
||||
ignore = False
|
||||
for nic in ignorenics:
|
||||
if libc.if_nametoindex(nic) == idx:
|
||||
ignore = True
|
||||
break # ignore DHCP from ignored NIC
|
||||
if ignore:
|
||||
continue
|
||||
recv = ipfromint(recv)
|
||||
rqv = memoryview(rawbuffer)[:i]
|
||||
client = (ipfromint(clientaddr.sin_addr.s_addr), socket.htons(clientaddr.sin_port))
|
||||
process_dhcp4req(handler, nodeguess, cfg, net4, idx, recv, rqv, client)
|
||||
elif netc == net6:
|
||||
recv = 'ff02::1:2'
|
||||
pkt, addr = netc.recvfrom(2048)
|
||||
idx = addr[-1]
|
||||
i = len(pkt)
|
||||
if i < 64:
|
||||
continue
|
||||
rqv = memoryview(pkt)
|
||||
rq = bytearray(rqv[:2])
|
||||
if rq[0] in (1, 3): # dhcpv6 solicit
|
||||
process_dhcp6req(handler, rqv, addr, netc, cfg, nodeguess)
|
||||
|
||||
except Exception as e:
|
||||
tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
|
||||
event=log.Events.stacktrace)
|
||||
net6.settimeout(0)
|
||||
net4.settimeout(0)
|
||||
cloop = asyncio.get_running_loop()
|
||||
# TODO:asyncmerge: honor ignorenics, clean the _recent_txids that have expired
|
||||
cloop.add_reader(net4, new_dhcp_packet, handler, nodeguess, cfg, net4)
|
||||
cloop.add_reader(net6, new_dhcp6_packet, handler, net6, cfg, nodeguess)
|
||||
|
||||
|
||||
_mac_to_uuidmap = {}
|
||||
|
||||
|
||||
def process_dhcp6req(handler, rqv, addr, net, cfg, nodeguess):
|
||||
async def process_dhcp6req(handler, rqv, addr, net, cfg, nodeguess):
|
||||
ip = addr[0]
|
||||
req, disco = v6opts_to_dict(bytearray(rqv[4:]))
|
||||
req['txid'] = rqv[1:4]
|
||||
@@ -540,22 +453,22 @@ def process_dhcp6req(handler, rqv, addr, net, cfg, nodeguess):
|
||||
if disco['uuid'] == '03000200-0400-0500-0006-000700080009':
|
||||
# Ignore common malformed dhcpv6 request from firmware
|
||||
return
|
||||
mac = neighutil.get_hwaddr(ip.split('%', 1)[0])
|
||||
mac = await neighutil.get_hwaddr(ip.split('%', 1)[0])
|
||||
if not mac:
|
||||
net.sendto(b'\x00', addr)
|
||||
tries = 5
|
||||
while tries and not mac:
|
||||
eventlet.sleep(0.01)
|
||||
await asyncio.sleep(0.01)
|
||||
tries -= 1
|
||||
mac = neighutil.get_hwaddr(ip.split('%', 1)[0])
|
||||
mac = await neighutil.get_hwaddr(ip.split('%', 1)[0])
|
||||
info = {'hwaddr': mac, 'uuid': disco['uuid'],
|
||||
'architecture': disco['arch'], 'services': ('pxe-client',)}
|
||||
if ignoredisco.get(mac, 0) + 90 < time.time():
|
||||
ignoredisco[mac] = time.time()
|
||||
handler(info)
|
||||
consider_discover(info, req, net, cfg, None, nodeguess, addr)
|
||||
await consider_discover(info, req, net, cfg, None, nodeguess, addr)
|
||||
|
||||
def process_dhcp4req(handler, nodeguess, cfg, net4, idx, recv, rqv, client):
|
||||
async def process_dhcp4req(handler, nodeguess, cfg, net4, idx, recv, rqv, client):
|
||||
rq = bytearray(rqv)
|
||||
addrlen = rq[2]
|
||||
if addrlen > 16 or addrlen == 0:
|
||||
@@ -598,7 +511,7 @@ def process_dhcp4req(handler, nodeguess, cfg, net4, idx, recv, rqv, client):
|
||||
and time.time() > ignoredisco.get(netaddr, 0) + 90):
|
||||
ignoredisco[netaddr] = time.time()
|
||||
handler(info)
|
||||
consider_discover(info, rqinfo, net4, cfg, rqv, nodeguess, requestor=client)
|
||||
await consider_discover(info, rqinfo, net4, cfg, rqv, nodeguess, requestor=client)
|
||||
|
||||
|
||||
|
||||
@@ -656,7 +569,7 @@ def get_deployment_profile(node, cfg, cfd=None):
|
||||
|
||||
staticassigns = {}
|
||||
myipbypeer = {}
|
||||
def check_reply(node, info, packet, sock, cfg, reqview, addr, requestor):
|
||||
async def check_reply(node, info, packet, sock, cfg, reqview, addr, requestor):
|
||||
if not requestor:
|
||||
requestor = ('0.0.0.0', None)
|
||||
if requestor[0] == '0.0.0.0' and not info.get('uuid', None):
|
||||
@@ -677,16 +590,16 @@ def check_reply(node, info, packet, sock, cfg, reqview, addr, requestor):
|
||||
if packet['vci'] and packet['vci'].startswith('PXEClient'):
|
||||
log.log({'info': 'IPv6 PXE boot attempt by {0}, but IPv6 PXE is not supported, try IPv6 HTTP boot or IPv4 boot'.format(node)})
|
||||
return
|
||||
return reply_dhcp6(node, addr, cfg, packet, cfd, profile, sock)
|
||||
return await reply_dhcp6(node, addr, cfg, packet, cfd, profile, sock)
|
||||
else:
|
||||
return reply_dhcp4(node, info, packet, cfg, reqview, httpboot, cfd, profile, sock, requestor)
|
||||
return await reply_dhcp4(node, info, packet, cfg, reqview, httpboot, cfd, profile, sock, requestor)
|
||||
|
||||
def reply_dhcp6(node, addr, cfg, packet, cfd, profile, sock):
|
||||
myaddrs = netutil.get_my_addresses(addr[-1], socket.AF_INET6)
|
||||
async def reply_dhcp6(node, addr, cfg, packet, cfd, profile, sock):
|
||||
myaddrs = await netutil.get_my_addresses(addr[-1], socket.AF_INET6)
|
||||
if not myaddrs:
|
||||
log.log({'info': 'Unable to provide IPv6 boot services to {0}, no viable IPv6 configuration on interface index "{1}" to respond through.'.format(node, addr[-1])})
|
||||
return
|
||||
niccfg = netutil.get_nic_config(cfg, node, ifidx=addr[-1], onlyfamily=socket.AF_INET6)
|
||||
niccfg = await netutil.get_nic_config(cfg, node, ifidx=addr[-1], onlyfamily=socket.AF_INET6)
|
||||
ipv6addr = niccfg.get('ipv6_address', None)
|
||||
ipv6prefix = niccfg.get('ipv6_prefix', None)
|
||||
ipv6method = niccfg.get('ipv6_method', 'static')
|
||||
@@ -766,7 +679,7 @@ def get_my_duid():
|
||||
|
||||
_recent_txids = {}
|
||||
|
||||
def reply_dhcp4(node, info, packet, cfg, reqview, httpboot, cfd, profile, sock=None, requestor=None):
|
||||
async def reply_dhcp4(node, info, packet, cfg, reqview, httpboot, cfd, profile, sock=None, requestor=None):
|
||||
replen = 275 # default is going to be 286
|
||||
# while myipn is describing presumed destination, it's really
|
||||
# vague in the face of aliases, need to convert to ifidx and evaluate
|
||||
@@ -811,7 +724,7 @@ def reply_dhcp4(node, info, packet, cfg, reqview, httpboot, cfd, profile, sock=N
|
||||
relayipa = socket.inet_ntoa(relayip)
|
||||
gateway = None
|
||||
netmask = None
|
||||
niccfg = netutil.get_nic_config(cfg, node, ifidx=info['netinfo']['ifidx'], relayipn=relayip, onlyfamily=socket.AF_INET)
|
||||
niccfg = await netutil.get_nic_config(cfg, node, ifidx=info['netinfo']['ifidx'], relayipn=relayip, onlyfamily=socket.AF_INET)
|
||||
nicerr = niccfg.get('error_msg', False)
|
||||
if nicerr:
|
||||
log.log({'error': nicerr})
|
||||
@@ -958,12 +871,12 @@ def reply_dhcp4(node, info, packet, cfg, reqview, httpboot, cfd, profile, sock=N
|
||||
ipinfo = 'without address, served from {0}'.format(myip)
|
||||
if relayipa:
|
||||
ipinfo += ' (relayed to {} via {})'.format(relayipa, requestor[0])
|
||||
eventlet.spawn(send_rsp, repview, replen, requestor, relayip, reqview, info, deferanswer, isboot, node, boottype, ipinfo, sock)
|
||||
tasks.spawn(send_rsp(repview, replen, requestor, relayip, reqview, info, deferanswer, isboot, node, boottype, ipinfo, sock))
|
||||
|
||||
|
||||
def send_rsp(repview, replen, requestor, relayip, reqview, info, defertxid, isboot, node, boottype, ipinfo, sock):
|
||||
async def send_rsp(repview, replen, requestor, relayip, reqview, info, defertxid, isboot, node, boottype, ipinfo, sock):
|
||||
if defertxid:
|
||||
eventlet.sleep(0.5)
|
||||
await asyncio.sleep(0.5)
|
||||
if defertxid in _recent_txids:
|
||||
log.log({'info': 'Skipping reply for {} over interface {} due to better offer being made over other interface'.format(node, info['netinfo']['ifidx'])})
|
||||
return
|
||||
@@ -1029,13 +942,13 @@ def ack_request(pkt, rq, info, sock=None, requestor=None):
|
||||
else:
|
||||
send_raw_packet(repview, len(rply), rq, info)
|
||||
|
||||
def consider_discover(info, packet, sock, cfg, reqview, nodeguess, addr=None, requestor=None):
|
||||
async def consider_discover(info, packet, sock, cfg, reqview, nodeguess, addr=None, requestor=None):
|
||||
if packet.get(53, None) == b'\x03':
|
||||
ack_request(packet, reqview, info, sock, requestor)
|
||||
elif info.get('hwaddr', None) in macmap: # and info.get('uuid', None):
|
||||
check_reply(macmap[info['hwaddr']], info, packet, sock, cfg, reqview, addr, requestor)
|
||||
await check_reply(macmap[info['hwaddr']], info, packet, sock, cfg, reqview, addr, requestor)
|
||||
elif info.get('uuid', None) in uuidmap:
|
||||
check_reply(uuidmap[info['uuid']], info, packet, sock, cfg, reqview, addr, requestor)
|
||||
await check_reply(uuidmap[info['uuid']], info, packet, sock, cfg, reqview, addr, requestor)
|
||||
elif packet.get(53, None) == b'\x03':
|
||||
ack_request(packet, reqview, info, sock, requestor)
|
||||
elif info.get('uuid', None) and info.get('hwaddr', None):
|
||||
|
||||
@@ -14,14 +14,14 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import asyncio
|
||||
import confluent.neighutil as neighutil
|
||||
import confluent.tasks as tasks
|
||||
import confluent.util as util
|
||||
import confluent.log as log
|
||||
import os
|
||||
import random
|
||||
import eventlet.greenpool
|
||||
import eventlet.green.select as select
|
||||
import eventlet.green.socket as socket
|
||||
import socket
|
||||
import struct
|
||||
import traceback
|
||||
|
||||
@@ -102,12 +102,12 @@ def _parse_SrvRply(parsed):
|
||||
parsed['urls'].append(url)
|
||||
|
||||
|
||||
def _parse_slp_packet(packet, peer, rsps, xidmap, defer=None, sock=None):
|
||||
async def _parse_slp_packet(packet, peer, rsps, xidmap, defer=None, sock=None):
|
||||
parsed = _parse_slp_header(packet)
|
||||
if not parsed:
|
||||
return
|
||||
addr = peer[0]
|
||||
mac = neighutil.get_hwaddr(addr)
|
||||
mac = await neighutil.get_hwaddr(addr)
|
||||
if mac:
|
||||
identifier = mac
|
||||
else:
|
||||
@@ -116,6 +116,7 @@ def _parse_slp_packet(packet, peer, rsps, xidmap, defer=None, sock=None):
|
||||
else:
|
||||
probepeer = (peer[0], struct.unpack('H', os.urandom(2))[0] | 1025) + peer[2:]
|
||||
try:
|
||||
sock.setblocking(1)
|
||||
sock.sendto(b'\x00', probepeer)
|
||||
except Exception:
|
||||
return
|
||||
@@ -198,7 +199,7 @@ def _generate_request_payload(srvtype, multicast, xid, prlist=''):
|
||||
return header + payload
|
||||
|
||||
|
||||
def _find_srvtype(net, net4, srvtype, addresses, xid):
|
||||
async def _find_srvtype(net, net4, srvtype, addresses, xid):
|
||||
"""Internal function to find a single service type
|
||||
|
||||
Helper to do singleton requests to srvtype
|
||||
@@ -208,10 +209,11 @@ def _find_srvtype(net, net4, srvtype, addresses, xid):
|
||||
:param addresses: Pass through of addresses argument from find_targets
|
||||
:return:
|
||||
"""
|
||||
cloop = asyncio.get_running_loop()
|
||||
data = _generate_request_payload(srvtype, True, xid)
|
||||
if addresses is not None:
|
||||
for addr in addresses:
|
||||
for saddr in socket.getaddrinfo(addr, 427):
|
||||
for saddr in await cloop.getaddrinfo(addr, 427):
|
||||
if saddr[0] == socket.AF_INET:
|
||||
net4.sendto(data, saddr[4])
|
||||
elif saddr[0] == socket.AF_INET6:
|
||||
@@ -253,21 +255,16 @@ def _find_srvtype(net, net4, srvtype, addresses, xid):
|
||||
pass
|
||||
|
||||
|
||||
def _grab_rsps(socks, rsps, interval, xidmap, deferrals):
|
||||
r = None
|
||||
res = select.select(socks, (), (), interval)
|
||||
if res:
|
||||
r = res[0]
|
||||
while r:
|
||||
for s in r:
|
||||
(rsp, peer) = s.recvfrom(9000)
|
||||
_parse_slp_packet(rsp, peer, rsps, xidmap, deferrals, s)
|
||||
res = select.select(socks, (), (), interval)
|
||||
if not res:
|
||||
r = None
|
||||
else:
|
||||
r = res[0]
|
||||
import time
|
||||
|
||||
def sock_read(fut, sock, cloop, allsocks):
|
||||
if fut.done():
|
||||
print("was already done???")
|
||||
return
|
||||
if not cloop.remove_reader(sock):
|
||||
print("Was already removed??")
|
||||
fut.set_result(sock)
|
||||
allsocks.discard(sock)
|
||||
|
||||
|
||||
def _parse_attrlist(attrstr):
|
||||
@@ -335,16 +332,17 @@ def _parse_attrs(data, parsed, xid=None):
|
||||
parsed['attributes'] = _parse_attrlist(attrstr)
|
||||
|
||||
|
||||
def fix_info(info, handler):
|
||||
async def fix_info(info, handler):
|
||||
if '_attempts' not in info:
|
||||
info['_attempts'] = 10
|
||||
if info['_attempts'] == 0:
|
||||
return
|
||||
info['_attempts'] -= 1
|
||||
_add_attributes(info)
|
||||
await _add_attributes(info)
|
||||
handler(info)
|
||||
|
||||
def _add_attributes(parsed):
|
||||
|
||||
async def _add_attributes(parsed):
|
||||
xid = parsed.get('xid', 42)
|
||||
attrq = _generate_attr_request(parsed['services'][0], xid)
|
||||
target = None
|
||||
@@ -360,14 +358,16 @@ def _add_attributes(parsed):
|
||||
net = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
|
||||
else:
|
||||
net = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
cloop = asyncio.get_running_loop()
|
||||
try:
|
||||
net.settimeout(2.0)
|
||||
net.connect(target)
|
||||
except socket.error:
|
||||
net.settimeout(0)
|
||||
net.setblocking(0)
|
||||
await asyncio.wait_for(cloop.sock_connect(net, target), 2.0)
|
||||
except (socket.error, asyncio.exceptions.TimeoutError) as te:
|
||||
return
|
||||
try:
|
||||
net.sendall(attrq)
|
||||
rsp = net.recv(8192)
|
||||
await cloop.sock_sendall(net, attrq)
|
||||
rsp = await cloop.sock_recv(net, 8192)
|
||||
net.close()
|
||||
_parse_attrs(rsp, parsed, xid)
|
||||
except Exception as e:
|
||||
@@ -379,11 +379,12 @@ def _add_attributes(parsed):
|
||||
def unicast_scan(address):
|
||||
pass
|
||||
|
||||
def query_srvtypes(target):
|
||||
async def query_srvtypes(target):
|
||||
"""Query the srvtypes advertised by the target
|
||||
|
||||
:param target: A sockaddr tuple (if you get the peer info)
|
||||
"""
|
||||
cloop = asyncio.get_running_loop()
|
||||
payload = b'\x00\x00\xff\xff\x00\x07DEFAULT'
|
||||
header = _generate_slp_header(payload, False, functionid=9, xid=1)
|
||||
packet = header + payload
|
||||
@@ -398,15 +399,13 @@ def query_srvtypes(target):
|
||||
while tries and not connected:
|
||||
tries -= 1
|
||||
try:
|
||||
net.settimeout(1.0)
|
||||
net.connect(target)
|
||||
net.settimeout(0)
|
||||
await asyncio.wait_for(cloop.sock_connect(net, target), 2.0)
|
||||
connected = True
|
||||
except socket.error:
|
||||
pass
|
||||
if not connected:
|
||||
return [u'']
|
||||
net.sendall(packet)
|
||||
rs = net.recv(8192)
|
||||
except (socket.error, asyncio.exceptions.TimeoutError) as te:
|
||||
return [u'']
|
||||
await cloop.sock_sendall(net, packet)
|
||||
rs = await cloop.sock_recv(net, 8192)
|
||||
net.close()
|
||||
parsed = _parse_slp_header(rs)
|
||||
if parsed:
|
||||
@@ -417,13 +416,13 @@ def query_srvtypes(target):
|
||||
stypes = payload[4:4+stypelen].decode('utf-8')
|
||||
return stypes.split(',')
|
||||
|
||||
def rescan(handler):
|
||||
async def rescan(handler):
|
||||
known_peers = set([])
|
||||
for scanned in scan():
|
||||
async for scanned in scan():
|
||||
for addr in scanned['addresses']:
|
||||
if addr in known_peers:
|
||||
break
|
||||
macaddr = neighutil.get_hwaddr(addr[0])
|
||||
macaddr = await neighutil.get_hwaddr(addr[0])
|
||||
if not macaddr:
|
||||
continue
|
||||
known_peers.add(addr)
|
||||
@@ -431,7 +430,15 @@ def rescan(handler):
|
||||
handler(scanned)
|
||||
|
||||
|
||||
def snoop(handler, protocol=None):
|
||||
def relay_packet(sock, pktq):
|
||||
sock.setblocking(0)
|
||||
try:
|
||||
rsp, peer = sock.recvfrom(9000)
|
||||
except socket.error as se:
|
||||
return
|
||||
pktq.put_nowait((sock, rsp, peer))
|
||||
|
||||
async def snoop(handler, protocol=None):
|
||||
"""Watch for SLP activity
|
||||
|
||||
handler will be called with a dictionary of relevant attributes
|
||||
@@ -441,10 +448,10 @@ def snoop(handler, protocol=None):
|
||||
"""
|
||||
tracelog = log.Logger('trace')
|
||||
try:
|
||||
active_scan(handler, protocol)
|
||||
await active_scan(handler, protocol)
|
||||
except Exception as e:
|
||||
tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
|
||||
event=log.Events.stacktrace)
|
||||
tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
|
||||
event=log.Events.stacktrace)
|
||||
net = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
net.setsockopt(IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
|
||||
slpg = socket.inet_pton(socket.AF_INET6, 'ff01::123')
|
||||
@@ -471,6 +478,10 @@ def snoop(handler, protocol=None):
|
||||
# socket in use can occur when aliased ipv4 are encountered
|
||||
net.bind(('', 427))
|
||||
net4.bind(('', 427))
|
||||
pktq = asyncio.Queue()
|
||||
cloop = asyncio.get_running_loop()
|
||||
cloop.add_reader(net, relay_packet, net, pktq)
|
||||
cloop.add_reader(net4, relay_packet, net4, pktq)
|
||||
newmacs = set([])
|
||||
known_peers = set([])
|
||||
peerbymacaddress = {}
|
||||
@@ -478,7 +489,6 @@ def snoop(handler, protocol=None):
|
||||
while True:
|
||||
try:
|
||||
newmacs.clear()
|
||||
r, _, _ = select.select((net, net4), (), (), 60)
|
||||
# clear known_peers and peerbymacaddress
|
||||
# to avoid stale info getting in...
|
||||
# rely upon the select(0.2) to catch rapid fire and aggregate ip
|
||||
@@ -488,31 +498,37 @@ def snoop(handler, protocol=None):
|
||||
known_peers.clear()
|
||||
peerbymacaddress.clear()
|
||||
deferpeers.clear()
|
||||
while r and len(deferpeers) < 256:
|
||||
for s in r:
|
||||
(rsp, peer) = s.recvfrom(9000)
|
||||
if peer in known_peers:
|
||||
timeo = 60
|
||||
rdy = True
|
||||
srp = await pktq.get()
|
||||
while srp and len(deferpeers) < 256:
|
||||
s, rsp, peer = srp
|
||||
try:
|
||||
srp = await asyncio.wait_for(pktq.get(), 0.2)
|
||||
except asyncio.exceptions.TimeoutError:
|
||||
srp = None
|
||||
if peer in known_peers:
|
||||
continue
|
||||
if peer in deferpeers:
|
||||
continue
|
||||
mac = await neighutil.get_hwaddr(peer[0])
|
||||
if not mac:
|
||||
probepeer = (peer[0], struct.unpack('H', os.urandom(2))[0] | 1025) + peer[2:]
|
||||
try:
|
||||
s.setblocking(1)
|
||||
s.sendto(b'\x00', probepeer)
|
||||
except Exception as e:
|
||||
continue
|
||||
if peer in deferpeers:
|
||||
continue
|
||||
mac = neighutil.get_hwaddr(peer[0])
|
||||
if not mac:
|
||||
probepeer = (peer[0], struct.unpack('H', os.urandom(2))[0] | 1025) + peer[2:]
|
||||
try:
|
||||
s.sendto(b'\x00', probepeer)
|
||||
except Exception:
|
||||
continue
|
||||
deferpeers.append(peer)
|
||||
continue
|
||||
process_peer(newmacs, known_peers, peerbymacaddress, peer)
|
||||
r, _, _ = select.select((net, net4), (), (), 0.2)
|
||||
deferpeers.append(peer)
|
||||
continue
|
||||
await process_peer(newmacs, known_peers, peerbymacaddress, peer)
|
||||
if deferpeers:
|
||||
eventlet.sleep(2.2)
|
||||
await asyncio.sleep(2.2)
|
||||
for peer in deferpeers:
|
||||
process_peer(newmacs, known_peers, peerbymacaddress, peer)
|
||||
await process_peer(newmacs, known_peers, peerbymacaddress, peer)
|
||||
for mac in newmacs:
|
||||
peerbymacaddress[mac]['xid'] = 1
|
||||
_add_attributes(peerbymacaddress[mac])
|
||||
await _add_attributes(peerbymacaddress[mac])
|
||||
peerbymacaddress[mac]['hwaddr'] = mac
|
||||
peerbymacaddress[mac]['protocol'] = protocol
|
||||
for srvurl in peerbymacaddress[mac].get('urls', ()):
|
||||
@@ -534,8 +550,8 @@ def snoop(handler, protocol=None):
|
||||
tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
|
||||
event=log.Events.stacktrace)
|
||||
|
||||
def process_peer(newmacs, known_peers, peerbymacaddress, peer):
|
||||
mac = neighutil.get_hwaddr(peer[0])
|
||||
async def process_peer(newmacs, known_peers, peerbymacaddress, peer):
|
||||
mac = await neighutil.get_hwaddr(peer[0])
|
||||
if not mac:
|
||||
return
|
||||
known_peers.add(peer)
|
||||
@@ -543,7 +559,7 @@ def process_peer(newmacs, known_peers, peerbymacaddress, peer):
|
||||
peerbymacaddress[mac]['addresses'].append(peer)
|
||||
else:
|
||||
try:
|
||||
q = query_srvtypes(peer)
|
||||
q = await query_srvtypes(peer)
|
||||
except Exception as e:
|
||||
q = None
|
||||
if not q or not q[0]:
|
||||
@@ -565,17 +581,16 @@ def process_peer(newmacs, known_peers, peerbymacaddress, peer):
|
||||
newmacs.add(mac)
|
||||
|
||||
|
||||
def active_scan(handler, protocol=None):
|
||||
async def active_scan(handler, protocol=None):
|
||||
known_peers = set([])
|
||||
toprocess = []
|
||||
# Implement a warmup, inducing neighbor table activity
|
||||
# by kernel and giving 2 seconds for a retry or two if
|
||||
# needed
|
||||
for scanned in scan():
|
||||
async for scanned in scan():
|
||||
for addr in scanned['addresses']:
|
||||
if addr in known_peers:
|
||||
break
|
||||
macaddr = neighutil.get_hwaddr(addr[0])
|
||||
macaddr = await neighutil.get_hwaddr(addr[0])
|
||||
if not macaddr:
|
||||
continue
|
||||
if not scanned.get('hwaddr', None):
|
||||
@@ -586,7 +601,7 @@ def active_scan(handler, protocol=None):
|
||||
handler(scanned)
|
||||
|
||||
|
||||
def scan(srvtypes=_slp_services, addresses=None, localonly=False):
|
||||
async def scan(srvtypes=_slp_services, addresses=None, localonly=False):
|
||||
"""Find targets providing matching requested srvtypes
|
||||
|
||||
This is a generator that will iterate over respondants to the SrvType
|
||||
@@ -611,6 +626,10 @@ def scan(srvtypes=_slp_services, addresses=None, localonly=False):
|
||||
# too, so force it
|
||||
#net.setsockopt(IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
|
||||
# we are going to do broadcast, so allow that...
|
||||
cloop = asyncio.get_running_loop()
|
||||
pktq = asyncio.Queue()
|
||||
cloop.add_reader(net, relay_packet, net, pktq)
|
||||
cloop.add_reader(net4, relay_packet, net4, pktq)
|
||||
initxid = random.randint(0, 32768)
|
||||
xididx = 0
|
||||
xidmap = {}
|
||||
@@ -618,23 +637,29 @@ def scan(srvtypes=_slp_services, addresses=None, localonly=False):
|
||||
# processed, mitigating volume of response traffic
|
||||
rsps = {}
|
||||
deferrals = []
|
||||
rcvq = asyncio.Queue()
|
||||
for srvtype in srvtypes:
|
||||
xididx += 1
|
||||
_find_srvtype(net, net4, srvtype, addresses, initxid + xididx)
|
||||
await _find_srvtype(net, net4, srvtype, addresses, initxid + xididx)
|
||||
xidmap[initxid + xididx] = srvtype
|
||||
_grab_rsps((net, net4), rsps, 0.1, xidmap, deferrals)
|
||||
# now do a more slow check to work to get stragglers,
|
||||
# but fortunately the above should have taken the brunt of volume, so
|
||||
# reduced chance of many responses overwhelming receive buffer.
|
||||
_grab_rsps((net, net4), rsps, 1, xidmap, deferrals)
|
||||
await asyncio.sleep(0) # give async a chance to move things off buffer to queue
|
||||
while True:
|
||||
try:
|
||||
srp = await asyncio.wait_for(pktq.get(), 1.0)
|
||||
sock, rsp, peer = srp
|
||||
await _parse_slp_packet(rsp, peer, rsps, xidmap, deferrals, sock)
|
||||
except asyncio.exceptions.TimeoutError:
|
||||
break
|
||||
cloop.remove_reader(net)
|
||||
cloop.remove_reader(net4)
|
||||
if deferrals:
|
||||
eventlet.sleep(1.2) # already have a one second pause from select above
|
||||
await asyncio.sleep(1.2) # already have a one second pause from select above
|
||||
for defer in deferrals:
|
||||
rsp, peer = defer
|
||||
_parse_slp_packet(rsp, peer, rsps, xidmap)
|
||||
await _parse_slp_packet(rsp, peer, rsps, xidmap)
|
||||
# now to analyze and flesh out the responses
|
||||
handleids = set([])
|
||||
gp = eventlet.greenpool.GreenPool(128)
|
||||
tsks = []
|
||||
for id in rsps:
|
||||
for srvurl in rsps[id].get('urls', ()):
|
||||
if len(srvurl) > 4:
|
||||
@@ -649,9 +674,10 @@ def scan(srvtypes=_slp_services, addresses=None, localonly=False):
|
||||
break
|
||||
else:
|
||||
continue
|
||||
gp.spawn_n(_add_attributes, rsps[id])
|
||||
tsks.append(tasks.spawn_task(_add_attributes(rsps[id])))
|
||||
handleids.add(id)
|
||||
gp.waitall()
|
||||
if tsks:
|
||||
await asyncio.wait(tsks)
|
||||
for id in handleids:
|
||||
if 'service:lighttpd' in rsps[id]['services']:
|
||||
currinf = rsps[id]
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
# NTS: ssdp:alive
|
||||
|
||||
|
||||
import asyncio
|
||||
import confluent.config.configmanager as cfm
|
||||
import confluent.collective.manager as collective
|
||||
import confluent.neighutil as neighutil
|
||||
@@ -35,16 +36,14 @@ import confluent.noderange as noderange
|
||||
import confluent.util as util
|
||||
import confluent.log as log
|
||||
import confluent.netutil as netutil
|
||||
import eventlet
|
||||
import eventlet.green.select as select
|
||||
import eventlet.green.socket as socket
|
||||
import eventlet.greenpool as gp
|
||||
import confluent.tasks as tasks
|
||||
import socket
|
||||
import os
|
||||
import time
|
||||
import struct
|
||||
import traceback
|
||||
|
||||
webclient = eventlet.import_patched('pyghmi.util.webclient')
|
||||
import aiohmi.util.webclient as webclient
|
||||
mcastv4addr = '239.255.255.250'
|
||||
mcastv6addr = 'ff02::c'
|
||||
|
||||
@@ -56,14 +55,14 @@ smsg = ('M-SEARCH * HTTP/1.1\r\n'
|
||||
'MX: 3\r\n\r\n')
|
||||
|
||||
|
||||
def active_scan(handler, protocol=None):
|
||||
async def active_scan(handler, protocol=None):
|
||||
known_peers = set([])
|
||||
for scanned in scan(['urn:dmtf-org:service:redfish-rest:1', 'urn::dmtf-org:service:redfish-rest:', 'urn::service:affluent']):
|
||||
async for scanned in scan(['urn:dmtf-org:service:redfish-rest:1', 'urn::dmtf-org:service:redfish-rest:', 'urn::service:affluent']):
|
||||
for addr in scanned['addresses']:
|
||||
addr = addr[0:1] + addr[2:]
|
||||
if addr in known_peers:
|
||||
break
|
||||
hwaddr = neighutil.get_hwaddr(addr[0])
|
||||
hwaddr = await neighutil.get_hwaddr(addr[0])
|
||||
if not hwaddr:
|
||||
continue
|
||||
if not scanned.get('hwaddr', None):
|
||||
@@ -73,9 +72,9 @@ def active_scan(handler, protocol=None):
|
||||
scanned['protocol'] = protocol
|
||||
handler(scanned)
|
||||
|
||||
def scan(services, target=None):
|
||||
async def scan(services, target=None):
|
||||
for service in services:
|
||||
for rply in _find_service(service, target):
|
||||
async for rply in _find_service(service, target):
|
||||
yield rply
|
||||
|
||||
|
||||
@@ -116,18 +115,17 @@ def _process_snoop(peer, rsp, mac, known_peers, newmacs, peerbymacaddress, byeha
|
||||
if '/eth' in value and value.endswith('.xml'):
|
||||
targurl = '/redfish/v1/'
|
||||
targtype = 'megarac-bmc'
|
||||
continue # MegaRAC redfish
|
||||
continue # MegaRAC redfish
|
||||
elif value.endswith('/DeviceDescription.json'):
|
||||
targurl = '/DeviceDescription.json'
|
||||
targtype = 'lenovo-xcc'
|
||||
continue
|
||||
else:
|
||||
return
|
||||
if handler and targurl:
|
||||
eventlet.spawn_n(check_fish_handler, handler, peerdata, known_peers, newmacs, peerbymacaddress, machandlers, mac, peer, targurl, targtype)
|
||||
if handler:
|
||||
tasks.spawn(check_fish_handler(handler, peerdata, known_peers, newmacs, peerbymacaddress, machandlers, mac, peer, targurl, targtype))
|
||||
|
||||
def check_fish_handler(handler, peerdata, known_peers, newmacs, peerbymacaddress, machandlers, mac, peer, targurl, targtype):
|
||||
retdata = check_fish((targurl, peerdata, targtype))
|
||||
async def check_fish_handler(handler, peerdata, known_peers, newmacs, peerbymacaddress, machandlers, mac, peer, targurl, targtype):
|
||||
retdata = await check_fish(('/DeviceDescription.json', peerdata, targtype))
|
||||
if retdata:
|
||||
known_peers.add(peer)
|
||||
newmacs.add(mac)
|
||||
@@ -135,7 +133,7 @@ def check_fish_handler(handler, peerdata, known_peers, newmacs, peerbymacaddress
|
||||
machandlers[mac] = handler
|
||||
|
||||
|
||||
def snoop(handler, byehandler=None, protocol=None, uuidlookup=None):
|
||||
async def snoop(handler, byehandler=None, protocol=None, uuidlookup=None):
|
||||
"""Watch for SSDP notify messages
|
||||
|
||||
The handler shall be called on any service coming online.
|
||||
@@ -152,8 +150,9 @@ def snoop(handler, byehandler=None, protocol=None, uuidlookup=None):
|
||||
# dabbling in multicast wizardry here, such sockets can cause big problems,
|
||||
# so we will have two distinct sockets
|
||||
tracelog = log.Logger('trace')
|
||||
cloop = asyncio.get_running_loop()
|
||||
try:
|
||||
active_scan(handler, protocol)
|
||||
await active_scan(handler, protocol)
|
||||
except Exception as e:
|
||||
tracelog.log(traceback.format_exc(), ltype=log.DataTypes.event,
|
||||
event=log.Events.stacktrace)
|
||||
@@ -179,6 +178,10 @@ def snoop(handler, byehandler=None, protocol=None, uuidlookup=None):
|
||||
net4.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
net4.bind(('', 1900))
|
||||
net6.bind(('', 1900))
|
||||
pktq = asyncio.Queue()
|
||||
cloop = asyncio.get_running_loop()
|
||||
cloop.add_reader(net4, _relay_pkt, net4, pktq)
|
||||
cloop.add_reader(net6, _relay_pkt, net6, pktq)
|
||||
peerbymacaddress = {}
|
||||
newmacs = set([])
|
||||
deferrednotifies = []
|
||||
@@ -188,131 +191,135 @@ def snoop(handler, byehandler=None, protocol=None, uuidlookup=None):
|
||||
newmacs.clear()
|
||||
deferrednotifies.clear()
|
||||
machandlers.clear()
|
||||
r = select.select((net4, net6), (), (), 60)
|
||||
if r:
|
||||
r = r[0]
|
||||
recent_peers = set([])
|
||||
while r and len(deferrednotifies) < 256:
|
||||
for s in r:
|
||||
(rsp, peer) = s.recvfrom(9000)
|
||||
if rsp[:4] == b'PING':
|
||||
timeout = None
|
||||
srp = await pktq.get()
|
||||
recent_peers.clear()
|
||||
while srp and len(deferrednotifies) < 256:
|
||||
srp = None
|
||||
if timeout is None:
|
||||
srp = await pktq.get()
|
||||
else:
|
||||
try:
|
||||
srp = await asyncio.wait_for(pktq.get(), timeout=timeout)
|
||||
except asyncio.exceptions.TimeoutError:
|
||||
break
|
||||
timeout = 0.2
|
||||
s, rsp, peer = srp
|
||||
if rsp[:4] == b'PING':
|
||||
continue
|
||||
if peer in recent_peers:
|
||||
continue
|
||||
rsp = rsp.split(b'\r\n')
|
||||
if b' ' not in rsp[0]:
|
||||
continue
|
||||
method, _ = rsp[0].split(b' ', 1)
|
||||
if method == b'NOTIFY':
|
||||
if peer in known_peers:
|
||||
continue
|
||||
if peer in recent_peers:
|
||||
recent_peers.add(peer)
|
||||
mac = await neighutil.get_hwaddr(peer[0])
|
||||
if mac == False:
|
||||
# neighutil determined peer ip is not local, skip attempt
|
||||
# to probe and critically, skip growing deferrednotifiers
|
||||
continue
|
||||
rsp = rsp.split(b'\r\n')
|
||||
if b' ' not in rsp[0]:
|
||||
continue
|
||||
method, _ = rsp[0].split(b' ', 1)
|
||||
if method == b'NOTIFY':
|
||||
if peer in known_peers:
|
||||
if not mac:
|
||||
probepeer = (peer[0], struct.unpack('H', os.urandom(2))[0] | 1025) + peer[2:]
|
||||
try:
|
||||
s.setblocking(1)
|
||||
s.sendto(b'\x00', probepeer)
|
||||
except Exception:
|
||||
continue
|
||||
recent_peers.add(peer)
|
||||
mac = neighutil.get_hwaddr(peer[0])
|
||||
if mac == False:
|
||||
# neighutil determined peer ip is not local, skip attempt
|
||||
# to probe and critically, skip growing deferrednotifiers
|
||||
deferrednotifies.append((peer, rsp))
|
||||
continue
|
||||
_process_snoop(peer, rsp, mac, known_peers, newmacs, peerbymacaddress, byehandler, machandlers, handler)
|
||||
elif method == b'M-SEARCH':
|
||||
if not uuidlookup:
|
||||
continue
|
||||
#ip = peer[0].partition('%')[0]
|
||||
for headline in rsp[1:]:
|
||||
if not headline:
|
||||
continue
|
||||
if not mac:
|
||||
probepeer = (peer[0], struct.unpack('H', os.urandom(2))[0] | 1025) + peer[2:]
|
||||
headline = util.stringify(headline)
|
||||
headline = headline.partition(':')
|
||||
if len(headline) < 3:
|
||||
continue
|
||||
forcereply = False
|
||||
if headline[0] == 'ST' and headline[-1].startswith(' urn:xcat.org:service:confluent:'):
|
||||
try:
|
||||
s.sendto(b'\x00', probepeer)
|
||||
cfm.check_quorum()
|
||||
except Exception:
|
||||
continue
|
||||
deferrednotifies.append((peer, rsp))
|
||||
continue
|
||||
_process_snoop(peer, rsp, mac, known_peers, newmacs, peerbymacaddress, byehandler, machandlers, handler)
|
||||
elif method == b'M-SEARCH':
|
||||
if not uuidlookup:
|
||||
continue
|
||||
#ip = peer[0].partition('%')[0]
|
||||
for headline in rsp[1:]:
|
||||
if not headline:
|
||||
continue
|
||||
headline = util.stringify(headline)
|
||||
headline = headline.partition(':')
|
||||
if len(headline) < 3:
|
||||
continue
|
||||
forcereply = False
|
||||
if headline[0] == 'ST' and headline[-1].startswith(' urn:xcat.org:service:confluent:'):
|
||||
try:
|
||||
cfm.check_quorum()
|
||||
except Exception:
|
||||
continue
|
||||
for query in headline[-1].split('/'):
|
||||
node = None
|
||||
if query.startswith('confluentuuid='):
|
||||
myuuid = cfm.get_global('confluent_uuid')
|
||||
curruuid = query.split('=', 1)[1].lower()
|
||||
if curruuid != myuuid:
|
||||
break
|
||||
forcereply = True
|
||||
elif query.startswith('allconfluent=1'):
|
||||
reply = 'HTTP/1.1 200 OK\r\n\r\nCONFLUENT: PRESENT\r\n'
|
||||
if not isinstance(reply, bytes):
|
||||
reply = reply.encode('utf8')
|
||||
try:
|
||||
s.sendto(reply, peer)
|
||||
except Exception:
|
||||
break
|
||||
elif query.startswith('uuid='):
|
||||
curruuid = query.split('=', 1)[1].lower()
|
||||
node = uuidlookup(curruuid)
|
||||
elif query.startswith('mac='):
|
||||
currmac = query.split('=', 1)[1].lower()
|
||||
node = uuidlookup(currmac)
|
||||
if node:
|
||||
cfg = cfm.ConfigManager(None)
|
||||
cfd = cfg.get_node_attributes(
|
||||
node, ['deployment.pendingprofile', 'collective.managercandidates'])
|
||||
if not forcereply:
|
||||
# Do not bother replying to a node that
|
||||
# we have no deployment activity
|
||||
# planned for
|
||||
if not cfd.get(node, {}).get(
|
||||
'deployment.pendingprofile', {}).get('value', None):
|
||||
break
|
||||
candmgrs = cfd.get(node, {}).get('collective.managercandidates', {}).get('value', None)
|
||||
if candmgrs:
|
||||
try:
|
||||
candmgrs = noderange.NodeRange(candmgrs, cfg).nodes
|
||||
except Exception:
|
||||
candmgrs = noderange.NodeRange(candmgrs).nodes
|
||||
if collective.get_myname() not in candmgrs:
|
||||
break
|
||||
currtime = time.time()
|
||||
seconds = int(currtime)
|
||||
msecs = int(currtime * 1000 % 1000)
|
||||
reply = 'HTTP/1.1 200 OK\r\nNODENAME: {0}\r\nCURRTIME: {1}\r\nCURRMSECS: {2}\r\n'.format(node, seconds, msecs)
|
||||
theip = peer[0].split('%', 1)[0]
|
||||
if netutil.ip_on_same_subnet(theip, 'fe80::', 64):
|
||||
if '%' in peer[0]:
|
||||
ifidx = peer[0].split('%', 1)[1]
|
||||
iface = socket.getaddrinfo(peer[0], 0, socket.AF_INET6, socket.SOCK_DGRAM)[0][-1][-1]
|
||||
else:
|
||||
ifidx = '{}'.format(peer[-1])
|
||||
iface = peer[-1]
|
||||
reply += 'MGTIFACE: {0}\r\n'.format(ifidx)
|
||||
ncfg = netutil.get_nic_config(
|
||||
cfg, node, ifidx=iface)
|
||||
if ncfg.get('matchesnodename', None):
|
||||
reply += 'DEFAULTNET: 1\r\n'
|
||||
elif not netutil.address_is_local(peer[0]):
|
||||
continue
|
||||
if not isinstance(reply, bytes):
|
||||
reply = reply.encode('utf8')
|
||||
try:
|
||||
s.sendto(reply, peer)
|
||||
except Exception:
|
||||
pass
|
||||
for query in headline[-1].split('/'):
|
||||
node = None
|
||||
if query.startswith('confluentuuid='):
|
||||
myuuid = cfm.get_global('confluent_uuid')
|
||||
curruuid = query.split('=', 1)[1].lower()
|
||||
if curruuid != myuuid:
|
||||
break
|
||||
r = select.select((net4, net6), (), (), 0.2)
|
||||
if r:
|
||||
r = r[0]
|
||||
forcereply = True
|
||||
elif query.startswith('allconfluent=1'):
|
||||
reply = 'HTTP/1.1 200 OK\r\n\r\nCONFLUENT: PRESENT\r\n'
|
||||
if not isinstance(reply, bytes):
|
||||
reply = reply.encode('utf8')
|
||||
s.setblocking(1)
|
||||
try:
|
||||
s.sendto(reply, peer)
|
||||
except Exception:
|
||||
break
|
||||
elif query.startswith('uuid='):
|
||||
curruuid = query.split('=', 1)[1].lower()
|
||||
node = uuidlookup(curruuid)
|
||||
elif query.startswith('mac='):
|
||||
currmac = query.split('=', 1)[1].lower()
|
||||
node = uuidlookup(currmac)
|
||||
if node:
|
||||
cfg = cfm.ConfigManager(None)
|
||||
cfd = cfg.get_node_attributes(
|
||||
node, ['deployment.pendingprofile', 'collective.managercandidates'])
|
||||
if not forcereply:
|
||||
# Do not bother replying to a node that
|
||||
# we have no deployment activity
|
||||
# planned for
|
||||
if not cfd.get(node, {}).get(
|
||||
'deployment.pendingprofile', {}).get('value', None):
|
||||
break
|
||||
candmgrs = cfd.get(node, {}).get('collective.managercandidates', {}).get('value', None)
|
||||
if candmgrs:
|
||||
candmgrs = noderange.NodeRange(candmgrs, cfg).nodes
|
||||
if collective.get_myname() not in candmgrs:
|
||||
break
|
||||
currtime = time.time()
|
||||
seconds = int(currtime)
|
||||
msecs = int(currtime * 1000 % 1000)
|
||||
reply = 'HTTP/1.1 200 OK\r\nNODENAME: {0}\r\nCURRTIME: {1}\r\nCURRMSECS: {2}\r\n'.format(node, seconds, msecs)
|
||||
theip = peer[0].split('%', 1)[0]
|
||||
if await netutil.ip_on_same_subnet(theip, 'fe80::', 64):
|
||||
if '%' in peer[0]:
|
||||
ifidx = peer[0].split('%', 1)[1]
|
||||
iface = await cloop.getaddrinfo(peer[0], 0, socket.AF_INET6, socket.SOCK_DGRAM)[0][-1][-1]
|
||||
else:
|
||||
ifidx = '{}'.format(peer[-1])
|
||||
iface = peer[-1]
|
||||
reply += 'MGTIFACE: {0}\r\n'.format(ifidx)
|
||||
ncfg = await netutil.get_nic_config(
|
||||
cfg, node, ifidx=iface)
|
||||
if ncfg.get('matchesnodename', None):
|
||||
reply += 'DEFAULTNET: 1\r\n'
|
||||
elif not await netutil.address_is_local(peer[0]):
|
||||
continue
|
||||
if not isinstance(reply, bytes):
|
||||
reply = reply.encode('utf8')
|
||||
s.setblocking(1)
|
||||
try:
|
||||
s.sendto(reply, peer)
|
||||
except Exception:
|
||||
pass
|
||||
break
|
||||
if deferrednotifies:
|
||||
eventlet.sleep(2.2)
|
||||
await asyncio.sleep(2.2)
|
||||
for peerrsp in deferrednotifies:
|
||||
peer, rsp = peerrsp
|
||||
mac = neighutil.get_hwaddr(peer[0])
|
||||
mac = await neighutil.get_hwaddr(peer[0])
|
||||
if not mac:
|
||||
continue
|
||||
_process_snoop(peer, rsp, mac, known_peers, newmacs, peerbymacaddress, byehandler, machandlers, handler)
|
||||
@@ -333,12 +340,24 @@ def _get_svrip(peerdata):
|
||||
return addr[0]
|
||||
return peerdata['addresses'][0][0]
|
||||
|
||||
def _find_service(service, target):
|
||||
def _relay_pkt(sock, pktq):
|
||||
sock.setblocking(0)
|
||||
try:
|
||||
rsp, peer = sock.recvfrom(9000)
|
||||
except socket.error as se:
|
||||
return
|
||||
pktq.put_nowait((sock, rsp, peer))
|
||||
|
||||
async def _find_service(service, target):
|
||||
cloop = asyncio.get_running_loop()
|
||||
net4 = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
net6 = socket.socket(socket.AF_INET6, socket.SOCK_DGRAM)
|
||||
net6.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 1)
|
||||
pktq = asyncio.Queue()
|
||||
cloop.add_reader(net4, _relay_pkt, net4, pktq)
|
||||
cloop.add_reader(net6, _relay_pkt, net6, pktq)
|
||||
if target:
|
||||
addrs = socket.getaddrinfo(target, 1900, 0, socket.SOCK_DGRAM)
|
||||
addrs = await cloop.getaddrinfo(target, 1900, 0, socket.SOCK_DGRAM)
|
||||
for addr in addrs:
|
||||
host = addr[4][0]
|
||||
if addr[0] == socket.AF_INET:
|
||||
@@ -390,31 +409,35 @@ def _find_service(service, target):
|
||||
# SSDP by spec encourages responses to spread out over a 3 second interval
|
||||
# hence we must be a bit more patient
|
||||
deadline = util.monotonic_time() + 4
|
||||
r, _, _ = select.select((net4, net6), (), (), 4)
|
||||
peerdata = {}
|
||||
deferparse = []
|
||||
while r:
|
||||
for s in r:
|
||||
(rsp, peer) = s.recvfrom(9000)
|
||||
if not neighutil.get_hwaddr(peer[0]):
|
||||
probepeer = (peer[0], struct.unpack('H', os.urandom(2))[0] | 1025) + peer[2:]
|
||||
try:
|
||||
s.sendto(b'\x00', probepeer)
|
||||
except Exception:
|
||||
continue
|
||||
deferparse.append((rsp, peer))
|
||||
srp = True
|
||||
timeout = 4
|
||||
while timeout and srp and len(deferparse) < 256:
|
||||
try:
|
||||
srp = await asyncio.wait_for(pktq.get(), timeout)
|
||||
except asyncio.exceptions.TimeoutError:
|
||||
break
|
||||
s, rsp, peer = srp
|
||||
if not await neighutil.get_hwaddr(peer[0]):
|
||||
probepeer = (peer[0], struct.unpack('H', os.urandom(2))[0] | 1025) + peer[2:]
|
||||
try:
|
||||
s.sendto(b'\x00', probepeer)
|
||||
except Exception:
|
||||
srp = True
|
||||
continue
|
||||
_parse_ssdp(peer, rsp, peerdata)
|
||||
deferparse.append((rsp, peer))
|
||||
srp = True
|
||||
continue
|
||||
await _parse_ssdp(peer, rsp, peerdata)
|
||||
timeout = deadline - util.monotonic_time()
|
||||
if timeout < 0:
|
||||
timeout = 0
|
||||
r, _, _ = select.select((net4, net6), (), (), timeout)
|
||||
if deferparse:
|
||||
eventlet.sleep(2.2)
|
||||
await asyncio.sleep(2.2)
|
||||
for dp in deferparse:
|
||||
rsp, peer = dp
|
||||
_parse_ssdp(peer, rsp, peerdata)
|
||||
querypool = gp.GreenPool()
|
||||
await _parse_ssdp(peer, rsp, peerdata)
|
||||
pooltargs = []
|
||||
for nid in peerdata:
|
||||
if peerdata[nid].get('services', [None])[0] == 'urn::service:affluent:1':
|
||||
@@ -441,7 +464,7 @@ def _find_service(service, target):
|
||||
continue
|
||||
else:
|
||||
for targurl in peerdata[nid]['urls']:
|
||||
if '/eth' in targurl and targurl.endswith('.xml'):
|
||||
if targurl and targurl.endswith('.xml'):
|
||||
pooltargs.append(('/redfish/v1/', peerdata[nid], 'megarac-bmc'))
|
||||
# For now, don't interrogate generic redfish bmcs
|
||||
# This is due to a need to deduplicate from some supported SLP
|
||||
@@ -450,11 +473,18 @@ def _find_service(service, target):
|
||||
# or we drop support for those devices
|
||||
#else:
|
||||
# pooltargs.append(('/redfish/v1/', peerdata[nid]))
|
||||
for pi in querypool.imap(check_fish, pooltargs):
|
||||
if pi is not None:
|
||||
yield pi
|
||||
tsks = []
|
||||
for targ in pooltargs:
|
||||
tsks.append(tasks.spawn_task(check_fish(targ)))
|
||||
while tsks:
|
||||
done, tsks = await asyncio.wait(tsks, return_when=asyncio.FIRST_COMPLETED)
|
||||
for dt in done:
|
||||
dt = await dt
|
||||
if dt is None:
|
||||
continue
|
||||
yield dt
|
||||
|
||||
def check_fish(urldata, port=443, verifycallback=None):
|
||||
async def check_fish(urldata, port=443, verifycallback=None):
|
||||
if not verifycallback:
|
||||
verifycallback = lambda x: True
|
||||
try:
|
||||
@@ -463,8 +493,8 @@ def check_fish(urldata, port=443, verifycallback=None):
|
||||
url, data = urldata
|
||||
targtype = 'service:redfish-bmc'
|
||||
try:
|
||||
wc = webclient.SecureHTTPConnection(_get_svrip(data), port, verifycallback=verifycallback, timeout=1.5)
|
||||
peerinfo = wc.grab_json_response(url, headers={'Accept': 'application/json'})
|
||||
wc = webclient.WebConnection(_get_svrip(data), port, verifycallback=verifycallback)
|
||||
peerinfo = await wc.grab_json_response(url, headers={'Accept': 'application/json'})
|
||||
except socket.error:
|
||||
return None
|
||||
if url == '/DeviceDescription.json':
|
||||
@@ -491,7 +521,7 @@ def check_fish(urldata, port=443, verifycallback=None):
|
||||
data['services'] = ['lenovo-xcc'] if 'xcc-variant' not in peerinfo else ['lenovo-xcc' + peerinfo['xcc-variant']]
|
||||
return data
|
||||
except (IndexError, KeyError):
|
||||
if 'type' in peerinfo and peerinfo['type'].lower() == 'lenovo-smm3':
|
||||
if 'type' in peerinfo and peerinfo['type'].lower() in ('lenovo-smm3', 'smm3'):
|
||||
del peerinfo['xcc-variant']
|
||||
data['uuid'] = peerinfo['enclosure-uuid']
|
||||
data['services'] = ['lenovo-smm3']
|
||||
@@ -499,7 +529,7 @@ def check_fish(urldata, port=443, verifycallback=None):
|
||||
return data
|
||||
return None
|
||||
url = '/redfish/v1/'
|
||||
peerinfo = wc.grab_json_response('/redfish/v1/')
|
||||
peerinfo = await wc.grab_json_response('/redfish/v1/')
|
||||
if url == '/redfish/v1/':
|
||||
if 'UUID' in peerinfo:
|
||||
data['services'] = [targtype]
|
||||
@@ -507,10 +537,10 @@ def check_fish(urldata, port=443, verifycallback=None):
|
||||
return data
|
||||
return None
|
||||
|
||||
def _parse_ssdp(peer, rsp, peerdata):
|
||||
async def _parse_ssdp(peer, rsp, peerdata):
|
||||
nid = peer[0]
|
||||
mac = None
|
||||
mac = neighutil.get_hwaddr(peer[0])
|
||||
mac = await neighutil.get_hwaddr(peer[0])
|
||||
if mac:
|
||||
nid = mac
|
||||
headlines = rsp.split(b'\r\n')
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
import confluent.exceptions as exc
|
||||
import confluent.log as log
|
||||
import confluent.messages as msg
|
||||
import eventlet
|
||||
import confluent.tasks as tasks
|
||||
import io
|
||||
import os
|
||||
import pwd
|
||||
@@ -31,12 +31,12 @@ import traceback
|
||||
updatesbytarget = {}
|
||||
uploadsbytarget = {}
|
||||
downloadsbytarget = {}
|
||||
updatepool = eventlet.greenpool.GreenPool(256)
|
||||
_tracelog = None
|
||||
sharedfiles = {}
|
||||
updatepool = tasks.TaskPool(max_concurrent=256)
|
||||
|
||||
|
||||
def execupdate(handler, filename, updateobj, type, owner, node, datfile):
|
||||
async def execupdate(handler, filename, updateobj, type, owner, node, datfile):
|
||||
global _tracelog
|
||||
try:
|
||||
if type != 'ffdc' and not datfile:
|
||||
@@ -66,10 +66,10 @@ def execupdate(handler, filename, updateobj, type, owner, node, datfile):
|
||||
return
|
||||
try:
|
||||
if type == 'firmware':
|
||||
completion = handler(filename, progress=updateobj.handle_progress,
|
||||
completion = await handler(filename, progress=updateobj.handle_progress,
|
||||
data=datfile, bank=updateobj.bank)
|
||||
else:
|
||||
completion = handler(filename, progress=updateobj.handle_progress,
|
||||
completion = await handler(filename, progress=updateobj.handle_progress,
|
||||
data=datfile)
|
||||
if type == 'ffdc' and completion:
|
||||
filename = completion
|
||||
@@ -122,7 +122,7 @@ class Updater(object):
|
||||
else:
|
||||
datfile = None
|
||||
self.datfile = datfile
|
||||
self.updateproc = updatepool.spawn(execupdate, handler, filename,
|
||||
self.updateproc = updatepool.schedule(execupdate, handler, filename,
|
||||
self, type, owner, node, datfile)
|
||||
if type == 'firmware':
|
||||
myparty = updatesbytarget
|
||||
@@ -145,7 +145,7 @@ class Updater(object):
|
||||
self.detail = progress.get('detail', '')
|
||||
|
||||
def cancel(self):
|
||||
self.updateproc.kill()
|
||||
self.updateproc.cancel()
|
||||
if self.datfile:
|
||||
self.datfile.close()
|
||||
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
#This handles port forwarding for web interfaces on management devices
|
||||
#It will also hijack port 3900 and do best effort..
|
||||
|
||||
import eventlet
|
||||
import eventlet.green.select as select
|
||||
import eventlet.green.socket as socket
|
||||
import asyncio
|
||||
import socket
|
||||
import confluent.tasks as tasks
|
||||
forwardersbyclient = {}
|
||||
relaysbysession = {}
|
||||
sessionsbyip = {}
|
||||
@@ -28,24 +28,37 @@ sockhandler = {}
|
||||
vidtargetbypeer = {}
|
||||
vidforwarder = None
|
||||
|
||||
def handle_connection(incoming, outgoing):
|
||||
while True:
|
||||
r, _, _ = select.select((incoming, outgoing), (), (), 60)
|
||||
for mysock in r:
|
||||
data = mysock.recv(32768)
|
||||
if not data:
|
||||
incoming.close()
|
||||
outgoing.close()
|
||||
return
|
||||
if mysock == incoming:
|
||||
outgoing.sendall(data)
|
||||
elif mysock == outgoing:
|
||||
incoming.sendall(data)
|
||||
async def handle_connection(incoming, outgoing):
|
||||
async def _relay(reader, writer):
|
||||
try:
|
||||
while True:
|
||||
data = await reader.read(32768)
|
||||
if not data:
|
||||
return
|
||||
writer.write(data)
|
||||
await writer.drain()
|
||||
except (ConnectionError, OSError):
|
||||
return
|
||||
|
||||
inrdr, inwriter = await asyncio.open_connection(sock=incoming)
|
||||
outrdr, outwriter = await asyncio.open_connection(sock=outgoing)
|
||||
try:
|
||||
done, pending = await asyncio.wait(
|
||||
[asyncio.ensure_future(_relay(inrdr, outwriter)),
|
||||
asyncio.ensure_future(_relay(outrdr, inwriter))],
|
||||
return_when=asyncio.FIRST_COMPLETED)
|
||||
for task in pending:
|
||||
task.cancel()
|
||||
finally:
|
||||
inwriter.close()
|
||||
outwriter.close()
|
||||
|
||||
|
||||
def forward_port(sock, target, clientip, sessionid):
|
||||
async def forward_port(sock, target, clientip, sessionid):
|
||||
loop = asyncio.get_event_loop()
|
||||
sock.setblocking(False)
|
||||
while True:
|
||||
conn, cli = sock.accept()
|
||||
conn, cli = await loop.sock_accept(sock)
|
||||
if cli[0] != clientip:
|
||||
conn.close()
|
||||
continue
|
||||
@@ -57,14 +70,19 @@ def forward_port(sock, target, clientip, sessionid):
|
||||
continue
|
||||
if sessionid not in relaysbysession:
|
||||
relaysbysession[sessionid] = {}
|
||||
relaysbysession[sessionid][eventlet.spawn(
|
||||
handle_connection, conn, client)] = conn
|
||||
relaysbysession[sessionid][tasks.spawn(
|
||||
handle_connection(conn, client))] = conn
|
||||
|
||||
|
||||
def forward_video():
|
||||
sock = eventlet.listen(('::', 3900, 0, 0), family=socket.AF_INET6)
|
||||
async def forward_video():
|
||||
sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
|
||||
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
sock.bind(('::', 3900, 0, 0))
|
||||
sock.listen(50)
|
||||
loop = asyncio.get_event_loop()
|
||||
sock.setblocking(False)
|
||||
while True:
|
||||
conn, cli = sock.accept()
|
||||
conn, cli = await loop.sock_accept(sock)
|
||||
if cli[0] not in vidtargetbypeer or not sessionsbyip.get(cli[0], None):
|
||||
conn.close()
|
||||
continue
|
||||
@@ -76,7 +94,7 @@ def forward_video():
|
||||
conn.close()
|
||||
vidclient.close()
|
||||
continue
|
||||
eventlet.spawn_n(handle_connection, conn, vidclient)
|
||||
tasks.spawn(handle_connection(conn, vidclient))
|
||||
|
||||
|
||||
def close_session(sessionid):
|
||||
@@ -124,10 +142,10 @@ def get_port(addr, clientip, sessionid):
|
||||
newport += 1
|
||||
continue
|
||||
forwardersbyclient[sessionid][addr] = newsock
|
||||
sockhandler[newsock] = eventlet.spawn(forward_port, newsock, addr,
|
||||
clientip, sessionid)
|
||||
sockhandler[newsock] = tasks.spawn(forward_port(newsock, addr,
|
||||
clientip, sessionid))
|
||||
if not vidforwarder:
|
||||
vidforwarder = eventlet.spawn(forward_video)
|
||||
vidforwarder = tasks.spawn(forward_video())
|
||||
vidtargetbypeer[clientip] = addr
|
||||
return forwardersbyclient[sessionid][addr].getsockname()[1]
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -63,11 +63,13 @@
|
||||
# (a future extended version might include suport for Forward Secure Sealing
|
||||
# or other fields)
|
||||
|
||||
import asyncio
|
||||
import collections
|
||||
import confluent.config.configmanager
|
||||
import confluent.config.conf as conf
|
||||
import confluent.exceptions as exc
|
||||
import eventlet
|
||||
import confluent.tasks as tasks
|
||||
import inspect
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
@@ -76,6 +78,7 @@ import stat
|
||||
import struct
|
||||
import time
|
||||
import traceback
|
||||
import random
|
||||
try:
|
||||
unicode
|
||||
except NameError:
|
||||
@@ -113,6 +116,7 @@ except ImportError:
|
||||
MIDNIGHT = 24 * 60 * 60
|
||||
_loggers = {}
|
||||
|
||||
|
||||
class Events(object):
|
||||
(
|
||||
undefined, clearscreen, clientconnect, clientdisconnect,
|
||||
@@ -632,7 +636,7 @@ class Logger(object):
|
||||
self.logentries.appendleft([DataTypes.event, tstamp, roll_data,
|
||||
Events.logrollover, None])
|
||||
if self.closer is None:
|
||||
self.closer = eventlet.spawn_after(15, self.closelog)
|
||||
self.closer = tasks.spawn_task_after(15, self.closelog)
|
||||
self.writer = None
|
||||
|
||||
def read_recent_text(self, size):
|
||||
@@ -781,7 +785,7 @@ class Logger(object):
|
||||
[ltype, timestamp, logdata, event, eventdata])
|
||||
if self.buffered:
|
||||
if self.writer is None:
|
||||
self.writer = eventlet.spawn_after(2, self.writedata)
|
||||
self.writer = tasks.spawn_task_after(2, self.writedata)
|
||||
else:
|
||||
self.writedata()
|
||||
|
||||
@@ -807,3 +811,5 @@ def logtrace():
|
||||
tracelog = Logger('trace', buffered=False)
|
||||
tracelog.log(traceback.format_exc(), ltype=DataTypes.event,
|
||||
event=Events.stacktrace)
|
||||
|
||||
tasks.logtrace = logtrace
|
||||
|
||||
@@ -25,13 +25,13 @@
|
||||
# service should have a null tenant and a tenant entry that correlates)
|
||||
__author__ = 'jjohnson2'
|
||||
|
||||
import asyncio
|
||||
import confluent.config.configmanager as configmanager
|
||||
import itertools
|
||||
from eventlet.support import greendns
|
||||
|
||||
manager_to_nodemap = {}
|
||||
|
||||
def node_by_manager(manager):
|
||||
async def node_by_manager(manager):
|
||||
"""Lookup a node by manager
|
||||
|
||||
Search for a node according to a given network address.
|
||||
@@ -46,7 +46,7 @@ def node_by_manager(manager):
|
||||
"""
|
||||
|
||||
manageraddresses = []
|
||||
for tmpaddr in greendns.getaddrinfo(manager, None):
|
||||
for tmpaddr in await asyncio.get_event_loop().getaddrinfo(manager, None):
|
||||
manageraddresses.append(tmpaddr[4][0])
|
||||
cfm = configmanager.ConfigManager(None)
|
||||
if manager in manager_to_nodemap:
|
||||
@@ -68,7 +68,7 @@ def node_by_manager(manager):
|
||||
if currhm in manageraddresses:
|
||||
manager_to_nodemap[manager] = node
|
||||
return node
|
||||
for curraddr in greendns.getaddrinfo(currhm, None):
|
||||
for curraddr in await asyncio.get_event_loop().getaddrinfo(currhm, None):
|
||||
curraddr = curraddr[4][0]
|
||||
if curraddr in manageraddresses:
|
||||
manager_to_nodemap[manager] = node
|
||||
|
||||
@@ -25,10 +25,14 @@
|
||||
# Things like heartbeating and discovery
|
||||
# It also will optionally snoop SLP DA requests
|
||||
|
||||
|
||||
#import logging
|
||||
#logging.basicConfig(filename='/tmp/asyn.log', level=logging.DEBUG)
|
||||
import atexit
|
||||
import confluent.auth as auth
|
||||
import confluent.config.conf as conf
|
||||
import confluent.config.configmanager as configmanager
|
||||
import confluent.debugger as debugger
|
||||
try:
|
||||
import anydbm as dbm
|
||||
except ModuleNotFoundError:
|
||||
@@ -39,6 +43,7 @@ import confluent.httpapi as httpapi
|
||||
import confluent.log as log
|
||||
import confluent.collective.manager as collective
|
||||
import confluent.discovery.protocols.pxe as pxe
|
||||
import linecache
|
||||
try:
|
||||
import confluent.sockapi as sockapi
|
||||
except ImportError:
|
||||
@@ -46,21 +51,14 @@ except ImportError:
|
||||
#only for now
|
||||
pass
|
||||
import confluent.discovery.core as disco
|
||||
import eventlet
|
||||
dbgif = False
|
||||
try:
|
||||
import eventlet.backdoor as backdoor
|
||||
dbgif = True
|
||||
except Exception:
|
||||
pass
|
||||
havefcntl = True
|
||||
try:
|
||||
import fcntl
|
||||
except ImportError:
|
||||
havefcntl = False
|
||||
#import multiprocessing
|
||||
import asyncio
|
||||
import gc
|
||||
from greenlet import greenlet
|
||||
import sys
|
||||
import os
|
||||
import glob
|
||||
@@ -73,6 +71,36 @@ import tempfile
|
||||
import uuid
|
||||
|
||||
|
||||
def format_stack(task):
|
||||
task.print_stack()
|
||||
extracted_list = []
|
||||
checked = set()
|
||||
for f in task.get_stack():
|
||||
lineno = f.f_lineno
|
||||
co = f.f_code
|
||||
filename = co.co_filename
|
||||
name = co.co_name
|
||||
if filename not in checked:
|
||||
checked.add(filename)
|
||||
linecache.checkcache(filename)
|
||||
line = linecache.getline(filename, lineno, f.f_globals)
|
||||
extracted_list.append((filename, lineno, name, line))
|
||||
|
||||
exc = task._exception
|
||||
if not extracted_list:
|
||||
yield f'No stack for {task!r}'
|
||||
elif exc is not None:
|
||||
yield f'Traceback for {task!r} (most recent call last):'
|
||||
else:
|
||||
yield f'Stack for {task!r} (most recent call last):'
|
||||
|
||||
for x in traceback.format_list(extracted_list):
|
||||
yield x
|
||||
if exc is not None:
|
||||
for line in traceback.format_exception_only(exc.__class__, exc):
|
||||
yield line
|
||||
|
||||
|
||||
def _daemonize():
|
||||
if not 'fork' in os.__dict__:
|
||||
return
|
||||
@@ -175,13 +203,9 @@ def dumptrace(signalname, frame):
|
||||
ht = open('/var/log/confluent/hangtraces', 'a')
|
||||
ht.write('Dumping active trace on ' + time.strftime('%X %x\n'))
|
||||
ht.write(''.join(traceback.format_stack(frame)))
|
||||
for o in gc.get_objects():
|
||||
if not isinstance(o, greenlet):
|
||||
continue
|
||||
if not o:
|
||||
continue
|
||||
ht.write('Thread trace: ({0})\n'.format(id(o)))
|
||||
ht.write(''.join(traceback.format_stack(o.gr_frame)))
|
||||
for atask in asyncio.all_tasks():
|
||||
ht.write('Async trace: ({0})\n'.format(id(atask)))
|
||||
ht.write(''.join([x for x in format_stack(atask)]))
|
||||
ht.close()
|
||||
|
||||
def doexit():
|
||||
@@ -253,6 +277,9 @@ def migrate_db():
|
||||
|
||||
|
||||
def run(args):
|
||||
asyncio.run(asyncrun(args))
|
||||
|
||||
async def asyncrun(args):
|
||||
setlimits()
|
||||
try:
|
||||
configmanager.ConfigManager(None)
|
||||
@@ -284,11 +311,16 @@ def run(args):
|
||||
print(repr(e))
|
||||
sys.exit(1)
|
||||
if '-f' not in args:
|
||||
_daemonize()
|
||||
sys.stderr.write("-f is now required")
|
||||
# the fork wreaks havoc with asyncio thread executor
|
||||
# If someone comes along with a non-systemd demand, will just have to have a daemonize wrapper
|
||||
sys.exit(1)
|
||||
#_daemonize()
|
||||
if '-o' not in args:
|
||||
_redirectoutput()
|
||||
if havefcntl:
|
||||
_updatepidfile()
|
||||
asyncio.get_event_loop().set_debug(True)
|
||||
signal.signal(signal.SIGINT, terminate)
|
||||
signal.signal(signal.SIGTERM, terminate)
|
||||
atexit.register(doexit)
|
||||
@@ -298,27 +330,15 @@ def run(args):
|
||||
configmanager.set_global('confluent_uuid', confluentuuid)
|
||||
if not configmanager._masterkey:
|
||||
configmanager.init_masterkey()
|
||||
if dbgif:
|
||||
oumask = os.umask(0o077)
|
||||
try:
|
||||
os.remove('/var/run/confluent/dbg.sock')
|
||||
except OSError:
|
||||
pass # We are not expecting the file to exist
|
||||
try:
|
||||
dbgsock = eventlet.listen("/var/run/confluent/dbg.sock",
|
||||
family=socket.AF_UNIX)
|
||||
eventlet.spawn_n(backdoor.backdoor_server, dbgsock)
|
||||
except AttributeError:
|
||||
pass # Windows...
|
||||
os.umask(oumask)
|
||||
debugger.start_dbgif()
|
||||
auth.check_for_yaml()
|
||||
collective.startup()
|
||||
consoleserver.initialize()
|
||||
await consoleserver.initialize()
|
||||
http_bind_host, http_bind_port = _get_connector_config('http')
|
||||
sock_bind_host, sock_bind_port = _get_connector_config('socket')
|
||||
try:
|
||||
sockservice = sockapi.SockApi(sock_bind_host, sock_bind_port)
|
||||
sockservice.start()
|
||||
asyncio.get_event_loop().create_task(sockservice.start())
|
||||
except NameError:
|
||||
pass
|
||||
webservice = httpapi.HttpApi(http_bind_host, http_bind_port)
|
||||
@@ -330,12 +350,25 @@ def run(args):
|
||||
configmanager.check_quorum()
|
||||
break
|
||||
except Exception:
|
||||
eventlet.sleep(0.5)
|
||||
eventlet.spawn_n(disco.start_detection)
|
||||
eventlet.sleep(1)
|
||||
consoleserver.start_console_sessions()
|
||||
await asyncio.sleep(0.5)
|
||||
disco.start_detection()
|
||||
await asyncio.sleep(1)
|
||||
await consoleserver.start_console_sessions()
|
||||
notifysock = os.environ.get('NOTIFY_SOCKET', None)
|
||||
if notifysock:
|
||||
if notifysock.startswith('@'):
|
||||
notifysock = '\0' + notifysock[1:]
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
|
||||
sock.connect(notifysock)
|
||||
sock.send(b'READY=1')
|
||||
watchdogsecs = int(os.environ.get('WATCHDOG_USEC', 0)) / 1000000
|
||||
if not watchdogsecs:
|
||||
watchdogsecs = 200
|
||||
watchdogsecs = watchdogsecs / 2
|
||||
while 1:
|
||||
eventlet.sleep(100)
|
||||
await asyncio.sleep(watchdogsecs)
|
||||
if notifysock:
|
||||
sock.send(b'WATCHDOG=1')
|
||||
|
||||
def _get_connector_config(session):
|
||||
host = conf.get_option(session, 'bindhost')
|
||||
|
||||
@@ -519,6 +519,8 @@ def get_input_message(path, operation, inputdata, nodes=None, multinode=False,
|
||||
return InputAlertDestination(path, nodes, inputdata, multinode)
|
||||
elif len(path) == 3 and path[:3] == ['configuration', 'management_controller', 'certificate_authorities'] and operation not in ('retrieve', 'delete'):
|
||||
return InputCertificateAuthority(path, nodes, inputdata)
|
||||
elif len(path) == 4 and path[:4] == ['configuration', 'management_controller', 'certificate', 'sign'] and operation not in ('retrieve', 'delete'):
|
||||
return InputSigningParameters(path, inputdata, nodes, configmanager)
|
||||
elif path == ['identify'] and operation != 'retrieve':
|
||||
return InputIdentifyMessage(path, nodes, inputdata)
|
||||
elif path == ['events', 'hardware', 'decode']:
|
||||
@@ -578,6 +580,8 @@ def get_input_message(path, operation, inputdata, nodes=None, multinode=False,
|
||||
return InputLicense(path, nodes, inputdata, configmanager)
|
||||
elif path == ['deployment', 'lock'] and inputdata:
|
||||
return InputDeploymentLock(path, nodes, inputdata)
|
||||
elif path == ['deployment', 'remote_config', 'run'] and inputdata:
|
||||
return InputRemoteConfig(path, nodes, inputdata)
|
||||
elif path == ['deployment', 'ident_image']:
|
||||
return InputIdentImage(path, nodes, inputdata)
|
||||
elif path == ['console', 'ikvm']:
|
||||
@@ -603,6 +607,11 @@ class InputFirmwareUpdate(ConfluentMessage):
|
||||
|
||||
@property
|
||||
def filename(self):
|
||||
# TODO: get the currennt_user and cross reference if that user is allowed to
|
||||
# read... however, not sure wwhat to do if user is pure confluent user
|
||||
# though the staging may get an explicit pass, which should cover the web case...
|
||||
# media and firmware are ways to currently push things out, but if we allow profile export
|
||||
# what then?
|
||||
if self._complexname:
|
||||
raise Exception('User requested substitutions, but code is '
|
||||
'written against old api, code must be fixed or '
|
||||
@@ -721,6 +730,9 @@ class InputConfigChangeSet(InputExpression):
|
||||
endattrs = {}
|
||||
for attr in attrs:
|
||||
origval = attrs[attr]
|
||||
if isinstance(origval, int):
|
||||
endattrs[attr] = origval
|
||||
continue
|
||||
if isinstance(origval, bytes) or isinstance(origval, unicode):
|
||||
origval = {'expression': origval}
|
||||
if 'expression' not in origval:
|
||||
@@ -956,6 +968,20 @@ class ConfluentInputMessage(ConfluentMessage):
|
||||
def is_valid_key(self, key):
|
||||
return key in self.valid_values
|
||||
|
||||
class InputSigningParameters(InputConfigChangeSet):
|
||||
|
||||
def get_days(self, node):
|
||||
attribs = self.get_attributes(node)
|
||||
return int(attribs['days'])
|
||||
|
||||
def get_added_names(self, node):
|
||||
attribs = self.get_attributes(node)
|
||||
addnames = []
|
||||
for subj in (attribs.get('added_names') or '').split(','):
|
||||
if subj:
|
||||
addnames.append(subj.strip())
|
||||
return addnames
|
||||
|
||||
|
||||
class InputCertificateAuthority(ConfluentInputMessage):
|
||||
keyname = 'pem'
|
||||
@@ -975,6 +1001,10 @@ class InputDeploymentLock(ConfluentInputMessage):
|
||||
keyname = 'lock'
|
||||
valid_values = ['autolock', 'unlocked', 'locked']
|
||||
|
||||
class InputRemoteConfig(ConfluentInputMessage):
|
||||
keyname = 'category'
|
||||
valid_values = ['post.d', 'firstboot.d', 'onboot.d']
|
||||
|
||||
class DeploymentLock(ConfluentChoiceMessage):
|
||||
valid_values = set([
|
||||
'autolock',
|
||||
@@ -1703,6 +1733,7 @@ class Disk(ConfluentMessage):
|
||||
state_aliases = {
|
||||
'unconfigured bad': 'fault',
|
||||
'unconfigured good': 'unconfigured',
|
||||
'unconfiguredgood': 'unconfigured',
|
||||
'global hot spare': 'hotspare',
|
||||
'dedicated hot spare': 'hotspare',
|
||||
}
|
||||
|
||||
@@ -1,29 +1,28 @@
|
||||
|
||||
import eventlet
|
||||
import asyncio
|
||||
import confluent.messages as msg
|
||||
import confluent.exceptions as exc
|
||||
import struct
|
||||
import eventlet.green.socket as socket
|
||||
import eventlet.green.subprocess as subprocess
|
||||
import socket
|
||||
import os
|
||||
mountsbyuser = {}
|
||||
_browserfsd = None
|
||||
|
||||
def assure_browserfs():
|
||||
async def assure_browserfs():
|
||||
global _browserfsd
|
||||
if _browserfsd is None:
|
||||
os.makedirs('/var/run/confluent/browserfs/mount', exist_ok=True)
|
||||
_browserfsd = subprocess.Popen(
|
||||
['/opt/confluent/bin/browserfs',
|
||||
_browserfsd = await asyncio.subprocess.create_subprocess_exec(
|
||||
'/opt/confluent/bin/browserfs',
|
||||
'-c', '/var/run/confluent/browserfs/control',
|
||||
'-s', '127.0.0.1:4006',
|
||||
# browserfs supports unix domain websocket, however apache reverse proxy is dicey that way in some versions
|
||||
'-w', '/var/run/confluent/browserfs/mount'])
|
||||
'-w', '/var/run/confluent/browserfs/mount')
|
||||
while not os.path.exists('/var/run/confluent/browserfs/control'):
|
||||
eventlet.sleep(0.5)
|
||||
await asyncio.sleep(0.5)
|
||||
|
||||
|
||||
def handle_request(configmanager, inputdata, pathcomponents, operation):
|
||||
async def handle_request(configmanager, inputdata, pathcomponents, operation):
|
||||
curruser = configmanager.current_user
|
||||
if len(pathcomponents) == 0:
|
||||
mounts = mountsbyuser.get(curruser, [])
|
||||
@@ -39,7 +38,7 @@ def handle_request(configmanager, inputdata, pathcomponents, operation):
|
||||
curridx = 1
|
||||
while curridx in usedidx:
|
||||
curridx += 1
|
||||
currmount = requestmount(curruser, inputdata['name'])
|
||||
currmount = await requestmount(curruser, inputdata['name'])
|
||||
currmount['index'] = curridx
|
||||
if curruser not in mountsbyuser:
|
||||
mountsbyuser[curruser] = []
|
||||
@@ -50,26 +49,25 @@ def handle_request(configmanager, inputdata, pathcomponents, operation):
|
||||
'authtoken': currmount['authtoken']
|
||||
})
|
||||
|
||||
def requestmount(subdir, filename):
|
||||
assure_browserfs()
|
||||
async def requestmount(subdir, filename):
|
||||
await assure_browserfs()
|
||||
cloop = asyncio.get_event_loop()
|
||||
a = socket.socket(socket.AF_UNIX)
|
||||
a.connect('/var/run/confluent/browserfs/control')
|
||||
a.settimeout(0)
|
||||
await cloop.sock_connect(a, '/var/run/confluent/browserfs/control')
|
||||
subname = subdir.encode()
|
||||
a.send(struct.pack('!II', 1, len(subname)))
|
||||
a.send(subname)
|
||||
fname = filename.encode()
|
||||
a.send(struct.pack('!I', len(fname)))
|
||||
a.send(fname)
|
||||
rsp = a.recv(4)
|
||||
await cloop.sock_sendall(a, struct.pack('!II', 1, len(subname)) + subname + struct.pack('!I', len(fname)) + fname)
|
||||
rsp = await cloop.sock_recv(a, 4)
|
||||
retcode = struct.unpack('!I', rsp)[0]
|
||||
if retcode != 0:
|
||||
raise Exception("Bad return code")
|
||||
rsp = a.recv(4)
|
||||
rsp = await cloop.sock_recv(a, 4)
|
||||
nlen = struct.unpack('!I', rsp)[0]
|
||||
idstr = a.recv(nlen).decode('utf8')
|
||||
rsp = a.recv(4)
|
||||
idstr = (await cloop.sock_recv(a, nlen)).decode('utf8')
|
||||
rsp = await cloop.sock_recv(a, 4)
|
||||
nlen = struct.unpack('!I', rsp)[0]
|
||||
authtok = a.recv(nlen).decode('utf8')
|
||||
authtok = (await cloop.sock_recv(a, nlen)).decode('utf8')
|
||||
thismount = {
|
||||
'id': idstr,
|
||||
'path': '{}/{}/{}'.format(idstr, subdir, filename),
|
||||
|
||||
@@ -16,11 +16,11 @@
|
||||
|
||||
# A consolidated manage of neighbor table information management.
|
||||
|
||||
import asyncio
|
||||
import confluent.netutil as netutil
|
||||
import confluent.util as util
|
||||
import os
|
||||
import eventlet.semaphore as semaphore
|
||||
import eventlet.green.socket as socket
|
||||
import socket
|
||||
import struct
|
||||
|
||||
|
||||
@@ -33,25 +33,28 @@ neightime = 0
|
||||
|
||||
import re
|
||||
|
||||
neighlock = semaphore.Semaphore()
|
||||
neighlock = asyncio.Lock()
|
||||
|
||||
def _update_neigh():
|
||||
async def _update_neigh():
|
||||
global neightable
|
||||
global neightime
|
||||
neightime = os.times()[4]
|
||||
s = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, socket.NETLINK_ROUTE)
|
||||
s.bind((0, 0))
|
||||
s.settimeout(0)
|
||||
# RTM_GETNEIGH
|
||||
# nlmsghdr struct: u32 len, u16 type, u16 flags, u32 seq, u32 pid
|
||||
nlhdr = b'\x1c\x00\x00\x00\x1e\x00\x01\x03\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
# ndmsg struct u8 family u8 pad, u16 pad, s32 ifidx, u16 state, u8 flags, u8 type
|
||||
ndmsg= b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
|
||||
s.sendall(nlhdr + ndmsg)
|
||||
cloop = asyncio.get_event_loop()
|
||||
await cloop.sock_sendall(s, nlhdr + ndmsg)
|
||||
#s.sendall(nlhdr + ndmsg)
|
||||
neightable = {}
|
||||
inprogress = True
|
||||
try:
|
||||
while inprogress:
|
||||
pdata = s.recv(65536)
|
||||
pdata = await cloop.sock_recv(s, 65536)
|
||||
v = memoryview(pdata)
|
||||
while len(v):
|
||||
length, typ = struct.unpack('IH', v[:6])
|
||||
@@ -82,7 +85,7 @@ def _update_neigh():
|
||||
s.close()
|
||||
|
||||
|
||||
def get_hwaddr(ipaddr):
|
||||
async def get_hwaddr(ipaddr):
|
||||
if '%' in ipaddr:
|
||||
ipaddr, _ = ipaddr.split('%', 1)
|
||||
hwaddr = None
|
||||
@@ -92,16 +95,16 @@ def get_hwaddr(ipaddr):
|
||||
ipaddr = socket.inet_pton(socket.AF_INET6, ipaddr)
|
||||
elif '.' in ipaddr:
|
||||
ipaddr = socket.inet_pton(socket.AF_INET, ipaddr)
|
||||
with neighlock:
|
||||
async with neighlock:
|
||||
updated = False
|
||||
if os.times()[4] > (neightime + 30):
|
||||
_update_neigh()
|
||||
await _update_neigh()
|
||||
updated = True
|
||||
hwaddr = neightable.get(ipaddr, None)
|
||||
if not hwaddr and not netutil.ipn_is_local(ipaddr):
|
||||
if not hwaddr and not await netutil.ipn_is_local(ipaddr):
|
||||
hwaddr = False
|
||||
if hwaddr == None and not updated:
|
||||
_update_neigh()
|
||||
await _update_neigh()
|
||||
hwaddr = neightable.get(ipaddr, None)
|
||||
if hwaddr:
|
||||
hwaddr = ':'.join(['{:02x}'.format(x) for x in bytearray(hwaddr)])
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
# this will implement noderange grammar
|
||||
|
||||
|
||||
import asyncio
|
||||
import confluent.exceptions as exc
|
||||
import codecs
|
||||
try:
|
||||
@@ -24,13 +25,10 @@ except ImportError:
|
||||
psutil = None
|
||||
import netifaces
|
||||
import struct
|
||||
import eventlet.green.socket as socket
|
||||
import eventlet.support.greendns
|
||||
import os
|
||||
getaddrinfo = eventlet.support.greendns.getaddrinfo
|
||||
import socket
|
||||
import confluent.tasks as tasks
|
||||
|
||||
eventlet.support.greendns.resolver.clear()
|
||||
eventlet.support.greendns.resolver._resolver.lifetime = 1
|
||||
|
||||
def msg_align(len):
|
||||
return (len + 3) & ~3
|
||||
@@ -74,18 +72,18 @@ def ipn_on_same_subnet(fam, first, second, prefix):
|
||||
second = struct.unpack('!I', second)[0]
|
||||
return (first & mask == second & mask)
|
||||
|
||||
def ip_on_same_subnet(first, second, prefix):
|
||||
async def ip_on_same_subnet(first, second, prefix):
|
||||
if first.startswith('::ffff:') and '.' in first:
|
||||
first = first.replace('::ffff:', '')
|
||||
if second.startswith('::ffff:') and '.' in second:
|
||||
second = second.replace('::ffff:', '')
|
||||
addrinf = socket.getaddrinfo(first, None, 0, socket.SOCK_STREAM)[0]
|
||||
addrinf = (await asyncio.get_running_loop().getaddrinfo(first, 0, type=socket.SOCK_STREAM))[0]
|
||||
fam = addrinf[0]
|
||||
if '%' in addrinf[-1][0]:
|
||||
return False
|
||||
ip = socket.inet_pton(fam, addrinf[-1][0])
|
||||
ip = int(codecs.encode(bytes(ip), 'hex'), 16)
|
||||
addrinf = socket.getaddrinfo(second, None, 0, socket.SOCK_STREAM)[0]
|
||||
addrinf = (await asyncio.get_running_loop().getaddrinfo(second, 0, type=socket.SOCK_STREAM))[0]
|
||||
if fam != addrinf[0]:
|
||||
return False
|
||||
txtaddr = addrinf[-1][0].split('%')[0]
|
||||
@@ -101,10 +99,10 @@ def ip_on_same_subnet(first, second, prefix):
|
||||
return ip & mask == oip & mask
|
||||
|
||||
|
||||
def ipn_is_local(ipn):
|
||||
async def ipn_is_local(ipn):
|
||||
if len(ipn) > 5 and ipn.startswith(b'\xfe\x80'):
|
||||
return True
|
||||
for addr in get_my_addresses():
|
||||
for addr in await get_my_addresses():
|
||||
if len(addr[1]) != len(ipn):
|
||||
continue
|
||||
if ipn_on_same_subnet(addr[0], ipn, addr[1], addr[2]):
|
||||
@@ -112,25 +110,25 @@ def ipn_is_local(ipn):
|
||||
return False
|
||||
|
||||
|
||||
def address_is_local(address):
|
||||
async def address_is_local(address):
|
||||
if psutil:
|
||||
ifas = psutil.net_if_addrs()
|
||||
for iface in ifas:
|
||||
for addr in ifas[iface]:
|
||||
if addr.family in (socket.AF_INET, socket.AF_INET6):
|
||||
cidr = mask_to_cidr(addr.netmask)
|
||||
if ip_on_same_subnet(addr.address, address, cidr):
|
||||
if await ip_on_same_subnet(addr.address, address, cidr):
|
||||
return True
|
||||
else:
|
||||
for iface in netifaces.interfaces():
|
||||
for i4 in netifaces.ifaddresses(iface).get(2, []):
|
||||
cidr = mask_to_cidr(i4['netmask'])
|
||||
if ip_on_same_subnet(i4['addr'], address, cidr):
|
||||
if await ip_on_same_subnet(i4['addr'], address, cidr):
|
||||
return True
|
||||
for i6 in netifaces.ifaddresses(iface).get(10, []):
|
||||
cidr = int(i6['netmask'].split('/')[1])
|
||||
laddr = i6['addr'].split('%')[0]
|
||||
if ip_on_same_subnet(laddr, address, cidr):
|
||||
if await ip_on_same_subnet(laddr, address, cidr):
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -146,7 +144,7 @@ def _rebuildidxmap():
|
||||
pass
|
||||
|
||||
|
||||
def myiptonets(svrip):
|
||||
async def myiptonets(svrip):
|
||||
fam = socket.AF_INET
|
||||
if ':' in svrip:
|
||||
fam = socket.AF_INET6
|
||||
@@ -159,7 +157,7 @@ def myiptonets(svrip):
|
||||
continue
|
||||
addr = addr.address
|
||||
addr = addr.split('%')[0]
|
||||
if addresses_match(addr, svrip):
|
||||
if await addresses_match(addr, svrip):
|
||||
relevantnic = iface
|
||||
break
|
||||
else:
|
||||
@@ -170,7 +168,7 @@ def myiptonets(svrip):
|
||||
for addr in netifaces.ifaddresses(iface).get(fam, []):
|
||||
addr = addr.get('addr', '')
|
||||
addr = addr.split('%')[0]
|
||||
if addresses_match(addr, svrip):
|
||||
if await addresses_match(addr, svrip):
|
||||
relevantnic = iface
|
||||
break
|
||||
else:
|
||||
@@ -220,13 +218,12 @@ class NetManager(object):
|
||||
self.consumednames4 = set([])
|
||||
self.consumednames6 = set([])
|
||||
|
||||
@property
|
||||
def allmyaddrs(self):
|
||||
async def allmyaddrs(self):
|
||||
if not self._allmyaddrs:
|
||||
self._allmyaddrs = get_my_addresses()
|
||||
self._allmyaddrs = await get_my_addresses()
|
||||
return self._allmyaddrs
|
||||
|
||||
def process_attribs(self, netname, attribs):
|
||||
async def process_attribs(self, netname, attribs):
|
||||
self.myattribs[netname] = {}
|
||||
ipv4addr = None
|
||||
ipv6addr = None
|
||||
@@ -255,7 +252,7 @@ class NetManager(object):
|
||||
if ipv4addr:
|
||||
try:
|
||||
luaddr = ipv4addr.split('/', 1)[0]
|
||||
for ai in socket.getaddrinfo(luaddr, 0, socket.AF_INET, socket.SOCK_STREAM):
|
||||
for ai in await asyncio.get_running_loop().getaddrinfo(luaddr, 0, family=socket.AF_INET, type=socket.SOCK_STREAM):
|
||||
ipv4addr.replace(luaddr, ai[-1][0])
|
||||
except socket.gaierror:
|
||||
pass
|
||||
@@ -263,7 +260,7 @@ class NetManager(object):
|
||||
currname = attribs.get('hostname', self.node).split()[0]
|
||||
if currname and currname not in self.consumednames4:
|
||||
try:
|
||||
for ai in socket.getaddrinfo(currname, 0, socket.AF_INET, socket.SOCK_STREAM):
|
||||
for ai in await asyncio.get_running_loop().getaddrinfo(currname, 0, family=socket.AF_INET, type=socket.SOCK_STREAM):
|
||||
ipv4addr = ai[-1][0]
|
||||
self.consumednames4.add(currname)
|
||||
except socket.gaierror:
|
||||
@@ -280,7 +277,7 @@ class NetManager(object):
|
||||
ipv6addr = attribs.get('ipv6_address', None)
|
||||
if ipv6addr:
|
||||
try:
|
||||
for ai in socket.getaddrinfo(ipv6addr, 0, socket.AF_INET6, socket.SOCK_STREAM):
|
||||
for ai in await asyncio.get_running_loop().getaddrinfo(ipv6addr, 0, family=socket.AF_INET6, type=socket.SOCK_STREAM):
|
||||
ipv6addr = ai[-1][0]
|
||||
except socket.gaierror:
|
||||
pass
|
||||
@@ -288,7 +285,7 @@ class NetManager(object):
|
||||
currname = attribs.get('hostname', self.node).split()[0]
|
||||
if currname and currname not in self.consumednames6:
|
||||
try:
|
||||
for ai in socket.getaddrinfo(currname, 0, socket.AF_INET6, socket.SOCK_STREAM):
|
||||
for ai in await asyncio.get_running_loop().getaddrinfo(currname, 0, family=socket.AF_INET6, type=socket.SOCK_STREAM):
|
||||
ipv6addr = ai[-1][0]
|
||||
self.consumednames6.add(currname)
|
||||
except socket.gaierror:
|
||||
@@ -327,7 +324,7 @@ class NetManager(object):
|
||||
if '/' not in myattribs.get('ipv6_address', '/'):
|
||||
ipn = socket.inet_pton(socket.AF_INET6, myattribs['ipv6_address'])
|
||||
plen = 64
|
||||
for addr in self.allmyaddrs:
|
||||
for addr in await self.allmyaddrs():
|
||||
if addr[0] != socket.AF_INET6:
|
||||
continue
|
||||
if ipn_on_same_subnet(addr[0], ipn, addr[1], addr[2]):
|
||||
@@ -336,7 +333,7 @@ class NetManager(object):
|
||||
if '/' not in myattribs.get('ipv4_address', '/'):
|
||||
ipn = socket.inet_pton(socket.AF_INET, myattribs['ipv4_address'])
|
||||
plen = 16
|
||||
for addr in self.allmyaddrs:
|
||||
for addr in await self.allmyaddrs():
|
||||
if addr[0] != socket.AF_INET:
|
||||
continue
|
||||
if ipn_on_same_subnet(addr[0], ipn, addr[1], addr[2]):
|
||||
@@ -346,8 +343,8 @@ class NetManager(object):
|
||||
myattribs['current_nic'] = False
|
||||
|
||||
|
||||
def get_flat_net_config(configmanager, node):
|
||||
fnc = get_full_net_config(configmanager, node)
|
||||
async def get_flat_net_config(configmanager, node):
|
||||
fnc = await get_full_net_config(configmanager, node)
|
||||
dft = fnc.get('default', {})
|
||||
if dft:
|
||||
ret = [dft]
|
||||
@@ -364,7 +361,7 @@ def add_netmask(ncfg):
|
||||
plen = ncfg['ipv4_address'].split('/', 1)[1]
|
||||
ncfg['ipv4_netmask'] = cidr_to_mask(int(plen))
|
||||
|
||||
def get_full_net_config(configmanager, node, serverip=None):
|
||||
async def get_full_net_config(configmanager, node, serverip=None):
|
||||
cfd = configmanager.get_node_attributes(node, ['net.*'])
|
||||
cfd = cfd.get(node, {})
|
||||
bmc = configmanager.get_node_attributes(
|
||||
@@ -374,11 +371,11 @@ def get_full_net_config(configmanager, node, serverip=None):
|
||||
bmc6 = None
|
||||
if bmc:
|
||||
try:
|
||||
bmc4 = socket.getaddrinfo(bmc, 0, socket.AF_INET, socket.SOCK_DGRAM)[0][-1][0]
|
||||
bmc4 = (await asyncio.get_running_loop().getaddrinfo(bmc, 0, family=socket.AF_INET, type=socket.SOCK_DGRAM))[0][-1][0]
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
bmc6 = socket.getaddrinfo(bmc, 0, socket.AF_INET6, socket.SOCK_DGRAM)[0][-1][0]
|
||||
bmc6 = (await asyncio.get_running_loop().getaddrinfo(bmc, 0, family=socket.AF_INET6, type=socket.SOCK_DGRAM))[0][-1][0]
|
||||
except Exception:
|
||||
pass
|
||||
attribs = {}
|
||||
@@ -400,16 +397,16 @@ def get_full_net_config(configmanager, node, serverip=None):
|
||||
attribs[iface][attrib] = val
|
||||
myaddrs = []
|
||||
if serverip:
|
||||
myaddrs = get_addresses_by_serverip(serverip)
|
||||
myaddrs = await get_addresses_by_serverip(serverip)
|
||||
nm = NetManager(myaddrs, node, configmanager)
|
||||
defaultnic = {}
|
||||
ppool = eventlet.greenpool.GreenPool(64)
|
||||
ppool = tasks.TaskPool()
|
||||
if None in attribs:
|
||||
ppool.spawn(nm.process_attribs, None, attribs[None])
|
||||
ppool.schedule(nm.process_attribs, None, attribs[None])
|
||||
del attribs[None]
|
||||
for netname in sorted(attribs):
|
||||
ppool.spawn(nm.process_attribs, netname, attribs[netname])
|
||||
ppool.waitall()
|
||||
ppool.schedule(nm.process_attribs, netname, attribs[netname])
|
||||
await ppool.waitall()
|
||||
for iface in list(nm.myattribs):
|
||||
if bmc4 and nm.myattribs[iface].get('ipv4_address', None) == bmc4:
|
||||
del nm.myattribs[iface]
|
||||
@@ -422,7 +419,7 @@ def get_full_net_config(configmanager, node, serverip=None):
|
||||
add_netmask(retattrs['default'])
|
||||
del nm.myattribs[None]
|
||||
else:
|
||||
nnc = get_nic_config(configmanager, node, serverip=serverip)
|
||||
nnc = await get_nic_config(configmanager, node, serverip=serverip)
|
||||
if nnc.get('ipv4_address', None):
|
||||
defaultnic['ipv4_address'] = '{}/{}'.format(nnc['ipv4_address'], nnc['prefix'])
|
||||
if nnc.get('ipv4_gateway', None):
|
||||
@@ -477,7 +474,7 @@ def noneify(cfgdata):
|
||||
# that mac address
|
||||
# the ip as reported by recvmsg to match the subnet of that net.* interface
|
||||
# if switch and port available, that should match.
|
||||
def get_nic_config(configmanager, node, ip=None, mac=None, ifidx=None,
|
||||
async def get_nic_config(configmanager, node, ip=None, mac=None, ifidx=None,
|
||||
serverip=None, relayipn=b'\x00\x00\x00\x00',
|
||||
clientip=None, onlyfamily=None):
|
||||
"""Fetch network configuration parameters for a nic
|
||||
@@ -533,12 +530,12 @@ def get_nic_config(configmanager, node, ip=None, mac=None, ifidx=None,
|
||||
if bmc:
|
||||
try:
|
||||
if onlyfamily in (0, socket.AF_INET):
|
||||
bmc4 = socket.getaddrinfo(bmc, 0, socket.AF_INET, socket.SOCK_DGRAM)[0][-1][0]
|
||||
bmc4 = (await asyncio.get_running_loop().getaddrinfo(bmc, 0, family=socket.AF_INET, type=socket.SOCK_DGRAM))[0][-1][0]
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
if onlyfamily in (0, socket.AF_INET6):
|
||||
bmc6 = socket.getaddrinfo(bmc, 0, socket.AF_INET6, socket.SOCK_DGRAM)[0][-1][0]
|
||||
bmc6 = (await asyncio.get_running_loop().getaddrinfo(bmc, 0, family=socket.AF_INET6, type=socket.SOCK_DGRAM))[0][-1][0]
|
||||
except Exception:
|
||||
pass
|
||||
cfgbyname = {}
|
||||
@@ -564,7 +561,7 @@ def get_nic_config(configmanager, node, ip=None, mac=None, ifidx=None,
|
||||
myaddrs = []
|
||||
if ifidx is not None:
|
||||
dhcprequested = False
|
||||
myaddrs = get_my_addresses(ifidx, family=onlyfamily)
|
||||
myaddrs = await get_my_addresses(ifidx, family=onlyfamily)
|
||||
v4broken = True
|
||||
v6broken = True
|
||||
for addr in myaddrs:
|
||||
@@ -579,7 +576,7 @@ def get_nic_config(configmanager, node, ip=None, mac=None, ifidx=None,
|
||||
isremote = False
|
||||
if serverip is not None:
|
||||
dhcprequested = False
|
||||
myaddrs = get_addresses_by_serverip(serverip)
|
||||
myaddrs = await get_addresses_by_serverip(serverip)
|
||||
if serverfam == socket.AF_INET6 and ipn_on_same_subnet(serverfam, serveripn, llaipn, 64):
|
||||
isremote = False
|
||||
elif clientfam:
|
||||
@@ -597,13 +594,13 @@ def get_nic_config(configmanager, node, ip=None, mac=None, ifidx=None,
|
||||
ip6bynodename = None
|
||||
try:
|
||||
if onlyfamily in (socket.AF_INET, 0):
|
||||
for addr in socket.getaddrinfo(node, 0, socket.AF_INET, socket.SOCK_DGRAM):
|
||||
for addr in await asyncio.get_running_loop().getaddrinfo(node, 0, family=socket.AF_INET, type=socket.SOCK_DGRAM):
|
||||
ipbynodename = addr[-1][0]
|
||||
except socket.gaierror:
|
||||
pass
|
||||
try:
|
||||
if onlyfamily in (socket.AF_INET6, 0):
|
||||
for addr in socket.getaddrinfo(node, 0, socket.AF_INET6, socket.SOCK_DGRAM):
|
||||
for addr in await asyncio.get_running_loop().getaddrinfo(node, 0, family=socket.AF_INET6, type=socket.SOCK_DGRAM):
|
||||
ip6bynodename = addr[-1][0]
|
||||
except socket.gaierror:
|
||||
pass
|
||||
@@ -650,7 +647,7 @@ def get_nic_config(configmanager, node, ip=None, mac=None, ifidx=None,
|
||||
if bmc6 and candip == bmc6:
|
||||
continue
|
||||
try:
|
||||
for inf in socket.getaddrinfo(candip, 0, fam, socket.SOCK_STREAM):
|
||||
for inf in await asyncio.get_running_loop().getaddrinfo(candip, 0, family=fam, type=socket.SOCK_STREAM):
|
||||
candipn = socket.inet_pton(fam, inf[-1][0])
|
||||
if ((isremote and ipn_on_same_subnet(fam, clientipn, candipn, int(candprefix)))
|
||||
or ipn_on_same_subnet(fam, bootsvrip, candipn, prefix)):
|
||||
@@ -669,7 +666,7 @@ def get_nic_config(configmanager, node, ip=None, mac=None, ifidx=None,
|
||||
except Exception as e:
|
||||
cfgdata['error_msg'] = "Error trying to evaluate net.*ipv4_address attribute value '{0}' on {1}: {2}".format(candip, node, str(e))
|
||||
elif candgw:
|
||||
for inf in socket.getaddrinfo(candgw, 0, fam, socket.SOCK_STREAM):
|
||||
for inf in await asyncio.get_running_loop().getaddrinfo(candgw, 0, family=fam, type=socket.SOCK_STREAM):
|
||||
candgwn = socket.inet_pton(fam, inf[-1][0])
|
||||
if ipn_on_same_subnet(fam, bootsvrip, candgwn, prefix):
|
||||
candgws.append((fam, candgwn, prefix))
|
||||
@@ -731,7 +728,7 @@ def get_nic_config(configmanager, node, ip=None, mac=None, ifidx=None,
|
||||
cfgdata['ipv{}_gateway'.format(nver)] = socket.inet_ntop(fam, candgwn)
|
||||
return noneify(cfgdata)
|
||||
if ip is not None:
|
||||
for prefixinfo in get_prefix_len_for_ip(ip):
|
||||
async for prefixinfo in get_prefix_len_for_ip(ip):
|
||||
fam, prefix = prefixinfo
|
||||
ip = ip.split('/', 1)[0]
|
||||
if fam == socket.AF_INET:
|
||||
@@ -747,14 +744,14 @@ def get_nic_config(configmanager, node, ip=None, mac=None, ifidx=None,
|
||||
if gw is None or not gw:
|
||||
continue
|
||||
gwn = socket.inet_pton(fam, gw)
|
||||
ip = socket.getaddrinfo(ip, 0, proto=socket.IPPROTO_TCP, family=fam)[-1][-1][0]
|
||||
ip = (await asyncio.get_running_loop().getaddrinfo(ip, 0, proto=socket.IPPROTO_TCP, family=fam))[-1][-1][0]
|
||||
ipn = socket.inet_pton(fam, ip)
|
||||
if ipn_on_same_subnet(fam, ipn, gwn, prefix):
|
||||
cfgdata['ipv{}_gateway'.format(nver)] = gw
|
||||
break
|
||||
return noneify(cfgdata)
|
||||
|
||||
def get_addresses_by_serverip(serverip):
|
||||
async def get_addresses_by_serverip(serverip):
|
||||
if '.' in serverip:
|
||||
fam = socket.AF_INET
|
||||
elif ':' in serverip:
|
||||
@@ -763,15 +760,15 @@ def get_addresses_by_serverip(serverip):
|
||||
raise ValueError('"{0}" is not a valid ip argument'.format(serverip))
|
||||
ipbytes = socket.inet_pton(fam, serverip)
|
||||
if ipbytes[:8] == b'\xfe\x80\x00\x00\x00\x00\x00\x00':
|
||||
myaddrs = get_my_addresses(matchlla=ipbytes)
|
||||
myaddrs = await get_my_addresses(matchlla=ipbytes)
|
||||
else:
|
||||
myaddrs = [x for x in get_my_addresses() if x[1] == ipbytes]
|
||||
myaddrs = [x for x in await get_my_addresses() if x[1] == ipbytes]
|
||||
return myaddrs
|
||||
|
||||
nlhdrsz = struct.calcsize('IHHII')
|
||||
ifaddrsz = struct.calcsize('BBBBI')
|
||||
|
||||
def get_my_addresses(idx=0, family=0, matchlla=None):
|
||||
async def get_my_addresses(idx=0, family=0, matchlla=None):
|
||||
# RTM_GETADDR = 22
|
||||
# nlmsghdr struct: u32 len, u16 type, u16 flags, u32 seq, u32 pid
|
||||
nlhdr = struct.pack('IHHII', nlhdrsz + ifaddrsz, 22, 0x301, 0, 0)
|
||||
@@ -779,10 +776,11 @@ def get_my_addresses(idx=0, family=0, matchlla=None):
|
||||
ifaddrmsg = struct.pack('BBBBI', family, 0, 0, 0, idx)
|
||||
s = socket.socket(socket.AF_NETLINK, socket.SOCK_RAW, socket.NETLINK_ROUTE)
|
||||
s.bind((0, 0))
|
||||
s.sendall(nlhdr + ifaddrmsg)
|
||||
s.setblocking(False)
|
||||
await asyncio.get_event_loop().sock_sendall(s, nlhdr + ifaddrmsg)
|
||||
addrs = []
|
||||
while True:
|
||||
pdata = s.recv(65536)
|
||||
pdata = await asyncio.get_event_loop().sock_recv(s, 65536)
|
||||
v = memoryview(pdata)
|
||||
if struct.unpack('H', v[4:6])[0] == 3: # netlink done message
|
||||
break
|
||||
@@ -798,7 +796,7 @@ def get_my_addresses(idx=0, family=0, matchlla=None):
|
||||
if rtalen < 4:
|
||||
break
|
||||
if rta[4:rtalen].tobytes() == matchlla:
|
||||
return get_my_addresses(idx=ridx)
|
||||
return await get_my_addresses(idx=ridx)
|
||||
rta = rta[msg_align(rtalen):]
|
||||
elif (ridx == idx or not idx) and scope == 0:
|
||||
rta = v[nlhdrsz+ifaddrsz:length]
|
||||
@@ -813,14 +811,14 @@ def get_my_addresses(idx=0, family=0, matchlla=None):
|
||||
return addrs
|
||||
|
||||
|
||||
def get_prefix_len_for_ip(ip):
|
||||
async def get_prefix_len_for_ip(ip):
|
||||
plen = None
|
||||
if '/' in ip:
|
||||
ip, plen = ip.split('/', 1)
|
||||
plen = int(plen)
|
||||
myaddrs = get_my_addresses()
|
||||
myaddrs = await get_my_addresses()
|
||||
found = False
|
||||
for inf in socket.getaddrinfo(ip, 0, 0, socket.SOCK_DGRAM):
|
||||
for inf in await asyncio.get_running_loop().getaddrinfo(ip, 0, type=socket.SOCK_DGRAM):
|
||||
if plen:
|
||||
yield (inf[0], plen)
|
||||
return
|
||||
@@ -833,7 +831,7 @@ def get_prefix_len_for_ip(ip):
|
||||
if not found:
|
||||
raise exc.NotImplementedException("Non local addresses not supported")
|
||||
|
||||
def addresses_match(addr1, addr2):
|
||||
async def addresses_match(addr1, addr2):
|
||||
"""Check two network addresses for similarity
|
||||
|
||||
Is it zero padded in one place, not zero padded in another? Is one place by name and another by IP??
|
||||
@@ -846,12 +844,12 @@ def addresses_match(addr1, addr2):
|
||||
"""
|
||||
if '%' in addr1 or '%' in addr2:
|
||||
return False
|
||||
for addrinfo in socket.getaddrinfo(addr1, 0, 0, socket.SOCK_STREAM):
|
||||
for addrinfo in await asyncio.get_running_loop().getaddrinfo(addr1, 0, type=socket.SOCK_STREAM):
|
||||
rootaddr1 = socket.inet_pton(addrinfo[0], addrinfo[4][0])
|
||||
if addrinfo[0] == socket.AF_INET6 and rootaddr1[:12] == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff':
|
||||
# normalize to standard IPv4
|
||||
rootaddr1 = rootaddr1[-4:]
|
||||
for otherinfo in socket.getaddrinfo(addr2, 0, 0, socket.SOCK_STREAM):
|
||||
for otherinfo in await asyncio.get_running_loop().getaddrinfo(addr2, 0, type=socket.SOCK_STREAM):
|
||||
otheraddr = socket.inet_pton(otherinfo[0], otherinfo[4][0])
|
||||
if otherinfo[0] == socket.AF_INET6 and otheraddr[:12] == b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff':
|
||||
otheraddr = otheraddr[-4:]
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
# This provides the implementation of locating MAC addresses on ethernet
|
||||
# switches. It is, essentially, a port of 'MacMap.pm' to confluent.
|
||||
# However, there are enhancements.
|
||||
# For one, each switch interrogation is handled in an eventlet 'thread'
|
||||
# For one, each switch interrogation is handled in an async coroutine
|
||||
# For another, MAC addresses are checked in the dictionary on every
|
||||
# switch return, rather than waiting for all switches to check in
|
||||
# (which makes it more responsive when there is a missing or bad switch)
|
||||
@@ -33,19 +33,21 @@
|
||||
if __name__ == '__main__':
|
||||
import sys
|
||||
import confluent.config.configmanager as cfm
|
||||
import asyncio
|
||||
import base64
|
||||
import confluent.networking.nxapi as nxapi
|
||||
import confluent.networking.srlinux as srlinux
|
||||
import confluent.exceptions as exc
|
||||
import confluent.log as log
|
||||
import confluent.messages as msg
|
||||
import confluent.snmputil as snmp
|
||||
import confluent.networking.netutil as netutil
|
||||
import confluent.util as util
|
||||
import eventlet
|
||||
from eventlet.greenpool import GreenPool
|
||||
import eventlet.semaphore
|
||||
import confluent.tasks as tasks
|
||||
import re
|
||||
webclient = eventlet.import_patched('pyghmi.util.webclient')
|
||||
|
||||
import aiohmi.util.webclient as webclient
|
||||
|
||||
# The interesting OIDs are:
|
||||
# lldpLocChassisId - to cross reference (1.0.8802.1.1.2.1.3.2.0)
|
||||
# lldpLocPortId - for cross referencing.. (1.0.8802.1.1.2.1.3.7.1.3)
|
||||
@@ -94,7 +96,7 @@ def lenovoname(idx, desc):
|
||||
return desc
|
||||
|
||||
nameoverrides = [
|
||||
(re.compile('20301\..*'), lenovoname),
|
||||
(re.compile(r'20301\..*'), lenovoname),
|
||||
]
|
||||
|
||||
# Lenovo chassis id rule is match only first 5 bytes for a match.....
|
||||
@@ -176,39 +178,45 @@ def _init_lldp(data, iname, idx, idxtoportid, switch):
|
||||
'chassisid': _chassisidbyswitch[switch]}
|
||||
|
||||
_fastbackends = {}
|
||||
def detect_backend(switch, verifier):
|
||||
async def detect_backend(switch, verifier):
|
||||
backend = _fastbackends.get(switch, None)
|
||||
if backend:
|
||||
return backend
|
||||
wc = webclient.SecureHTTPConnection(
|
||||
wc = webclient.WebConnection(
|
||||
switch, 443, verifycallback=verifier, timeout=5)
|
||||
apicheck, retcode = wc.grab_json_response_with_status('/affluent/')
|
||||
apicheck, retcode = await wc.grab_json_response_with_status('/affluent/')
|
||||
if retcode == 401 and apicheck.startswith(b'{}'):
|
||||
_fastbackends[switch] = 'affluent'
|
||||
else:
|
||||
apicheck, retcode = wc.grab_json_response_with_status('/api/')
|
||||
apicheck, retcode = await wc.grab_json_response_with_status('/api/')
|
||||
if retcode == 400 and apicheck.startswith(b'{"imdata":['):
|
||||
_fastbackends[switch] = 'nxapi'
|
||||
else:
|
||||
rsp = await wc.grab_response_with_status('/jsonrpc', {'dummy': 'data'}, expect_type='json')
|
||||
if rsp[1] == 401 and rsp[2].get('WWW-Authenticate', '').startswith('Basic realm="SRLinux"'):
|
||||
_fastbackends[switch] = 'srlinux'
|
||||
return _fastbackends.get(switch, None)
|
||||
|
||||
def _extract_neighbor_data_https(switch, user, password, cfm, lldpdata):
|
||||
async def _extract_neighbor_data_https(switch, user, password, cfm, lldpdata):
|
||||
kv = util.TLSCertVerifier(cfm, switch,
|
||||
'pubkeys.tls_hardwaremanager').verify_cert
|
||||
backend = detect_backend(switch, kv)
|
||||
backend = await detect_backend(switch, kv)
|
||||
if not backend:
|
||||
raise Exception("No HTTPS backend identified")
|
||||
wc = webclient.SecureHTTPConnection(
|
||||
wc = webclient.WebConnection(
|
||||
switch, 443, verifycallback=kv, timeout=5)
|
||||
if backend == 'affluent':
|
||||
return _extract_neighbor_data_affluent(switch, user, password, cfm, lldpdata, wc)
|
||||
return await _extract_neighbor_data_affluent(switch, user, password, cfm, lldpdata, wc)
|
||||
elif backend == 'nxapi':
|
||||
return _extract_neighbor_data_nxapi(switch, user, password, cfm, lldpdata, wc)
|
||||
return await _extract_neighbor_data_nxapi(switch, user, password, cfm, lldpdata, wc)
|
||||
elif backend == 'srlinux':
|
||||
return await _extract_neighbor_data_srlinux(switch, user, password, cfm, lldpdata, wc)
|
||||
|
||||
|
||||
|
||||
def _extract_neighbor_data_nxapi(switch, user, password, cfm, lldpdata, wc):
|
||||
async def _extract_neighbor_data_nxapi(switch, user, password, cfm, lldpdata, wc):
|
||||
cli = nxapi.NxApiClient(switch, user, password, cfm)
|
||||
lldpinfo = cli.get_lldp()
|
||||
lldpinfo = await cli.get_lldp()
|
||||
for port in lldpinfo:
|
||||
portdata = lldpinfo[port]
|
||||
peerid = '{0}.{1}'.format(
|
||||
@@ -217,13 +225,32 @@ def _extract_neighbor_data_nxapi(switch, user, password, cfm, lldpdata, wc):
|
||||
)
|
||||
portdata['peerid'] = peerid
|
||||
_extract_extended_desc(portdata, portdata['peerdescription'], True)
|
||||
portdata['switch'] = switch
|
||||
_neighbypeerid[peerid] = portdata
|
||||
lldpdata[port] = portdata
|
||||
_neighdata[switch] = lldpdata
|
||||
|
||||
def _extract_neighbor_data_affluent(switch, user, password, cfm, lldpdata, wc):
|
||||
async def _extract_neighbor_data_srlinux(switch, user, password, cfm, lldpdata, wc):
|
||||
cli = srlinux.SRLinuxClient(switch, user, password, cfm)
|
||||
await cli.login()
|
||||
lldpinfo = await cli.get_lldp()
|
||||
for port in lldpinfo:
|
||||
portdata = lldpinfo[port]
|
||||
peerid = '{0}.{1}'.format(
|
||||
portdata.get('peerchassisid', '').replace(':', '-').replace('/', '-'),
|
||||
portdata.get('peerportid', '').replace(':', '-').replace('/', '-'),
|
||||
)
|
||||
portdata['peerid'] = peerid
|
||||
_extract_extended_desc(portdata, portdata['peerdescription'], True)
|
||||
portdata['switch'] = switch
|
||||
_neighbypeerid[peerid] = portdata
|
||||
lldpdata[port] = portdata
|
||||
_neighdata[switch] = lldpdata
|
||||
|
||||
|
||||
async def _extract_neighbor_data_affluent(switch, user, password, cfm, lldpdata, wc):
|
||||
wc.set_basic_credentials(user, password)
|
||||
neighdata = wc.grab_json_response('/affluent/lldp/all')
|
||||
neighdata = await wc.grab_json_response('/affluent/lldp/all')
|
||||
chassisid = neighdata['chassis']['id']
|
||||
_chassisidbyswitch[switch] = chassisid,
|
||||
for record in neighdata['neighbors']:
|
||||
@@ -250,58 +277,67 @@ def _extract_neighbor_data_affluent(switch, user, password, cfm, lldpdata, wc):
|
||||
_neighdata[switch] = lldpdata
|
||||
|
||||
|
||||
def _extract_neighbor_data_b(args):
|
||||
async def _extract_neighbor_data_b(args):
|
||||
"""Build LLDP data about elements connected to switch
|
||||
|
||||
args are carried as a tuple, because of eventlet convenience
|
||||
args are carried as a tuple
|
||||
"""
|
||||
switch, password, user, cfm, force = args[:5]
|
||||
# Safely unpack args with defaults to avoid IndexError
|
||||
switch = args[0] if len(args) > 0 else None
|
||||
password = args[1] if len(args) > 1 else None
|
||||
user = args[2] if len(args) > 2 else None
|
||||
cfm = args[3] if len(args) > 3 else None
|
||||
privproto = args[4] if len(args) > 4 else None
|
||||
force = args[5] if len(args) > 5 else False
|
||||
vintage = _neighdata.get(switch, {}).get('!!vintage', 0)
|
||||
now = util.monotonic_time()
|
||||
if vintage > (now - 60) and not force:
|
||||
return
|
||||
lldpdata = {'!!vintage': now}
|
||||
try:
|
||||
return _extract_neighbor_data_https(switch, user, password, cfm, lldpdata)
|
||||
return await _extract_neighbor_data_https(switch, user, password, cfm, lldpdata)
|
||||
except Exception as e:
|
||||
pass
|
||||
conn = snmp.Session(switch, password, user)
|
||||
conn = snmp.Session(switch, password, user, privacy_protocol=privproto)
|
||||
sid = None
|
||||
for sysid in conn.walk('1.3.6.1.2.1.1.2'):
|
||||
async for sysid in conn.walk('1.3.6.1.2.1.1.2'):
|
||||
sid = str(sysid[1][6:])
|
||||
_noaffluent.add(switch)
|
||||
idxtoifname = {}
|
||||
idxtoportid = {}
|
||||
_chassisidbyswitch[switch] = sanitize(list(
|
||||
conn.walk('1.0.8802.1.1.2.1.3.2'))[0][1])
|
||||
for oidindex in conn.walk('1.0.8802.1.1.2.1.3.7.1.3'):
|
||||
async for cid in conn.walk('1.0.8802.1.1.2.1.3.2'):
|
||||
_chassisidbyswitch[switch] = sanitize(cid[1])
|
||||
break
|
||||
#_chassisidbyswitch[switch] = sanitize(list(
|
||||
# conn.walk('1.0.8802.1.1.2.1.3.2'))[0][1])
|
||||
async for oidindex in conn.walk('1.0.8802.1.1.2.1.3.7.1.3'):
|
||||
idx = oidindex[0][-1]
|
||||
idxtoportid[idx] = sanitize(oidindex[1])
|
||||
for oidindex in conn.walk('1.0.8802.1.1.2.1.3.7.1.4'):
|
||||
async for oidindex in conn.walk('1.0.8802.1.1.2.1.3.7.1.4'):
|
||||
idx = oidindex[0][-1]
|
||||
idxtoifname[idx] = _lldpdesc_to_ifname(sid, idx, str(oidindex[1]))
|
||||
for remotedesc in conn.walk('1.0.8802.1.1.2.1.4.1.1.10'):
|
||||
async for remotedesc in conn.walk('1.0.8802.1.1.2.1.4.1.1.10'):
|
||||
iname = idxtoifname.get(remotedesc[0][-2],
|
||||
idxtoportid.get(remotedesc[0][-2], None))
|
||||
if iname is None:
|
||||
continue
|
||||
_init_lldp(lldpdata, iname, remotedesc[0][-2], idxtoportid, switch)
|
||||
_extract_extended_desc(lldpdata[iname], remotedesc[1], user)
|
||||
for remotename in conn.walk('1.0.8802.1.1.2.1.4.1.1.9'):
|
||||
async for remotename in conn.walk('1.0.8802.1.1.2.1.4.1.1.9'):
|
||||
iname = idxtoifname.get(remotename[0][-2],
|
||||
idxtoportid.get(remotename[0][-2], None))
|
||||
if iname is None:
|
||||
continue
|
||||
_init_lldp(lldpdata, iname, remotename[0][-2], idxtoportid, switch)
|
||||
lldpdata[iname]['peername'] = str(remotename[1])
|
||||
for remotename in conn.walk('1.0.8802.1.1.2.1.4.1.1.7'):
|
||||
async for remotename in conn.walk('1.0.8802.1.1.2.1.4.1.1.7'):
|
||||
iname = idxtoifname.get(remotename[0][-2],
|
||||
idxtoportid.get(remotename[0][-2], None))
|
||||
if iname is None:
|
||||
continue
|
||||
_init_lldp(lldpdata, iname, remotename[0][-2], idxtoportid, switch)
|
||||
lldpdata[iname]['peerportid'] = sanitize(remotename[1])
|
||||
for remoteid in conn.walk('1.0.8802.1.1.2.1.4.1.1.5'):
|
||||
async for remoteid in conn.walk('1.0.8802.1.1.2.1.4.1.1.5'):
|
||||
iname = idxtoifname.get(remoteid[0][-2],
|
||||
idxtoportid.get(remoteid[0][-2], None))
|
||||
if iname is None:
|
||||
@@ -321,19 +357,20 @@ def _extract_neighbor_data_b(args):
|
||||
_neighdata[switch] = lldpdata
|
||||
|
||||
|
||||
def update_switch_data(switch, configmanager, force=False, retexc=False):
|
||||
async def update_switch_data(switch, configmanager, force=False, retexc=False):
|
||||
switchcreds = netutil.get_switchcreds(configmanager, (switch,))[0]
|
||||
ndr = _extract_neighbor_data(switchcreds + (force, retexc))
|
||||
ndr = await _extract_neighbor_data(switchcreds + (force, retexc))
|
||||
if retexc and isinstance(ndr, Exception):
|
||||
raise ndr
|
||||
return _neighdata.get(switch, {})
|
||||
|
||||
|
||||
def update_neighbors(configmanager, force=False, retexc=False):
|
||||
return _update_neighbors_backend(configmanager, force, retexc)
|
||||
async def update_neighbors(configmanager, force=False, retexc=False):
|
||||
async for ans in _update_neighbors_backend(configmanager, force, retexc):
|
||||
yield ans
|
||||
|
||||
|
||||
def _update_neighbors_backend(configmanager, force, retexc):
|
||||
async def _update_neighbors_backend(configmanager, force, retexc):
|
||||
global _neighdata
|
||||
global _neighbypeerid
|
||||
vintage = _neighdata.get('!!vintage', 0)
|
||||
@@ -345,27 +382,26 @@ def _update_neighbors_backend(configmanager, force, retexc):
|
||||
switches = netutil.list_switches(configmanager)
|
||||
switchcreds = netutil.get_switchcreds(configmanager, switches)
|
||||
switchcreds = [ x + (force, retexc) for x in switchcreds]
|
||||
pool = GreenPool(64)
|
||||
for ans in pool.imap(_extract_neighbor_data, switchcreds):
|
||||
async for ans in tasks.task_imap(_extract_neighbor_data, switchcreds, max_concurrent=64):
|
||||
yield ans
|
||||
|
||||
|
||||
def _extract_neighbor_data(args):
|
||||
async def _extract_neighbor_data(args):
|
||||
# single switch neighbor data update
|
||||
switch = args[0]
|
||||
if switch not in _updatelocks:
|
||||
_updatelocks[switch] = eventlet.semaphore.Semaphore()
|
||||
_updatelocks[switch] = asyncio.Semaphore()
|
||||
if _updatelocks[switch].locked():
|
||||
while _updatelocks[switch].locked():
|
||||
eventlet.sleep(1)
|
||||
await asyncio.sleep(1)
|
||||
return
|
||||
try:
|
||||
with _updatelocks[switch]:
|
||||
return _extract_neighbor_data_b(args)
|
||||
async with _updatelocks[switch]:
|
||||
return await _extract_neighbor_data_b(args)
|
||||
except Exception as e:
|
||||
yieldexc = False
|
||||
if len(args) >= 6:
|
||||
yieldexc = args[5]
|
||||
if len(args) >= 7:
|
||||
yieldexc = args[6]
|
||||
if yieldexc:
|
||||
return e
|
||||
else:
|
||||
@@ -376,6 +412,7 @@ if __name__ == '__main__':
|
||||
# (should do three argument form for snmpv3 test
|
||||
import sys
|
||||
_extract_neighbor_data((sys.argv[1], sys.argv[2], None, True))
|
||||
asyncio.run(_extract_neighbor_data((sys.argv[1], sys.argv[2], None, True)))
|
||||
print(repr(_neighdata))
|
||||
|
||||
|
||||
@@ -430,14 +467,14 @@ def list_info(parms, requestedparameter):
|
||||
results.add(_api_sanitize_string(candidate))
|
||||
return [msg.ChildCollection(x + suffix) for x in util.natural_sort(results)]
|
||||
|
||||
def _handle_neighbor_query(pathcomponents, configmanager):
|
||||
async def _handle_neighbor_query(pathcomponents, configmanager):
|
||||
choices, parms, listrequested, childcoll = _parameterize_path(
|
||||
pathcomponents)
|
||||
if not childcoll: # this means it's a single entry with by-peerid
|
||||
# guaranteed
|
||||
if (parms['by-peerid'] not in _neighbypeerid and
|
||||
_neighbypeerid.get('!!vintage', 0) < util.monotonic_time() - 60):
|
||||
for x in update_neighbors(configmanager, retexc=True):
|
||||
async for x in update_neighbors(configmanager, retexc=True):
|
||||
if isinstance(x, Exception):
|
||||
raise x
|
||||
if parms['by-peerid'] not in _neighbypeerid:
|
||||
@@ -448,9 +485,9 @@ def _handle_neighbor_query(pathcomponents, configmanager):
|
||||
if listrequested not in multi_selectors | single_selectors:
|
||||
raise exc.NotFoundException('{0} is not found'.format(listrequested))
|
||||
if 'by-switch' in parms:
|
||||
update_switch_data(parms['by-switch'], configmanager, retexc=True)
|
||||
await update_switch_data(parms['by-switch'], configmanager, retexc=True)
|
||||
else:
|
||||
for x in update_neighbors(configmanager, retexc=True):
|
||||
async for x in update_neighbors(configmanager, retexc=True):
|
||||
if isinstance(x, Exception):
|
||||
raise x
|
||||
return list_info(parms, listrequested)
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
# This provides the implementation of locating MAC addresses on ethernet
|
||||
# switches. It is, essentially, a port of 'MacMap.pm' to confluent.
|
||||
# However, there are enhancements.
|
||||
# For one, each switch interrogation is handled in an eventlet 'thread'
|
||||
# For one, each switch interrogation is handled in an coroutine
|
||||
# For another, MAC addresses are checked in the dictionary on every
|
||||
# switch return, rather than waiting for all switches to check in
|
||||
# (which makes it more responsive when there is a missing or bad switch)
|
||||
@@ -42,29 +42,26 @@ if __name__ == '__main__':
|
||||
import confluent.config.configmanager as cfm
|
||||
import confluent.snmputil as snmp
|
||||
|
||||
|
||||
import asyncio
|
||||
from confluent.networking.lldp import detect_backend, _handle_neighbor_query, get_fingerprint
|
||||
from confluent.networking.netutil import get_switchcreds, list_switches, get_portnamemap
|
||||
import eventlet.green.select as select
|
||||
|
||||
import eventlet.green.socket as socket
|
||||
import socket
|
||||
|
||||
import confluent.collective.manager as collective
|
||||
import confluent.exceptions as exc
|
||||
import confluent.log as log
|
||||
import confluent.messages as msg
|
||||
import confluent.noderange as noderange
|
||||
import confluent.tasks as tasks
|
||||
import confluent.networking.nxapi as nxapi
|
||||
import confluent.networking.srlinux as srlinux
|
||||
import confluent.util as util
|
||||
from eventlet.greenpool import GreenPool
|
||||
import eventlet.green.subprocess as subprocess
|
||||
import fcntl
|
||||
import eventlet
|
||||
import eventlet.semaphore
|
||||
import msgpack
|
||||
import random
|
||||
import re
|
||||
webclient = eventlet.import_patched('pyghmi.util.webclient')
|
||||
import aiohmi.util.webclient as webclient
|
||||
|
||||
|
||||
noaffluent = set([])
|
||||
@@ -125,9 +122,9 @@ def _namesmatch(switchdesc, userdesc):
|
||||
return True
|
||||
return False
|
||||
|
||||
def _map_switch(args):
|
||||
async def _map_switch(args):
|
||||
try:
|
||||
return _map_switch_backend(args)
|
||||
return await _map_switch_backend(args)
|
||||
except (UnicodeError, socket.gaierror):
|
||||
log.log({'error': "Cannot resolve switch '{0}' to an address".format(
|
||||
args[0])})
|
||||
@@ -152,34 +149,44 @@ def _nodelookup(switch, ifname):
|
||||
return _switchportmap[switch][portdesc]
|
||||
return None
|
||||
|
||||
def _fast_map_switch(args):
|
||||
switch, password, user, cfgm = args
|
||||
async def _fast_map_switch(args):
|
||||
switch, password, user, cfgm = args[:4]
|
||||
macdata = None
|
||||
kv = util.TLSCertVerifier(cfgm, switch,
|
||||
'pubkeys.tls_hardwaremanager').verify_cert
|
||||
backend = detect_backend(switch, kv)
|
||||
backend = await detect_backend(switch, kv)
|
||||
if backend == 'affluent':
|
||||
return _affluent_map_switch(switch, password, user, cfgm, macdata)
|
||||
return await _affluent_map_switch(switch, password, user, cfgm, macdata)
|
||||
elif backend == 'nxapi':
|
||||
return _nxapi_map_switch(switch, password, user, cfgm)
|
||||
return await _nxapi_map_switch(switch, password, user, cfgm)
|
||||
elif backend == 'srlinux':
|
||||
return await _srlinux_map_switch(switch, password, user, cfgm)
|
||||
raise Exception("No fast backend match")
|
||||
|
||||
def _nxapi_map_switch(switch, password, user, cfgm):
|
||||
async def _srlinux_map_switch(switch, password, user, cfgm):
|
||||
cli = srlinux.SRLinuxClient(switch, user, password, cfgm)
|
||||
await cli.login()
|
||||
mt = await cli.get_mac_table()
|
||||
_macsbyswitch[switch] = mt
|
||||
_fast_backend_fixup(mt, switch)
|
||||
|
||||
async def _nxapi_map_switch(switch, password, user, cfgm):
|
||||
cli = nxapi.NxApiClient(switch, user, password, cfgm)
|
||||
mt = cli.get_mac_table()
|
||||
await cli.login()
|
||||
mt = await cli.get_mac_table()
|
||||
_macsbyswitch[switch] = mt
|
||||
_fast_backend_fixup(mt, switch)
|
||||
|
||||
|
||||
|
||||
def _affluent_map_switch(switch, password, user, cfgm, macs):
|
||||
async def _affluent_map_switch(switch, password, user, cfgm, macs):
|
||||
if not macs:
|
||||
kv = util.TLSCertVerifier(cfgm, switch,
|
||||
'pubkeys.tls_hardwaremanager').verify_cert
|
||||
wc = webclient.SecureHTTPConnection(
|
||||
wc = webclient.WebConnection(
|
||||
switch, 443, verifycallback=kv, timeout=5)
|
||||
wc.set_basic_credentials(user, password)
|
||||
macs, retcode = wc.grab_json_response_with_status('/affluent/macs/by-port')
|
||||
macs, retcode = await wc.grab_json_response_with_status('/affluent/macs/by-port')
|
||||
if retcode != 200:
|
||||
raise Exception("No affluent detected")
|
||||
_macsbyswitch[switch] = macs
|
||||
@@ -213,17 +220,18 @@ def _fast_backend_fixup(macs, switch):
|
||||
else:
|
||||
_nodesbymac[mac] = (nodename, nummacs)
|
||||
|
||||
def _offload_map_switch(switch, password, user):
|
||||
async def _offload_map_switch(switch, password, user, privprotocol=None):
|
||||
if _offloader is None:
|
||||
_start_offloader()
|
||||
await _start_offloader()
|
||||
evtid = random.randint(0, 4294967295)
|
||||
while evtid in _offloadevts:
|
||||
evtid = random.randint(0, 4294967295)
|
||||
_offloadevts[evtid] = eventlet.Event()
|
||||
_offloader.stdin.write(msgpack.packb((evtid, switch, password, user),
|
||||
use_bin_type=True))
|
||||
_offloader.stdin.flush()
|
||||
result = _offloadevts[evtid].wait()
|
||||
_offloadevts[evtid] = asyncio.get_event_loop().create_future()
|
||||
_offloader.stdin.write(msgpack.packb((evtid, switch, password, user, privprotocol),
|
||||
use_bin_type=True))
|
||||
#_offloader.stdin.flush()
|
||||
await _offloader.stdin.drain()
|
||||
result = await _offloadevts[evtid]
|
||||
del _offloadevts[evtid]
|
||||
if len(result) == 2:
|
||||
if result[0] == 1:
|
||||
@@ -234,36 +242,36 @@ def _offload_map_switch(switch, password, user):
|
||||
|
||||
|
||||
|
||||
def _start_offloader():
|
||||
async def _start_offloader():
|
||||
global _offloader
|
||||
_offloader = subprocess.Popen(
|
||||
[sys.executable, __file__, '-o'], bufsize=0, stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE)
|
||||
fl = fcntl.fcntl(_offloader.stdout.fileno(), fcntl.F_GETFL)
|
||||
fcntl.fcntl(_offloader.stdout.fileno(),
|
||||
fcntl.F_SETFL, fl | os.O_NONBLOCK)
|
||||
eventlet.spawn_n(_recv_offload)
|
||||
eventlet.sleep(0)
|
||||
#_offloader = subprocess.Popen(
|
||||
# [sys.executable, __file__, '-o'], bufsize=0, stdin=subprocess.PIPE,
|
||||
# stdout=subprocess.PIPE)
|
||||
_offloader = await asyncio.subprocess.create_subprocess_exec(sys.executable, __file__, '-o', stdin=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE)
|
||||
#fl = fcntl.fcntl(_offloader.stdout.fileno(), fcntl.F_GETFL)
|
||||
#fcntl.fcntl(_offloader.stdout.fileno(),
|
||||
# fcntl.F_SETFL, fl | os.O_NONBLOCK)
|
||||
asyncio.get_event_loop().create_task(_recv_offload())
|
||||
|
||||
|
||||
def _recv_offload():
|
||||
async def _recv_offload():
|
||||
try:
|
||||
upacker = msgpack.Unpacker(encoding='utf8')
|
||||
except TypeError:
|
||||
upacker = msgpack.Unpacker(raw=False, strict_map_key=False)
|
||||
instream = _offloader.stdout.fileno()
|
||||
#instream = _offloader.stdout.fileno()
|
||||
while True:
|
||||
select.select([_offloader.stdout], [], [])
|
||||
upacker.feed(os.read(instream, 128))
|
||||
datum = await _offloader.stdout.read(512)
|
||||
upacker.feed(datum)
|
||||
for result in upacker:
|
||||
if result[0] not in _offloadevts:
|
||||
print("Uh oh, unexpected event id... " + repr(result))
|
||||
continue
|
||||
_offloadevts[result[0]].send(result[1:])
|
||||
eventlet.sleep(0)
|
||||
_offloadevts[result[0]].set_result(result[1:])
|
||||
await asyncio.sleep(0)
|
||||
|
||||
|
||||
def _map_switch_backend(args):
|
||||
async def _map_switch_backend(args):
|
||||
"""Manipulate portions of mac address map relevant to a given switch
|
||||
"""
|
||||
|
||||
@@ -280,16 +288,15 @@ def _map_switch_backend(args):
|
||||
# fallback if ifName is empty
|
||||
#
|
||||
global _macmap
|
||||
if len(args) == 4:
|
||||
switch, password, user, _ = args # 4th arg is for affluent only
|
||||
if not user:
|
||||
user = None
|
||||
else:
|
||||
switch, password = args
|
||||
switch = args[0] if len(args) > 0 else None
|
||||
password = args[1] if len(args) > 1 else None
|
||||
user = args[2] if len(args) > 2 else None
|
||||
privprotocol = args[4] if len(args) > 4 else None
|
||||
if not user: # make '' be treated as None
|
||||
user = None
|
||||
if switch not in noaffluent:
|
||||
try:
|
||||
return _fast_map_switch(args)
|
||||
return await _fast_map_switch(args)
|
||||
except exc.PubkeyInvalid:
|
||||
log.log({'error': 'While trying to gather ethernet mac addresses '
|
||||
'from {0}, the TLS certificate failed validation. '
|
||||
@@ -297,8 +304,8 @@ def _map_switch_backend(args):
|
||||
'expected due to reinstall or new certificate'.format(switch)})
|
||||
except Exception as e:
|
||||
pass
|
||||
mactobridge, ifnamemap, bridgetoifmap = _offload_map_switch(
|
||||
switch, password, user)
|
||||
mactobridge, ifnamemap, bridgetoifmap = await _offload_map_switch(
|
||||
switch, password, user, privprotocol)
|
||||
maccounts = {}
|
||||
bridgetoifvalid = False
|
||||
for mac in mactobridge:
|
||||
@@ -367,9 +374,9 @@ def _map_switch_backend(args):
|
||||
_nodesbymac[mac] = (nodename, maccounts[ifname])
|
||||
_macsbyswitch[switch] = newmacs
|
||||
|
||||
def _snmp_map_switch_relay(rqid, switch, password, user):
|
||||
async def _snmp_map_switch_relay(rqid, switch, password, user, privprotocol=None):
|
||||
try:
|
||||
res = _snmp_map_switch(switch, password, user)
|
||||
res = await _snmp_map_switch(switch, password, user, privprotocol)
|
||||
payload = msgpack.packb((rqid,) + res, use_bin_type=True)
|
||||
try:
|
||||
sys.stdout.buffer.write(payload)
|
||||
@@ -383,6 +390,7 @@ def _snmp_map_switch_relay(rqid, switch, password, user):
|
||||
except AttributeError:
|
||||
sys.stdout.write(payload)
|
||||
except Exception as e:
|
||||
import traceback
|
||||
payload = msgpack.packb((rqid, 2, str(e)), use_bin_type=True)
|
||||
try:
|
||||
sys.stdout.buffer.write(payload)
|
||||
@@ -391,12 +399,12 @@ def _snmp_map_switch_relay(rqid, switch, password, user):
|
||||
finally:
|
||||
sys.stdout.flush()
|
||||
|
||||
def _snmp_map_switch(switch, password, user):
|
||||
async def _snmp_map_switch(switch, password, user, privprotocol=None):
|
||||
haveqbridge = False
|
||||
mactobridge = {}
|
||||
conn = snmp.Session(switch, password, user)
|
||||
ifnamemap = get_portnamemap(conn)
|
||||
for vb in conn.walk('1.3.6.1.2.1.17.7.1.2.2.1.2'):
|
||||
conn = snmp.Session(switch, password, user, privacy_protocol=privprotocol)
|
||||
ifnamemap = await get_portnamemap(conn)
|
||||
async for vb in conn.walk('1.3.6.1.2.1.17.7.1.2.2.1.2'):
|
||||
haveqbridge = True
|
||||
oid, bridgeport = vb
|
||||
if not bridgeport:
|
||||
@@ -408,7 +416,7 @@ def _snmp_map_switch(switch, password, user):
|
||||
)
|
||||
mactobridge[macaddr] = int(bridgeport)
|
||||
if not haveqbridge:
|
||||
for vb in conn.walk('1.3.6.1.2.1.17.4.3.1.2'):
|
||||
async for vb in conn.walk('1.3.6.1.2.1.17.4.3.1.2'):
|
||||
oid, bridgeport = vb
|
||||
if not bridgeport:
|
||||
continue
|
||||
@@ -420,15 +428,15 @@ def _snmp_map_switch(switch, password, user):
|
||||
vlanstocheck = set([])
|
||||
try:
|
||||
#ciscoiftovlanmap = {}
|
||||
for vb in conn.walk('.1.3.6.1.4.1.9.9.68.1.2.2.1.2'):
|
||||
async for vb in conn.walk('.1.3.6.1.4.1.9.9.68.1.2.2.1.2'):
|
||||
vlanstocheck.add(vb[1])
|
||||
#ciscotrunktovlanmap = {}
|
||||
for vb in conn.walk('.1.3.6.1.4.1.9.9.46.1.6.1.1.5'):
|
||||
async for vb in conn.walk('.1.3.6.1.4.1.9.9.46.1.6.1.1.5'):
|
||||
vlanstocheck.add(vb[1])
|
||||
except Exception:
|
||||
# We might have crashed snmp on a non-cisco switch
|
||||
# in such a case, delay 8 seconds to allow recovery to complete
|
||||
eventlet.sleep(8)
|
||||
await asyncio.sleep(8)
|
||||
if not vlanstocheck:
|
||||
vlanstocheck.add(None)
|
||||
bridgetoifmap = {}
|
||||
@@ -440,7 +448,7 @@ def _snmp_map_switch(switch, password, user):
|
||||
if not isinstance(password, str):
|
||||
password = password.decode('utf8')
|
||||
conn = snmp.Session(switch, '{}@{}'.format(password, vlan))
|
||||
for vb in conn.walk('1.3.6.1.2.1.17.1.4.1.2'):
|
||||
async for vb in conn.walk('1.3.6.1.2.1.17.1.4.1.2'):
|
||||
bridgeport, ifidx = vb
|
||||
bridgeport = int(str(bridgeport).rsplit('.', 1)[1])
|
||||
try:
|
||||
@@ -455,13 +463,13 @@ def _snmp_map_switch(switch, password, user):
|
||||
switchbackoff = 30
|
||||
|
||||
|
||||
def find_nodeinfo_by_mac(mac, configmanager):
|
||||
async def find_nodeinfo_by_mac(mac, configmanager):
|
||||
now = util.monotonic_time()
|
||||
if vintage and (now - vintage) < 90 and mac in _nodesbymac:
|
||||
return _nodesbymac[mac][0], {'maccount': _nodesbymac[mac][1]}
|
||||
# do not actually sweep switches more than once every 30 seconds
|
||||
# however, if there is an update in progress, wait on it
|
||||
for _ in update_macmap(configmanager,
|
||||
async for _ in update_macmap(configmanager,
|
||||
vintage and (now - vintage) < switchbackoff):
|
||||
if mac in _nodesbymac:
|
||||
return _nodesbymac[mac][0], {'maccount': _nodesbymac[mac][1]}
|
||||
@@ -471,10 +479,10 @@ def find_nodeinfo_by_mac(mac, configmanager):
|
||||
return None, {'maccount': 0}
|
||||
|
||||
|
||||
mapupdating = eventlet.semaphore.Semaphore()
|
||||
mapupdating = asyncio.Lock()
|
||||
|
||||
|
||||
def update_macmap(configmanager, impatient=False):
|
||||
async def update_macmap(configmanager, impatient=False):
|
||||
"""Interrogate switches to build/update mac table
|
||||
|
||||
Begin a rebuild process. This process is a generator that will yield
|
||||
@@ -484,28 +492,28 @@ def update_macmap(configmanager, impatient=False):
|
||||
"""
|
||||
if mapupdating.locked():
|
||||
while mapupdating.locked():
|
||||
eventlet.sleep(1)
|
||||
await asyncio.sleep(1)
|
||||
yield None
|
||||
return
|
||||
if impatient:
|
||||
return
|
||||
completions = _full_updatemacmap(configmanager)
|
||||
for completion in completions:
|
||||
async for completion in completions:
|
||||
try:
|
||||
yield completion
|
||||
except GeneratorExit:
|
||||
# the calling function has stopped caring, but we want to finish
|
||||
# the sweep, background it
|
||||
eventlet.spawn_n(_finish_update, completions)
|
||||
tasks.spawn(_finish_update(completions))
|
||||
raise
|
||||
|
||||
|
||||
def _finish_update(completions):
|
||||
for _ in completions:
|
||||
async def _finish_update(completions):
|
||||
async for _ in completions:
|
||||
pass
|
||||
|
||||
|
||||
def _full_updatemacmap(configmanager):
|
||||
async def _full_updatemacmap(configmanager):
|
||||
global vintage
|
||||
global _apimacmap
|
||||
global _macmap
|
||||
@@ -514,7 +522,7 @@ def _full_updatemacmap(configmanager):
|
||||
global _macsbyswitch
|
||||
global switchbackoff
|
||||
start = util.monotonic_time()
|
||||
with mapupdating:
|
||||
async with mapupdating:
|
||||
vintage = util.monotonic_time()
|
||||
# Clear all existing entries
|
||||
_macmap = {}
|
||||
@@ -579,10 +587,12 @@ def _full_updatemacmap(configmanager):
|
||||
if switch not in switches:
|
||||
del _macsbyswitch[switch]
|
||||
switchauth = get_switchcreds(configmanager, switches)
|
||||
pool = GreenPool(64)
|
||||
for ans in pool.imap(_map_switch, switchauth):
|
||||
vintage = util.monotonic_time()
|
||||
yield ans
|
||||
#pool = GreenPool(64)
|
||||
tsks = []
|
||||
for sa in switchauth:
|
||||
tsks.append(_map_switch(sa))
|
||||
for tsk in asyncio.as_completed(tsks):
|
||||
yield await tsk
|
||||
_apimacmap = _macmap
|
||||
endtime = util.monotonic_time()
|
||||
duration = endtime - start
|
||||
@@ -599,20 +609,20 @@ def _dump_locations(info, macaddr, nodename=None):
|
||||
portinfo = []
|
||||
for location in info:
|
||||
portinfo.append({'switch': location[0],
|
||||
'port': location[1], 'macsonport': location[2]})
|
||||
'port': location[1], 'macsonport': location[2]})
|
||||
retdata['ports'] = sorted(portinfo, key=lambda x: x['macsonport'],
|
||||
reverse=True)
|
||||
yield msg.KeyValueData(retdata)
|
||||
|
||||
|
||||
def handle_api_request(configmanager, inputdata, operation, pathcomponents):
|
||||
async def handle_api_request(configmanager, inputdata, operation, pathcomponents):
|
||||
if operation == 'retrieve':
|
||||
return handle_read_api_request(pathcomponents, configmanager)
|
||||
return await handle_read_api_request(pathcomponents, configmanager)
|
||||
if (operation in ('update', 'create') and
|
||||
pathcomponents == ['networking', 'macs', 'rescan']):
|
||||
if inputdata != {'rescan': 'start'}:
|
||||
raise exc.InvalidArgumentException('Input must be rescan=start')
|
||||
eventlet.spawn_n(rescan, configmanager)
|
||||
tasks.spawn(rescan(configmanager))
|
||||
return [msg.KeyValueData({'rescan': 'started'})]
|
||||
raise exc.NotImplementedException(
|
||||
'Operation {0} on {1} not implemented'.format(
|
||||
@@ -630,7 +640,7 @@ def get_node_fingerprints(nodename, configmanager):
|
||||
_namesmatch)
|
||||
|
||||
|
||||
def handle_read_api_request(pathcomponents, configmanager):
|
||||
async def handle_read_api_request(pathcomponents, configmanager):
|
||||
# TODO(jjohnson2): discovery core.py api handler design, apply it here
|
||||
# to make this a less tangled mess as it gets extended
|
||||
if len(pathcomponents) == 1:
|
||||
@@ -641,7 +651,7 @@ def handle_read_api_request(pathcomponents, configmanager):
|
||||
return [msg.ChildCollection(x + '/')
|
||||
for x in list_switches(configmanager)]
|
||||
else:
|
||||
return _handle_neighbor_query(pathcomponents[2:], configmanager)
|
||||
return await _handle_neighbor_query(pathcomponents[2:], configmanager)
|
||||
elif len(pathcomponents) == 2:
|
||||
if pathcomponents[-1] == 'macs':
|
||||
return [msg.ChildCollection(x) for x in (# 'by-node/',
|
||||
@@ -726,31 +736,35 @@ def dump_macinfo(macaddr):
|
||||
return _dump_locations(info, macaddr, _nodesbymac.get(macaddr, (None,))[0])
|
||||
|
||||
|
||||
def rescan(cfg):
|
||||
for _ in update_macmap(cfg):
|
||||
async def rescan(cfg):
|
||||
async for _ in update_macmap(cfg):
|
||||
pass
|
||||
|
||||
async def get_stdin_reader():
|
||||
cloop = asyncio.get_event_loop()
|
||||
reader = asyncio.StreamReader()
|
||||
protocol = asyncio.StreamReaderProtocol(reader)
|
||||
await cloop.connect_read_pipe(lambda: protocol, sys.stdin)
|
||||
return reader
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) > 1 and sys.argv[1] == '-o':
|
||||
try:
|
||||
upacker = msgpack.Unpacker(encoding='utf8')
|
||||
except TypeError:
|
||||
upacker = msgpack.Unpacker(raw=False, strict_map_key=False)
|
||||
currfl = fcntl.fcntl(sys.stdin.fileno(), fcntl.F_GETFL)
|
||||
fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, currfl | os.O_NONBLOCK)
|
||||
async def offloader_main():
|
||||
try:
|
||||
upacker = msgpack.Unpacker(encoding='utf8')
|
||||
except TypeError:
|
||||
upacker = msgpack.Unpacker(raw=False, strict_map_key=False)
|
||||
#currfl = fcntl.fcntl(sys.stdin.fileno(), fcntl.F_GETFL)
|
||||
#fcntl.fcntl(sys.stdin.fileno(), fcntl.F_SETFL, currfl | os.O_NONBLOCK)
|
||||
sreader = await get_stdin_reader()
|
||||
while True:
|
||||
data = await sreader.read(512)
|
||||
upacker.feed(data)
|
||||
for cmd in upacker:
|
||||
tasks.spawn(_snmp_map_switch_relay(*cmd))
|
||||
sys.exit(0)
|
||||
|
||||
while True:
|
||||
r = select.select([sys.stdin], [], [])
|
||||
try:
|
||||
upacker.feed(sys.stdin.buffer.read())
|
||||
except AttributeError:
|
||||
upacker.feed(sys.stdin.read())
|
||||
for cmd in upacker:
|
||||
eventlet.spawn_n(_snmp_map_switch_relay, *cmd)
|
||||
sys.exit(0)
|
||||
async def test_main():
|
||||
cg = cfm.ConfigManager(None)
|
||||
for res in update_macmap(cg):
|
||||
async for res in update_macmap(cg):
|
||||
print("map has updated")
|
||||
if len(sys.argv) > 1:
|
||||
print(repr(_macmap[sys.argv[1]]))
|
||||
@@ -762,3 +776,9 @@ if __name__ == '__main__':
|
||||
print(repr(_macmap))
|
||||
print("switch to fdb lookup table: -------------------")
|
||||
print(repr(_macsbyswitch))
|
||||
|
||||
if __name__ == '__main__':
|
||||
if len(sys.argv) > 1 and sys.argv[1] == '-o':
|
||||
asyncio.run(offloader_main())
|
||||
sys.exit(0)
|
||||
asyncio.run(test_main())
|
||||
|
||||
@@ -21,7 +21,7 @@ import confluent.collective.manager as collective
|
||||
def get_switchcreds(configmanager, switches):
|
||||
switchcfg = configmanager.get_node_attributes(
|
||||
switches, ('secret.hardwaremanagementuser', 'secret.snmpcommunity',
|
||||
'secret.hardwaremanagementpassword',
|
||||
'secret.hardwaremanagementpassword', 'snmp.privacyprotocol',
|
||||
'collective.managercandidates'), decrypt=True)
|
||||
switchauth = []
|
||||
for switch in switches:
|
||||
@@ -39,6 +39,7 @@ def get_switchcreds(configmanager, switches):
|
||||
user = None
|
||||
password = switchparms.get(
|
||||
'secret.snmpcommunity', {}).get('value', None)
|
||||
privacy_protocol = None
|
||||
if not password:
|
||||
password = switchparms.get(
|
||||
'secret.hardwaremanagementpassword', {}).get('value',
|
||||
@@ -47,7 +48,9 @@ def get_switchcreds(configmanager, switches):
|
||||
'secret.hardwaremanagementuser', {}).get('value', None)
|
||||
if not user:
|
||||
user = None
|
||||
switchauth.append((switch, password, user, configmanager))
|
||||
privacy_protocol = switchparms.get(
|
||||
'snmp.privacyprotocol', {}).get('value', None)
|
||||
switchauth.append((switch, password, user, configmanager, privacy_protocol))
|
||||
return switchauth
|
||||
|
||||
|
||||
@@ -69,10 +72,10 @@ def list_switches(configmanager):
|
||||
return util.natural_sort(switches)
|
||||
|
||||
|
||||
def get_portnamemap(conn):
|
||||
async def get_portnamemap(conn):
|
||||
ifnamemap = {}
|
||||
havenames = False
|
||||
for vb in conn.walk('1.3.6.1.2.1.31.1.1.1.1'):
|
||||
async for vb in conn.walk('1.3.6.1.2.1.31.1.1.1.1'):
|
||||
ifidx, ifname = vb
|
||||
if not ifname:
|
||||
continue
|
||||
@@ -80,7 +83,7 @@ def get_portnamemap(conn):
|
||||
ifidx = int(str(ifidx).rsplit('.', 1)[1])
|
||||
ifnamemap[ifidx] = str(ifname)
|
||||
if not havenames:
|
||||
for vb in conn.walk('1.3.6.1.2.1.2.2.1.2'):
|
||||
async for vb in conn.walk('1.3.6.1.2.1.2.2.1.2'):
|
||||
ifidx, ifname = vb
|
||||
ifidx = int(str(ifidx).rsplit('.', 1)[1])
|
||||
ifnamemap[ifidx] = str(ifname)
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
|
||||
import confluent.util as util
|
||||
import time
|
||||
import eventlet
|
||||
webclient = eventlet.import_patched('pyghmi.util.webclient')
|
||||
import aiohmi.util.webclient as webclient
|
||||
|
||||
|
||||
|
||||
_healthmap = {
|
||||
'normal': 'ok',
|
||||
@@ -87,40 +88,42 @@ class NxApiClient:
|
||||
self.password = self.password.decode()
|
||||
except Exception:
|
||||
pass
|
||||
self.wc = webclient.SecureHTTPConnection(switch, port=443, verifycallback=cv)
|
||||
self.login()
|
||||
self.wc = webclient.WebConnection(switch, port=443, verifycallback=cv)
|
||||
self.logged = False
|
||||
|
||||
def login(self):
|
||||
async def login(self):
|
||||
payload = {'aaaUser':
|
||||
{'attributes':
|
||||
{'name': self.user,
|
||||
'pwd': self.password}}}
|
||||
rsp = self.wc.grab_json_response_with_status('/api/mo/aaaLogin.json', payload)
|
||||
rsp = await self.wc.grab_json_response_with_status('/api/mo/aaaLogin.json', payload)
|
||||
if rsp[1] != 200:
|
||||
raise Exception("Failed authenticating")
|
||||
rsp = rsp[0]
|
||||
self.authtoken = rsp['imdata'][0]['aaaLogin']['attributes']['token']
|
||||
self.wc.cookies['Apic-Cookie'] = self.authtoken
|
||||
self.wc.cookies.update_cookies({'APIC-cookie': self.authtoken})
|
||||
self.logged = True
|
||||
|
||||
def get_firmware(self):
|
||||
async def get_firmware(self):
|
||||
firmdata = {}
|
||||
for imdata in self.grab_imdata('/api/mo/sys/showversion.json'):
|
||||
async for imdata in self.grab_imdata('/api/mo/sys/showversion.json'):
|
||||
attrs = imdata['sysmgrShowVersion']['attributes']
|
||||
firmdata['NX-OS'] = {'version': attrs['nxosVersion'], 'date': attrs['nxosCompileTime']}
|
||||
firmdata['BIOS'] = {'version': attrs['biosVersion'], 'date': attrs['biosCompileTime']}
|
||||
return firmdata
|
||||
|
||||
def get_sensors(self):
|
||||
async def get_sensors(self):
|
||||
sensedata = []
|
||||
for imdata in self.grab_imdata('/api/mo/sys/ch.json?rsp-subtree=full'):
|
||||
async for imdata in self.grab_imdata('/api/mo/sys/ch.json?rsp-subtree=full'):
|
||||
hwinfo = imdata['eqptCh']['children']
|
||||
for component in hwinfo:
|
||||
add_sensedata(component, sensedata)
|
||||
return sensedata
|
||||
for sd in sensedata:
|
||||
yield sd
|
||||
|
||||
def get_health(self):
|
||||
async def get_health(self):
|
||||
healthdata = {'health': 'ok', 'sensors': []}
|
||||
for sensor in self.get_sensors():
|
||||
async for sensor in self.get_sensors():
|
||||
currhealth = sensor.get('health', 'ok')
|
||||
if currhealth != 'ok':
|
||||
healthdata['sensors'].append(sensor)
|
||||
@@ -130,9 +133,9 @@ class NxApiClient:
|
||||
healthdata['health'] = 'warning'
|
||||
return healthdata
|
||||
|
||||
def get_inventory(self):
|
||||
async def get_inventory(self):
|
||||
invdata = []
|
||||
for imdata in self.grab_imdata('/api/mo/sys/ch.json?rsp-subtree=full'):
|
||||
async for imdata in self.grab_imdata('/api/mo/sys/ch.json?rsp-subtree=full'):
|
||||
hwinfo = imdata['eqptCh']
|
||||
chattr = hwinfo['attributes']
|
||||
invinfo = {'name': 'System', 'present': True}
|
||||
@@ -160,30 +163,32 @@ class NxApiClient:
|
||||
invdata.append(invinfo)
|
||||
return invdata
|
||||
|
||||
def grab(self, url, cache=True, retry=True):
|
||||
async def grab(self, url, cache=True, retry=True):
|
||||
if not self.logged:
|
||||
await self.login()
|
||||
if cache is True:
|
||||
cache = 1
|
||||
if cache:
|
||||
if url in self.cachedurls:
|
||||
if self.cachedurls[url][1] > time.monotonic() - cache:
|
||||
return self.cachedurls[url][0]
|
||||
rsp = self.wc.grab_json_response_with_status(url)
|
||||
rsp = await self.wc.grab_json_response_with_status(url)
|
||||
if rsp[1] == 403 and retry:
|
||||
self.login()
|
||||
return self.grab(url, cache, False)
|
||||
await self.login()
|
||||
return await self.grab(url, cache, False)
|
||||
if rsp[1] != 200:
|
||||
raise Exception("Error making request")
|
||||
self.cachedurls[url] = rsp[0], time.monotonic()
|
||||
return rsp[0]
|
||||
|
||||
def grab_imdata(self, url):
|
||||
response = self.grab(url)
|
||||
async def grab_imdata(self, url):
|
||||
response = await self.grab(url)
|
||||
for imdata in response['imdata']:
|
||||
yield imdata
|
||||
|
||||
def get_mac_table(self):
|
||||
async def get_mac_table(self):
|
||||
macdict = {}
|
||||
for macinfo in self.grab_imdata('/api/mo/sys/mac/table.json?rsp-subtree=full'):
|
||||
async for macinfo in self.grab_imdata('/api/mo/sys/mac/table.json?rsp-subtree=full'):
|
||||
mactable = macinfo['l2MacAddressTable']['children']
|
||||
for macent in mactable:
|
||||
mace = macent['l2MacAddressEntry']['attributes']
|
||||
@@ -195,9 +200,9 @@ class NxApiClient:
|
||||
return macdict
|
||||
|
||||
|
||||
def get_lldp(self):
|
||||
async def get_lldp(self):
|
||||
lldpbyport = {}
|
||||
for lldpimdata in self.grab_imdata('/api/mo/sys/lldp/inst.json?rsp-subtree=full'):
|
||||
async for lldpimdata in self.grab_imdata('/api/mo/sys/lldp/inst.json?rsp-subtree=full'):
|
||||
lldpdata = lldpimdata['lldpInst']['children']
|
||||
for lldpinfo in lldpdata:
|
||||
if 'lldpIf' not in lldpinfo:
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user