diff --git a/confluent_server/aiohmi/ipmi/console.py b/confluent_server/aiohmi/ipmi/console.py index ab463eb5..4fdb49f0 100644 --- a/confluent_server/aiohmi/ipmi/console.py +++ b/confluent_server/aiohmi/ipmi/console.py @@ -148,7 +148,11 @@ class Console(object): # some BMCs disagree on the endianness, so do both valid_ports = (self.port, struct.unpack( 'H', self.port))[0]) - if (data[8] + (data[9] << 8)) not in valid_ports: + solport = data[8] + (data[9] << 8) + # A bmc behind a port forward answers with the port it listens on + # rather than the one it was reached through; payloads ride the + # session, never the advertised port. + if solport not in valid_ports and self.port == 623: # TODO(jbjohnso): support atypical SOL port number raise NotImplementedError("Non-standard SOL Port Number") # ignore data[10:11] for now, the vlan detail, shouldn't matter to this diff --git a/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py b/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py index 6cde2fed..2e832978 100644 --- a/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py +++ b/confluent_server/confluent/plugins/hardwaremanagement/ipmi.py @@ -263,14 +263,37 @@ def get_conn_params(node, configdata): kg = configdata['secret.ipmikg']['value'] else: kg = passphrase - # TODO(jbjohnso): check if the end has some number after a : without [] - # for non default port + # Read a port off the address, as the redfish plugin does. Brackets come + # off, unlike there: this address goes to socket.getaddrinfo, which rejects + # a bracketed literal, rather than into a URL where one is required. + bmc = bmc.strip() + port = 623 + if bmc.startswith('['): + bracket_end = bmc.find(']') + if bracket_end > 0: + if len(bmc) > bracket_end + 1 and bmc[bracket_end + 1] == ':': + try: + port = int(bmc[bracket_end + 2:]) + except (ValueError, TypeError): + pass + bmc = bmc[1:bracket_end] + elif bmc.count(':') == 1: + hostpart, _, portstr = bmc.rpartition(':') + try: + port = int(portstr) + except (ValueError, TypeError): + pass + bmc = hostpart + if not 0 < port <= 65535: + # NOTE: Fallback to 623 if port read is not valid + port = 623 + return { 'username': username, 'passphrase': passphrase, 'kg': kg, 'bmc': bmc, - 'port': 623, + 'port': port, } @@ -288,6 +311,11 @@ def _donothing(data): class IpmiConsole(conapi.Console): configattributes = frozenset(_configattributes) bmctonodemapping = {} + # Whether this instance is the one that put its endpoint in the mapping + # above. False until it does, so that an instance rejected as a duplicate, + # or one that failed earlier in __init__, does not remove on the way out an + # entry that belongs to the node holding the endpoint. + claimedbmc = False def __init__(self, node, config): self.error = None @@ -304,20 +332,26 @@ class IpmiConsole(conapi.Console): self.kg = connparams['kg'] self.bmc = connparams['bmc'] self.port = connparams['port'] + # The port is part of the identity: several bmcs may sit behind one + # address on different ports, and they are distinct devices. + self.bmckey = (self.bmc, self.port) self.connected = False - # ok, is self.bmc unique among nodes already + # ok, is this bmc unique among nodes already # Cannot actually create console until 'connect', when we get callback - if (self.bmc in self.bmctonodemapping and - self.bmctonodemapping[self.bmc] != node): + if (self.bmckey in self.bmctonodemapping and + self.bmctonodemapping[self.bmckey] != node): raise Exception( "Duplicate hardwaremanagement.manager attribute for {0} and {1}".format( - node, self.bmctonodemapping[self.bmc])) - self.bmctonodemapping[self.bmc] = node + node, self.bmctonodemapping[self.bmckey])) + self.bmctonodemapping[self.bmckey] = node + self.claimedbmc = True def __del__(self): self.solconnection = None + if not self.claimedbmc: + return try: - del self.bmctonodemapping[self.bmc] + del self.bmctonodemapping[self.bmckey] except KeyError: pass diff --git a/confluent_server/confluent/sockapi.py b/confluent_server/confluent/sockapi.py index b3bcc289..d02bcce4 100644 --- a/confluent_server/confluent/sockapi.py +++ b/confluent_server/confluent/sockapi.py @@ -438,31 +438,40 @@ async def _tlsstartup(cnn): raise Exception('Unable to find workable SSL support') tasks.spawn(sessionhdl(cnn, authname, cert=cert)) -def removesocket(): +default_socketpath = "/var/run/confluent/api.sock" + + +def removesocket(socketpath=None): + if socketpath is None: + socketpath = default_socketpath try: - os.remove("/var/run/confluent/api.sock") + os.remove(socketpath) except OSError: pass -async def _unixdomainhandler(bind_group=None, bind_perms=None): +async def _unixdomainhandler(bind_group=None, bind_perms=None, + socketpath=None): aloop = asyncio.get_running_loop() if not bind_perms: bind_perms = 0o666 + if socketpath is None: + socketpath = default_socketpath unixsocket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) unixsocket.settimeout(0) try: - os.remove("/var/run/confluent/api.sock") + os.remove(socketpath) 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) + socketdir = os.path.dirname(socketpath) + if socketdir and not os.path.isdir(socketdir): + os.makedirs(socketdir, 0o755) oldumask = os.umask(0o777 - bind_perms) - unixsocket.bind("/var/run/confluent/api.sock") - os.chmod("/var/run/confluent/api.sock", bind_perms) + unixsocket.bind(socketpath) + os.chmod(socketpath, bind_perms) if bind_group: - shutil.chown("/var/run/confluent/api.sock", group=bind_group) + shutil.chown(socketpath, group=bind_group) os.umask(oldumask) - atexit.register(removesocket) + atexit.register(removesocket, socketpath) unixsocket.listen(5) while True: cnn, addr = await aloop.sock_accept(unixsocket) @@ -491,13 +500,15 @@ async def _unixdomainhandler(bind_group=None, bind_perms=None): class SockApi(object): - def __init__(self, bindhost=None, bindport=None, bindgroup=None, bindperms=None): + def __init__(self, bindhost=None, bindport=None, bindgroup=None, + bindperms=None, socketpath=None): self.tlsserver = None self.unixdomainserver = None self.bind_host = bindhost or '::' self.bind_port = bindport or 13001 self.bind_group = bindgroup self.bind_perms = bindperms + self.socketpath = socketpath or default_socketpath async def start(self): global auditlog @@ -509,7 +520,8 @@ class SockApi(object): self.start_remoteapi() else: tasks.spawn(self.watch_for_cert()) - self.unixdomainserver = tasks.spawn_task(_unixdomainhandler(self.bind_group, self.bind_perms)) + self.unixdomainserver = tasks.spawn_task(_unixdomainhandler( + self.bind_group, self.bind_perms, self.socketpath)) async def watch_for_cert(self): watcher = libc.inotify_init1(os.O_NONBLOCK)