diff --git a/confluent_client/bin/confetty b/confluent_client/bin/confetty index 02690e68..86ef1aea 100755 --- a/confluent_client/bin/confetty +++ b/confluent_client/bin/confetty @@ -763,7 +763,10 @@ def conserver_command(filehandle, localcommand): def get_command_bytes(filehandle, localcommand, cmdlen): while len(localcommand) < cmdlen: - ready, _, _ = select.select((filehandle,), (), (), 1) + try: + ready, _, _ = select.select((filehandle,), (), (), 1) + except select.error: + ready = () if ready: localcommand += filehandle.read() return localcommand @@ -776,7 +779,10 @@ def check_escape_seq(currinput, filehandle): sys.stdout.flush() return conserver_command( filehandle, currinput[len(conserversequence):]) - ready, _, _ = select.select((filehandle,), (), (), 3) + try: + ready, _, _ = select.select((filehandle,), (), (), 3) + except select.error: + ready = () if not ready: # 3 seconds of no typing break currinput += filehandle.read() @@ -866,8 +872,11 @@ def check_power_state(): while inconsole or not doexit: if inconsole: - rdylist, _, _ = select.select( - (sys.stdin, session.connection), (), (), 10) + try: + rdylist, _, _ = select.select( + (sys.stdin, session.connection), (), (), 10) + except select.error: + rdylist = () for fh in rdylist: if fh == session.connection: # this only should get called in the diff --git a/confluent_client/bin/nodeconfig b/confluent_client/bin/nodeconfig index 6055e315..78f46680 100755 --- a/confluent_client/bin/nodeconfig +++ b/confluent_client/bin/nodeconfig @@ -67,6 +67,8 @@ cfgpaths = { 'bmc.ipv4_gateway': ( 'configuration/management_controller/net_interfaces/management', 'ipv4_gateway'), + 'bmc.hostname': ( + 'configuration/management_controller/hostname', 'hostname'), } autodeps = { diff --git a/confluent_client/bin/nodeshell b/confluent_client/bin/nodeshell index 6db57f6a..d090e0ba 100755 --- a/confluent_client/bin/nodeshell +++ b/confluent_client/bin/nodeshell @@ -86,7 +86,7 @@ def run(): desc = pipedesc[r] node = desc['node'] data = True - while data and select.select([r], [], [], 0): + while data and select.select([r], [], [], 0)[0]: data = r.readline() if data: if desc['type'] == 'stdout': diff --git a/confluent_client/confluent/__init__.py b/confluent_client/confluent/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/confluent_server/confluent/__init__.py b/confluent_server/confluent/__init__.py deleted file mode 100644 index 07e5b37e..00000000 --- a/confluent_server/confluent/__init__.py +++ /dev/null @@ -1 +0,0 @@ -__version__ = "1.5.0.dev395.ggacb3a20" diff --git a/confluent_server/confluent/config/configmanager.py b/confluent_server/confluent/config/configmanager.py index 7bad6802..9d2087af 100644 --- a/confluent_server/confluent/config/configmanager.py +++ b/confluent_server/confluent/config/configmanager.py @@ -1684,19 +1684,33 @@ def restore_db_from_directory(location, password): if e.errno == 2: raise Exception("Cannot restore without keys, this may be a " "redacted dump") + try: + moreglobals = json.load(open(os.path.join(location, 'globals.json'))) + for globvar in moreglobals: + set_global(globvar, moreglobals[globvar]) + except IOError as e: + if e.errno != 2: + raise with open(os.path.join(location, 'main.json'), 'r') as cfgfile: cfgdata = cfgfile.read() ConfigManager(tenant=None)._load_from_json(cfgdata) -def dump_db_to_directory(location, password, redact=None): - if not redact: +def dump_db_to_directory(location, password, redact=None, skipkeys=False): + if not redact and not skipkeys: with open(os.path.join(location, 'keys.json'), 'w') as cfgfile: cfgfile.write(_dump_keys(password)) cfgfile.write('\n') with open(os.path.join(location, 'main.json'), 'w') as cfgfile: cfgfile.write(ConfigManager(tenant=None)._dump_to_json(redact=redact)) cfgfile.write('\n') + bkupglobals = {} + for globvar in _cfgstore['globals']: + if globvar.endswith('_key'): + continue + bkupglobals[globvar] = _cfgstore['globals'][globvar] + if bkupglobals: + json.dump(bkupglobals, open(os.path.join(location, 'globals.json'))) try: for tenant in os.listdir( os.path.join(ConfigManager._cfgdir, '/tenants/')): diff --git a/confluent_server/confluent/core.py b/confluent_server/confluent/core.py index 88db962d..2cd5e11f 100644 --- a/confluent_server/confluent/core.py +++ b/confluent_server/confluent/core.py @@ -154,6 +154,10 @@ def _init_core(): 'pluginattrs': ['hardwaremanagement.method'], 'default': 'ipmi', }), + 'hostname': PluginRoute({ + 'pluginattrs': ['hardwaremanagement.method'], + 'default': 'ipmi', + }), 'identifier': PluginRoute({ 'pluginattrs': ['hardwaremanagement.method'], 'default': 'ipmi', diff --git a/confluent_server/confluent/discovery/core.py b/confluent_server/confluent/discovery/core.py index 0b7523a2..1cc31a9c 100644 --- a/confluent_server/confluent/discovery/core.py +++ b/confluent_server/confluent/discovery/core.py @@ -85,6 +85,7 @@ import eventlet import eventlet.greenpool import eventlet.semaphore +autosensors = set() class nesteddict(dict): def __missing__(self, key): @@ -350,7 +351,28 @@ def _parameterize_path(pathcomponents): return validselectors, keyparams, listrequested, childcoll +def handle_autosense_config(operation, inputdata): + autosense = cfm.get_global('discovery.autosense') + autosense = autosense or autosense is None + if operation == 'retrieve': + yield msg.KeyValueData({'enabled': autosense}) + elif operation == 'update': + enabled = inputdata['enabled'] + if type(enabled) in (unicode, str): + enabled = enabled.lower() in ('true', '1', 'y', 'yes', 'enable', + 'enabled') + if autosense == enabled: + return + cfm.set_global('discovery.autosense', enabled) + if enabled: + start_autosense() + else: + stop_autosense() + + def handle_api_request(configmanager, inputdata, operation, pathcomponents): + if pathcomponents == ['discovery', 'autosense']: + return handle_autosense_config(operation, inputdata) if operation == 'retrieve': return handle_read_api_request(pathcomponents) elif (operation in ('update', 'create') and @@ -398,6 +420,7 @@ def handle_read_api_request(pathcomponents): if len(pathcomponents) == 1: dirlist = [msg.ChildCollection(x + '/') for x in sorted(list(subcats))] dirlist.append(msg.ChildCollection('rescan')) + dirlist.append(msg.ChildCollection('autosense')) return dirlist if not coll: return show_info(queryparms['by-mac']) @@ -695,6 +718,9 @@ def get_chained_smm_name(nodename, cfg, handler, nl=None, checkswitch=True): smmaddr = cd[nodename]['hardwaremanagement.manager']['value'] pkey = cd[nodename].get('pubkeys.tls_hardwaremanager', {}).get( 'value', None) + if not pkey: + # We cannot continue through a break in the chain + return None, False if pkey: cv = util.TLSCertVerifier( cfg, nodename, 'pubkeys.tls_hardwaremanager').verify_cert @@ -1075,12 +1101,14 @@ def newnodes(added, deleting, configmanager): global attribwatcher global needaddhandled global nodeaddhandler + _map_unique_ids() configmanager.remove_watcher(attribwatcher) allnodes = configmanager.list_nodes() attribwatcher = configmanager.watch_attributes( allnodes, ('discovery.policy', 'net*.switch', - 'hardwaremanagement.manager', 'net*.switchport', 'id.uuid', - 'pubkeys.tls_hardwaremanager', 'net*.bootable'), _recheck_nodes) + 'hardwaremanagement.manager', 'net*.switchport', + 'id.uuid', 'pubkeys.tls_hardwaremanager', + 'net*.bootable'), _recheck_nodes) if nodeaddhandler: needaddhandled = True else: @@ -1126,14 +1154,23 @@ def start_detection(): 'hardwaremanagement.manager', 'net*.switchport', 'id.uuid', 'pubkeys.tls_hardwaremanager'), _recheck_nodes) cfg.watch_nodecollection(newnodes) - eventlet.spawn_n(slp.snoop, safe_detected) - eventlet.spawn_n(pxe.snoop, safe_detected) + autosense = cfm.get_global('discovery.autosense') + if autosense or autosense is None: + 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) +def stop_autosense(): + for watcher in list(autosensors): + watcher.kill() + autosensors.discard(watcher) + +def start_autosense(): + autosensors.add(eventlet.spawn(slp.snoop, safe_detected)) + autosensors.add(eventlet.spawn(pxe.snoop, safe_detected)) nodes_by_fprint = {} diff --git a/confluent_server/confluent/discovery/protocols/ssdp.py b/confluent_server/confluent/discovery/protocols/ssdp.py index de543697..2b03f28a 100644 --- a/confluent_server/confluent/discovery/protocols/ssdp.py +++ b/confluent_server/confluent/discovery/protocols/ssdp.py @@ -191,7 +191,6 @@ def _parse_ssdp(peer, rsp, peerdata): _, code, _ = headlines[0].split(' ', 2) except ValueError: return - myurl = None if code == '200': if nid in peerdata: peerdatum = peerdata[nid] @@ -208,7 +207,6 @@ def _parse_ssdp(peer, rsp, peerdata): header = header.strip() value = value.strip() if header == 'AL' or header == 'LOCATION': - myurl = value if 'urls' not in peerdatum: peerdatum['urls'] = [value] elif value not in peerdatum['urls']: diff --git a/confluent_server/confluent/messages.py b/confluent_server/confluent/messages.py index 13331f11..fb40ad79 100644 --- a/confluent_server/confluent/messages.py +++ b/confluent_server/confluent/messages.py @@ -397,6 +397,9 @@ def get_input_message(path, operation, inputdata, nodes=None, multinode=False, elif (path[:3] == ['configuration', 'management_controller', 'identifier'] and operation != 'retrieve'): return InputMCI(path, nodes, inputdata) + elif (path[:3] == ['configuration', 'management_controller', 'hostname'] + and operation != 'retrieve'): + return InputHostname(path, nodes, inputdata, configmanager) elif (path[:4] == ['configuration', 'management_controller', 'net_interfaces', 'management'] and operation != 'retrieve'): return InputNetworkConfiguration(path, nodes, inputdata, @@ -719,6 +722,25 @@ class InputBMCReset(ConfluentInputMessage): return self.inputbynode[node] + +class InputHostname(ConfluentInputMessage): + def __init__(self, path, nodes, inputdata, configmanager): + self.inputbynode = {} + self.stripped = False + if not inputdata or 'hostname' not in inputdata: + raise exc.InvalidArgumentException('missing hostname attribute') + if nodes is None: + raise exc.InvalidArgumentException( + 'This only supports per-node input') + for expanded in configmanager.expand_attrib_expression( + nodes, inputdata['hostname']): + node, value = expanded + self.inputbynode[node] = value + + def hostname(self, node): + return self.inputbynode[node] + + class InputMCI(ConfluentInputMessage): def __init__(self, path, nodes, inputdata): self.inputbynode = {} @@ -1298,6 +1320,17 @@ class MCI(ConfluentMessage): self.kvpairs = {name: kv} +class Hostname(ConfluentMessage): + def __init__(self, name=None, hostname=None): + self.notnode = name is None + self.desc = 'BMC hostname' + + kv = {'hostname': {'value': hostname}} + if self.notnode: + self.kvpairs = kv + else: + self.kvpairs = {name: kv} + class DomainName(ConfluentMessage): def __init__(self, name=None, dn=None): self.notnode = name is None diff --git a/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py b/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py index eff94678..ae93d8e9 100644 --- a/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py +++ b/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py @@ -344,7 +344,7 @@ def perform_request(operator, node, element, cfg, results).handle_request() except pygexc.IpmiException as ipmiexc: excmsg = str(ipmiexc) - if excmsg == 'Session no longer connected': + if excmsg in ('Session no longer connected', 'timeout'): results.put(msg.ConfluentTargetTimeout(node)) else: results.put(msg.ConfluentNodeError(node, excmsg)) @@ -518,6 +518,8 @@ class IpmiHandler(object): return self.handle_reset() elif self.element[1:3] == ['management_controller', 'identifier']: return self.handle_identifier() + elif self.element[1:3] == ['management_controller', 'hostname']: + return self.handle_hostname() elif self.element[1:3] == ['management_controller', 'domain_name']: return self.handle_domain_name() elif self.element[1:3] == ['management_controller', 'ntp']: @@ -986,6 +988,16 @@ class IpmiHandler(object): self.ipmicmd.set_mci(mci) return + def handle_hostname(self): + if 'read' == self.op: + hostname = self.ipmicmd.get_hostname() + self.output.put(msg.Hostname(self.node, hostname)) + return + elif 'update' == self.op: + hostname = self.inputdata.hostname(self.node) + self.ipmicmd.set_hostname(hostname) + return + def handle_domain_name(self): if 'read' == self.op: dn = self.ipmicmd.get_domain_name() diff --git a/confluent_server/confluent_server.spec.tmpl b/confluent_server/confluent_server.spec.tmpl index 55e307cc..e93922d7 100644 --- a/confluent_server/confluent_server.spec.tmpl +++ b/confluent_server/confluent_server.spec.tmpl @@ -12,7 +12,7 @@ Group: Development/Libraries BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot Prefix: %{_prefix} BuildArch: noarch -Requires: python-pyghmi >= 1.0.34, python-eventlet, python-greenlet, python-crypto >= 2.6.1, confluent_client, pyparsing, python-paramiko, python-dns, python-netifaces, python2-pyasn1 >= 0.2.3, python-pysnmp >= 4.3.4, python-pyte, python-lxml, python-eficompressor +Requires: python-pyghmi >= 1.0.34, python-eventlet, python-greenlet, python-crypto >= 2.6.1, confluent_client, python-pyparsing, python-paramiko, python-dns, python-netifaces, python2-pyasn1 >= 0.2.3, python-pysnmp >= 4.3.4, python-pyte, python-lxml, python-eficompressor Vendor: Jarrod Johnson Url: http://xcat.sf.net/ diff --git a/confluent_server/makesetup b/confluent_server/makesetup index 26bb712a..44e7293f 100755 --- a/confluent_server/makesetup +++ b/confluent_server/makesetup @@ -6,4 +6,6 @@ if [ "$NUMCOMMITS" != "$VERSION" ]; then fi echo $VERSION > VERSION sed -e "s/#VERSION#/$VERSION/" setup.py.tmpl > setup.py -echo '__version__ = "'$VERSION'"' > confluent/__init__.py +if [ -f confluent/client.py ]; then + echo '__version__ = "'$VERSION'"' > confluent/__init__.py +fi diff --git a/confluent_server/setup.py.tmpl b/confluent_server/setup.py.tmpl index 85ecb57e..94b010f0 100644 --- a/confluent_server/setup.py.tmpl +++ b/confluent_server/setup.py.tmpl @@ -17,7 +17,8 @@ setup( 'confluent/plugins/shell/', 'confluent/plugins/configuration/'], install_requires=['paramiko', 'pycrypto>=2.6', 'confluent_client>=0.1.0', 'eventlet', - 'pyghmi>=0.6.5'], + 'dnspython', 'netifaces', 'pyte', 'pysnmp', 'pyparsing', + 'pyghmi>=1.0.44'], scripts=['bin/confluent', 'bin/confluentdbutil'], data_files=[('/etc/init.d', ['sysvinit/confluent']), ('/usr/lib/sysctl.d', ['sysctl/confluent.conf']),