diff --git a/confluent_server/aiohmi/redfish/command.py b/confluent_server/aiohmi/redfish/command.py index de6a813f..7d1eb8b1 100644 --- a/confluent_server/aiohmi/redfish/command.py +++ b/confluent_server/aiohmi/redfish/command.py @@ -1157,6 +1157,107 @@ class Command(object): await self._do_web_request(await self.get_bmcnicurl(), {'HostName': hostname}, 'PATCH', etag='*') + async def _netprotocolurl(self): + bmcinfo = await self._do_web_request(await self.get_bmcurl()) + netprotocols = bmcinfo.get('NetworkProtocol', {}).get('@odata.id', None) + if not netprotocols: + raise exc.UnsupportedFunctionality( + 'This platform does not describe its manager network protocols') + return netprotocols + + async def get_mci(self): + """Get the management controller identifier + + Redfish has no separate identifier for a management controller the way + ipmi does, and the platforms that offer both report the same string for + each, so use the name the manager answers to. + """ + netcfg = await self._do_web_request(await self._netprotocolurl()) + name = netcfg.get('HostName', None) + if name is None: + raise exc.UnsupportedFunctionality( + 'This platform does not report a manager identifier') + return name + + async def set_mci(self, mci): + await self._do_web_request(await self._netprotocolurl(), + {'HostName': mci}, method='PATCH', etag='*') + + async def get_domain_name(self): + netcfg = await self._do_web_request(await self._netprotocolurl()) + fqdn = netcfg.get('FQDN', None) + if not fqdn: + return '' + # The fqdn is the manager name with the domain appended, and only the + # domain part is wanted here + hostname = netcfg.get('HostName', None) + if hostname and fqdn.startswith(hostname + '.'): + return fqdn[len(hostname) + 1:] + return fqdn.partition('.')[2] + + async def set_domain_name(self, domain): + netprotocols = await self._netprotocolurl() + netcfg = await self._do_web_request(netprotocols) + hostname = netcfg.get('HostName', '') + fqdn = '{0}.{1}'.format(hostname, domain) if domain else hostname + await self._do_web_request(netprotocols, {'FQDN': fqdn}, + method='PATCH', etag='*') + + async def get_remote_kvm_available(self): + bmcinfo = await self._do_web_request(await self.get_bmcurl()) + gconsole = bmcinfo.get('GraphicalConsole', {}) + return bool(gconsole.get('ServiceEnabled', False)) + + _ledstatusmap = { + 'Lit': 'On', + 'Blinking': 'Blink', + 'Off': 'Off', + } + + async def get_leds(self): + """Get LED status information + + Standard redfish only describes the identify indicator, so that is all a + platform without an oem specific view of its leds can report. + """ + sysinfo = await self.sysinfo() + seen = False + for chassis in sysinfo.get('Links', {}).get('Chassis', []): + chassisinfo = await self._do_web_request(chassis['@odata.id']) + state = chassisinfo.get('IndicatorLED', None) + if state is None: + continue + seen = True + yield ('identify', {'status': self._ledstatusmap.get(state, state)}) + break + if not seen and sysinfo.get('IndicatorLED', None) is not None: + state = sysinfo['IndicatorLED'] + yield ('identify', {'status': self._ledstatusmap.get(state, state)}) + + # A firmware inventory entry says what it belongs to with RelatedItem, so + # map the collections that turn up there onto the categories confluent asks + # for + _fwcategorybyurl = ( + ('/Drives/', 'disks'), + ('/Storage/', 'disks'), + ('/PCIeDevices/', 'adapters'), + ('/NetworkAdapters/', 'adapters'), + ('/NetworkInterfaces/', 'adapters'), + ) + + def _fwcategory(self, fwi): + """Which category a firmware inventory entry belongs to. + + Returns None when the entry gives nothing to judge by. + """ + related = fwi.get('RelatedItem', []) + for item in related: + url = item.get('@odata.id', '') + for frag, category in self._fwcategorybyurl: + if frag in url: + return category + return 'core' if related else None + async def get_firmware(self, components=(), category=None): self._fwnamemap = {} oem = await self.oem() diff --git a/confluent_server/confluent/plugins/hardwaremanagement/redfish.py b/confluent_server/confluent/plugins/hardwaremanagement/redfish.py index 96476be1..1dc81b00 100644 --- a/confluent_server/confluent/plugins/hardwaremanagement/redfish.py +++ b/confluent_server/confluent/plugins/hardwaremanagement/redfish.py @@ -655,43 +655,13 @@ class IpmiHandler: await self.ipmicmd.install_bmc_certificate(cert) async def handle_alerts(self): - if self.element[3] == 'destinations': - if len(self.element) == 4: - # A list of destinations - maxdest = await self.ipmicmd.get_alert_destination_count() - for alertidx in range(0, maxdest + 1): - await self.output.put(msg.ChildCollection(alertidx)) - return - elif len(self.element) == 5: - alertidx = int(self.element[-1]) - if self.op == 'read': - destdata = await self.ipmicmd.get_alert_destination(alertidx) - await self.output.put(msg.AlertDestination( - ip=destdata['address'], - acknowledge=destdata['acknowledge_required'], - acknowledge_timeout=destdata.get('acknowledge_timeout', None), - retries=destdata['retries'], - name=self.node)) - return - elif self.op == 'update': - alertparms = self.inputdata.alert_params_by_node( - self.node) - alertargs = {} - if 'acknowledge' in alertparms: - alertargs['acknowledge_required'] = alertparms['acknowledge'] - if 'acknowledge_timeout' in alertparms: - alertargs['acknowledge_timeout'] = alertparms['acknowledge_timeout'] - if 'ip' in alertparms: - alertargs['ip'] = alertparms['ip'] - if 'retries' in alertparms: - alertargs['retries'] = alertparms['retries'] - await self.ipmicmd.set_alert_destination(destination=alertidx, - **alertargs) - return - elif self.op == 'delete': - await self.ipmicmd.clear_alert_destination(alertidx) - return - raise Exception('Not implemented') + # Redfish describes where to send events with the EventService + # subscription collection, which is a different model from the numbered + # ipmi PET destinations this resource was built around, so say so rather + # than calling methods the redfish client does not have + raise pygexc.UnsupportedFunctionality( + 'Alert destinations are not implemented for redfish, use the ' + 'EventService subscriptions on the bmc directly') async def handle_nets(self): if len(self.element) == 3: @@ -1437,9 +1407,23 @@ class IpmiHandler: await self.ipmicmd.set_domain_name(dn) return + _locationfields = ('room', 'location', 'building', 'rack', 'contactnames') + async def handle_location_config(self): if 'read' == self.op: - lc = await self.ipmicmd.get_location_information() + await self.output.put(msg.KeyValueData( + await self.ipmicmd.get_location_information(), self.node)) + return + elif 'update' == self.op: + attribs = self.inputdata.get_attributes(self.node) + locargs = {x: attribs[x] for x in self._locationfields + if x in attribs} + if not locargs: + raise exc.InvalidArgumentException( + 'Location accepts only: {0}'.format( + ', '.join(self._locationfields))) + await self.ipmicmd.set_location_information(**locargs) + return async def handle_bmcconfig(self, advanced=False, extended=False): if 'read' == self.op: