def set_identify(self, on=True, duration=None): """Request identify light Request the identify light to turn off, on for a duration, or on indefinitely. Other than error exceptions, :param on: Set to True to force on or False to force off :param duration: Set if wanting to request turn on for a duration rather than indefinitely on """ if duration is not None: duration = int(duration) if duration > 255: duration = 255 if duration < 0: duration = 0 response = self.raw_command(netfn=0, command=4, data=[duration]) if 'error' in response: raise exc.IpmiException(response['error']) return forceon = 0 if on: forceon = 1 if self.ipmi_session.ipmiversion < 2.0: # ipmi 1.5 made due with just one byte, make best effort # to imitate indefinite as close as possible identifydata = [255 * forceon] else: identifydata = [0, forceon] response = self.raw_command(netfn=0, command=4, data=identifydata) if 'error' in response: raise exc.IpmiException(response['error'])
def set_power(self, powerstate, wait=False): """Request power state change :param powerstate: * on -- Request system turn on * off -- Request system turn off without waiting for OS to shutdown * shutdown -- Have system request OS proper shutdown * reset -- Request system reset without waiting for OS * boot -- If system is off, then 'on', else 'reset' :param wait: If True, do not return until system actually completes requested state change for 300 seconds. If a non-zero number, adjust the wait time to the requested number of seconds :returns: dict -- A dict describing the response retrieved """ if powerstate not in power_states: raise exc.InvalidParameterValue( "Unknown power state %s requested" % powerstate) self.newpowerstate = powerstate response = self.ipmi_session.raw_command(netfn=0, command=1) if 'error' in response: raise exc.IpmiException(response['error']) self.powerstate = 'on' if (response['data'][0] & 1) else 'off' if self.powerstate == self.newpowerstate: return {'powerstate': self.powerstate} if self.newpowerstate == 'boot': self.newpowerstate = 'on' if self.powerstate == 'off' else 'reset' response = self.ipmi_session.raw_command( netfn=0, command=2, data=[power_states[self.newpowerstate]]) if 'error' in response: raise exc.IpmiException(response['error']) self.lastresponse = {'pendingpowerstate': self.newpowerstate} waitattempts = 300 if not isinstance(wait, bool): waitattempts = wait if (wait and self.newpowerstate in ('on', 'off', 'shutdown', 'softoff')): if self.newpowerstate in ('softoff', 'shutdown'): self.waitpowerstate = 'off' else: self.waitpowerstate = self.newpowerstate currpowerstate = None while currpowerstate != self.waitpowerstate and waitattempts > 0: response = self.ipmi_session.raw_command(netfn=0, command=1, delay_xmit=1) if 'error' in response: return response currpowerstate = 'on' if (response['data'][0] & 1) else 'off' waitattempts -= 1 if currpowerstate != self.waitpowerstate: raise exc.IpmiException( "System did not accomplish power state change") return {'powerstate': currpowerstate} else: return self.lastresponse
def set_ris_configuration_parameter(self, media_type_id, param_id, value): multiply = lambda value, times=None: [value] * times if times else [] hexify = lambda x, y=None: ([ord(elem) for elem in x]) reset_progress_bit = False data = [media_type_id, param_id] # Ris Restart if param_id == 0x0b and media_type_id == 0x08: rsp = self.ipmicmd.raw_command(netfn=0x32, command=0x9f, data=data) # Ris State elif param_id == 0x0a and media_type_id == 0x08: data.extend([0x00, 0x00 if value == 'disable' else 0x01]) rsp = self.ipmicmd.raw_command(netfn=0x32, command=0x9f, data=data) # Password elif param_id in [0x04] and media_type_id != 0x08: data.append(0x00) data.extend(hexify(value, 32)) rsp = self.ipmicmd.raw_command(netfn=0x32, command=0x9f, data=data) elif param_id in [0x02, 0x05] and media_type_id != 0x08: data.append(0x00) data.extend(hexify(value, 64)) rsp = self.ipmicmd.raw_command(netfn=0x32, command=0x9f, data=data) # Ris Souce Path, image name, domain name or user name elif param_id in [0x01, 0x00, 0x06, 0x03] and media_type_id != 0x08: if len(value) > 256: raise StandardError('Value should be less than 256 caracteres') # Progress Bit self.ipmicmd.xraw_command(netfn=0x32, command=0x9f, data=(0x01, param_id, 0x00, 0x01)) for i in range(1, int(math.ceil(len(value) / 64.0)) + 1): data = [0x01, param_id, i] substr_start = (i - 1) * 64 substr_end = i * 64 data.extend(hexify(value[substr_start:substr_end], 64)) self.ipmicmd.raw_command(netfn=0x32, command=0x9f, data=data) # Reset Progress Bit rsp = self.ipmicmd.raw_command(netfn=0x32, command=0x9f, data=(0x01, param_id, 0x00, 0x00)) else: raise StandardError('Unknown parameter or not implemented') error_messages = { # Documented message 0x95: 'Image format is not supported', } if 'code' in rsp and rsp['code'] != 0: if rsp['code'] in error_messages: raise pygexc.IpmiException(error_messages[rsp['code']], rsp['code']) else: raise pygexc.IpmiException(rsp['error'], rsp['code'])
def _xmit_packet(self, retry=True, delay_xmit=None): if self.sequencenumber: self.sequencenumber += 1 if delay_xmit is not None: # skip transmit, let retry timer do it's thing self.waiting_sessions[self] = {} self.waiting_sessions[self]["ipmisession"] = self self.waiting_sessions[self]["timeout"] = delay_xmit + _monotonic_time() return if self.sockaddr: self.send_data(self.netpacket, self.sockaddr) else: self.allsockaddrs = [] try: for res in socket.getaddrinfo( self.bmc, self.port, 0, socket.SOCK_DGRAM ): sockaddr = res[4] if res[0] == socket.AF_INET: # convert the sockaddr to AF_INET6 newhost = "::ffff:" + sockaddr[0] sockaddr = (newhost, sockaddr[1], 0, 0) self.allsockaddrs.append(sockaddr) self.bmc_handlers[sockaddr] = self self.send_data(self.netpacket, sockaddr) except socket.gaierror: raise exc.IpmiException("Unable to transmit to specified address") if retry: self.waiting_sessions[self] = {} self.waiting_sessions[self]["ipmisession"] = self self.waiting_sessions[self]["timeout"] = self.timeout + _monotonic_time()
def test__run_set_power_cmd_ipmi_exc(self, mock_command): ipmicmd = mock_command.return_value ipmicmd.set_power.side_effect = pyghmi_exc.IpmiException() self.assertRaises(pyghmi_exc.IpmiException, self.driver._run_set_power_cmd, self.host, 'off', expected_state='off')
def get_boot_device(self, task): """Get the current boot device for the task's node. Returns the current boot device of the node. :param task: a task from TaskManager. :raises: MissingParameterValue if required IPMI parameters are missing. :raises: IPMIFailure on an error from pyghmi. :returns: a dictionary containing: :boot_device: the boot device, one of :mod:`ironic.common.boot_devices` or None if it is unknown. :persistent: Whether the boot device will persist to all future boots or not, None if it is unknown. """ driver_info = task.node.driver_info driver_internal_info = task.node.driver_internal_info if (driver_info.get('ipmi_force_boot_device', False) and driver_internal_info.get('persistent_boot_device') and driver_internal_info.get('is_next_boot_persistent', True)): return { 'boot_device': driver_internal_info['persistent_boot_device'], 'persistent': True } driver_info = _parse_driver_info(task.node) response = {'boot_device': None} try: ipmicmd = ipmi_command.Command(bmc=driver_info['address'], userid=driver_info['username'], password=driver_info['password']) ret = ipmicmd.get_bootdev() # FIXME(lucasagomes): pyghmi doesn't seem to handle errors # consistently, for some errors it raises an exception # others it just returns a dictionary with the error. if 'error' in ret: raise pyghmi_exception.IpmiException(ret['error']) except pyghmi_exception.IpmiException as e: LOG.error( _LE("IPMI get boot device failed for node %(node_id)s " "with the following error: %(error)s"), { 'node_id': driver_info['uuid'], 'error': e }) raise exception.IPMIFailure(cmd=e) response['persistent'] = ret.get('persistent') bootdev = ret.get('bootdev') if bootdev: response['boot_device'] = next( (dev for dev, hdev in _BOOT_DEVICES_MAP.items() if hdev == bootdev), None) return response
def send_payload(self, payload, payload_type=1, retry=True, needskeepalive=False): while not (self.connected or self.broken): session.Session.wait_for_rsp(timeout=10) if not self.ipmi_session.logged: raise exc.IpmiException('Session no longer connected') self.ipmi_session.send_payload(payload, payload_type=payload_type, retry=retry, needskeepalive=needskeepalive)
def fetch_fru(self, fruid): response = self.ipmicmd.raw_command(netfn=0xa, command=0x10, data=[fruid]) if 'error' in response: raise iexc.IpmiException(response['error'], code=response['code']) frusize = response['data'][0] | (response['data'][1] << 8) # In our case, we don't need to think too hard about whether # the FRU is word or byte, we just process what we get back in the # payload chunksize = 240 # Selected as it is accomodated by most tested things # and many tested things broke after going much # bigger if chunksize > frusize: chunksize = frusize offset = 0 self.rawfru = bytearray([]) while chunksize: response = self.ipmicmd.raw_command( netfn=0xa, command=0x11, data=[fruid, offset & 0xff, offset >> 8, chunksize]) if response['code'] in (201, 202): # if it was too big, back off and try smaller # Try just over half to mitigate the chance of # one request becoming three rather than just two if chunksize == 3: raise iexc.IpmiException(response['error']) chunksize //= 2 chunksize += 2 continue elif 'error' in response: raise iexc.IpmiException(response['error'], response['code']) self.rawfru.extend(response['data'][1:]) offset += response['data'][0] if response['data'][0] == 0: break if offset + chunksize > frusize: chunksize = frusize - offset
def set_media_redirection_state(self, image_type, image_name, value): data = [image_type, 0x01 if value == 'start' else 0x00] data.extend([ord(elem) for elem in image_name]) data.append(0x00) rsp = self.ipmicmd.raw_command(netfn=0x3a, command=0x16, data=data) error_messages = { 0x90: 'Media support is not enabled', 0x91: 'Error in get vmedia configuration', 0x92: 'Media is not running', 0x95: 'Slot is not available for the particular Image type', 0x96: 'Error in retrieving available image information', 0x97: 'Valid Image is not available', 0x98: 'Image format is not supported', 0x99: 'Image file not found', 0x9a: 'invalid start/stop command' } if 'code' in rsp and rsp['code'] != 0: if rsp['code'] in error_messages: raise pygexc.IpmiException(error_messages[rsp['code']], rsp['code']) else: raise pygexc.IpmiException(rsp['error'], rsp['code'])
def test_get_pci_exception(self, exec_mock): pci_device_ids = ['0x1111/0x1179', '0x2100/0x0080'] exec_mock.side_effect = ipmi_exception.IpmiException('Error') cmd = "0x2E 0xF1 0x80 0x28 0x00 0x1A 0x1 0x00" e = self.assertRaises(ipmi.IPMIFailure, ipmi.get_pci_device, self.info, pci_device_ids) exec_mock.assert_called_once_with(mock.ANY, cmd) self.assertEqual( 'IPMI operation \'GET PCI device quantity\' ' 'failed: Error', str(e))
def get_power(self): """Get current power state of the managed system The response, if successful, should contain 'powerstate' key and either 'on' or 'off' to indicate current state. :returns: dict -- {'powerstate': value} """ response = self.ipmi_session.raw_command(netfn=0, command=1) if 'error' in response: raise exc.IpmiException(response['error']) assert (response['command'] == 1 and response['netfn'] == 1) self.powerstate = 'on' if (response['data'][0] & 1) else 'off' return {'powerstate': self.powerstate}
def _get_ipmicmd(self, user=None, password=None): priv = None if user is None or password is None: if self.trieddefault: raise pygexc.IpmiException() priv = 4 # manually indicate priv to avoid double-attempt if user is None: user = self.DEFAULT_USER if password is None: password = self.DEFAULT_PASS return ipmicommand.Command(self.ipaddr, user, password, privlevel=priv, keepalive=False)
def get_sensor_data(self): """Get sensor reading objects Iterates sensor reading objects pertaining to the currently managed BMC. :returns: Iterator of sdr.SensorReading objects """ if self._sdr is None: self._sdr = sdr.SDR(self) for sensor in self._sdr.get_sensor_numbers(): rsp = self.raw_command(command=0x2d, netfn=4, data=(sensor, )) if 'error' in rsp: if rsp['code'] == 203: # Sensor does not exist, optional dev continue raise exc.IpmiException(rsp['error'], code=rsp['code']) yield self._sdr.sensors[sensor].decode_sensor_reading(rsp['data'])
def get_sensor_reading(self, sensorname): """Get a sensor reading by name Returns a single decoded sensor reading per the name passed in :param sensorname: Name of the desired sensor :returns: sdr.SensorReading object """ if self._sdr is None: self._sdr = sdr.SDR(self) for sensor in self._sdr.get_sensor_numbers(): if self._sdr.sensors[sensor].name == sensorname: rsp = self.raw_command(command=0x2d, netfn=4, data=(sensor, )) if 'error' in rsp: raise exc.IpmiException(rsp['error'], rsp['code']) return self._sdr.sensors[sensor].decode_sensor_reading( rsp['data']) raise Exception('Sensor not found: ' + sensorname)
def get_sdr(self): repinfo = self.ipmicmd.xraw_command(netfn=0x0a, command=0x20) repinfo['data'] = bytearray(repinfo['data']) if (repinfo['data'][0] != 0x51): # we only understand SDR version 51h, the only version defined # at time of this writing raise NotImplementedError #NOTE(jbjohnso): we actually don't need to care about 'numrecords' # since FFFF marks the end explicitly #numrecords = (rsp['data'][2] << 8) + rsp['data'][1] #NOTE(jbjohnso): don't care about 'free space' at the moment #NOTE(jbjohnso): most recent timstamp data for add and erase could be # handy to detect cache staleness, but for now will assume invariant # over life of session #NOTE(jbjohnso): not looking to support the various options in op # support, ignore those for now, reservation if some BMCs can't read # full SDR in one slurp recid = 0 rsvid = 0 # partial 'get sdr' will require this offset = 0 size = 0xff chunksize = 128 self.broken_sensor_ids = {} while recid != 0xffff: # per 33.12 Get SDR command, 0xffff marks end newrecid = 0 currlen = 0 sdrdata = bytearray() while True: # loop until SDR fetched wholly if size != 0xff and rsvid == 0: rsvid = self.get_sdr_reservation() rqdata = [ rsvid & 0xff, rsvid >> 8, recid & 0xff, recid >> 8, offset, size ] sdrrec = self.ipmicmd.raw_command(netfn=0x0a, command=0x23, data=rqdata) if sdrrec['code'] == 0xca: if size == 0xff: # get just 5 to get header to know length size = 5 elif size > 5: size /= 2 # push things over such that it's less # likely to be just 1 short of a read # and incur a whole new request size += 2 chunksize = size continue if sdrrec['code'] == 0xc5: # need a new reservation id rsvid = 0 continue if sdrrec['code'] != 0: raise exc.IpmiException(sdrrec['error']) if newrecid == 0: newrecid = (sdrrec['data'][1] << 8) + sdrrec['data'][0] if currlen == 0: currlen = sdrrec['data'][6] + 5 # compensate for header sdrdata.extend(sdrrec['data'][2:]) # determine next offset to use based on current offset and the # size used last time. offset += size if offset >= currlen: break if size == 5 and offset == 5: # bump up size after header retrieval size = chunksize if (offset + size) > currlen: size = currlen - offset self.add_sdr(sdrdata) offset = 0 if size != 0xff: size = 5 if newrecid == recid: raise exc.BmcErrorException("Incorrect SDR record id from BMC") recid = newrecid for sid in self.broken_sensor_ids: del self.sensors[sid]
def get_sdr_reservation(self): rsp = self.ipmicmd.raw_command(netfn=0x0a, command=0x22) if rsp['code'] != 0: raise exc.IpmiException(rsp['error']) return rsp['data'][0] + (rsp['data'][1] << 8)
def test__send_raw_fail(self, ipmi_mock): ipmicmd = ipmi_mock.return_value ipmicmd.xraw_command.side_effect = pyghmi_exception.IpmiException() self.assertRaises(exception.IPMIFailure, ipminative._send_raw, self.info, '0x01 0x02')
def get_sdr(self): repinfo = self.ipmicmd.xraw_command(netfn=0x0a, command=0x20) repinfo['data'] = bytearray(repinfo['data']) if (repinfo['data'][0] != 0x51): # we only understand SDR version 51h, the only version defined # at time of this writing raise NotImplementedError # NOTE(jbjohnso): we actually don't need to care about 'numrecords' # since FFFF marks the end explicitly # numrecords = (rsp['data'][2] << 8) + rsp['data'][1] # NOTE(jbjohnso): don't care about 'free space' at the moment # NOTE(jbjohnso): most recent timstamp data for add and erase could be # handy to detect cache staleness, but for now will assume invariant # over life of session # NOTE(jbjohnso): not looking to support the various options in op # support, ignore those for now, reservation if some BMCs can't read # full SDR in one slurp modtime = struct.unpack('!Q', repinfo['data'][5:13])[0] recid = 0 rsvid = 0 # partial 'get sdr' will require this offset = 0 size = 0xff chunksize = 128 try: csdrs = shared_sdrs[ (self.fw_major, self.fw_minor, self.mfg_id, self.prod_id, self.device_id, modtime)] self.sensors = csdrs['sensors'] self.fru = csdrs['fru'] return except KeyError: pass cachefilename = None self.broken_sensor_ids = {} if self.cachedir: cachefilename = 'sdrcache.{0}.{1}.{2}.{3}.{4}.{5}'.format( self.mfg_id, self.prod_id, self.device_id, self.fw_major, self.fw_minor, modtime) cachefilename = os.path.join(self.cachedir, cachefilename) if cachefilename and os.path.isfile(cachefilename): with open(cachefilename, 'r') as cfile: csdrs = pickle.load(cfile) for sdrdata in csdrs: self.add_sdr(sdrdata) for sid in self.broken_sensor_ids: try: del self.sensors[sid] except KeyError: pass shared_sdrs[ (self.fw_major, self.fw_minor, self.mfg_id, self.prod_id, self.device_id, modtime)] = { 'sensors': self.sensors, 'fru': self.fru, } return sdrraw = [] if cachefilename else None while recid != 0xffff: # per 33.12 Get SDR command, 0xffff marks end newrecid = 0 currlen = 0 sdrdata = bytearray() while True: # loop until SDR fetched wholly if size != 0xff and rsvid == 0: rsvid = self.get_sdr_reservation() rqdata = [rsvid & 0xff, rsvid >> 8, recid & 0xff, recid >> 8, offset, size] sdrrec = self.ipmicmd.raw_command(netfn=0x0a, command=0x23, data=rqdata) if sdrrec['code'] == 0xca: if size == 0xff: # get just 5 to get header to know length size = 5 elif size > 5: size /= 2 # push things over such that it's less # likely to be just 1 short of a read # and incur a whole new request size += 2 chunksize = size continue if sdrrec['code'] == 0xc5: # need a new reservation id rsvid = 0 continue if sdrrec['code'] != 0: raise exc.IpmiException(sdrrec['error']) if newrecid == 0: newrecid = (sdrrec['data'][1] << 8) + sdrrec['data'][0] if currlen == 0: currlen = sdrrec['data'][6] + 5 # compensate for header sdrdata.extend(sdrrec['data'][2:]) # determine next offset to use based on current offset and the # size used last time. offset += size if offset >= currlen: break if size == 5 and offset == 5: # bump up size after header retrieval size = chunksize if (offset + size) > currlen: size = currlen - offset self.add_sdr(sdrdata) if sdrraw is not None: sdrraw.append(sdrdata) offset = 0 if size != 0xff: size = 5 if newrecid == recid: raise exc.BmcErrorException("Incorrect SDR record id from BMC") recid = newrecid for sid in self.broken_sensor_ids: try: del self.sensors[sid] except KeyError: pass shared_sdrs[(self.fw_major, self.fw_minor, self.mfg_id, self.prod_id, self.device_id, modtime)] = { 'sensors': self.sensors, 'fru': self.fru, } if cachefilename: suffix = ''.join( random.choice(string.ascii_lowercase) for _ in range(12)) with open(cachefilename + '.' + suffix, 'w') as cfile: pickle.dump(sdrraw, cfile) os.rename(cachefilename + '.' + suffix, cachefilename)