Support in-band IPMI for Linux systems

This is a subset of pyghmi functionality for use in dealing with BMC
over KCS or similar Linux driver supported IPMI access through
/dev/ipmi0

Change-Id: Ifeb3935cbb3c95e7871b74a2b42e59e71ecb6697
This commit is contained in:
Jarrod Johnson 2017-02-26 13:44:02 -05:00
parent 42b6807840
commit 26573c1ce5
5 changed files with 197 additions and 50 deletions

View File

@ -1,7 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 IBM Corporation
# Copyright 2015 Lenovo
# Copyright 2015-2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -23,7 +23,11 @@ import pyghmi.exceptions as exc
import pyghmi.ipmi.events as sel
import pyghmi.ipmi.fru as fru
from pyghmi.ipmi.oem.lookup import get_oem_handler
from pyghmi.ipmi.private import session
try:
from pyghmi.ipmi.private import session
except ImportError:
session = None
from pyghmi.ipmi.private import localsession
import pyghmi.ipmi.private.util as pygutil
import pyghmi.ipmi.sdr as sdr
import socket
@ -103,15 +107,16 @@ class Command(object):
callback_args parameter. However, callback_args can optionally be populated
if desired.
:param bmc: hostname or ip address of the BMC
:param userid: username to use to connect
:param password: password to connect to the BMC
:param bmc: hostname or ip address of the BMC (default is local)
:param userid: username to use to connect (default to no user)
:param password: password to connect to the BMC (defaults to no password)
:param onlogon: function to run when logon completes in an asynchronous
fashion. This will result in a greenthread behavior.
:param kg: Optional parameter to use if BMC has a particular Kg configured
"""
def __init__(self, bmc, userid, password, port=623, onlogon=None, kg=None):
def __init__(self, bmc=None, userid=None, password=None, port=623,
onlogon=None, kg=None):
# TODO(jbjohnso): accept tuples and lists of each parameter for mass
# operations without pushing the async complexities up the stack
self.onlogon = onlogon
@ -121,7 +126,9 @@ class Command(object):
self._netchannel = None
self._ipv6support = None
self.certverify = None
if onlogon is not None:
if bmc is None:
self.ipmi_session = localsession.Session()
elif onlogon is not None:
self.ipmi_session = session.Session(bmc=bmc,
userid=userid,
password=password,

View File

@ -16,7 +16,7 @@
from datetime import datetime
import json
from pyghmi.ipmi.private.session import _monotonic_time
from pyghmi.ipmi.private.util import _monotonic_time
import pyghmi.util.webclient as webclient
import urllib
import weakref

View File

@ -0,0 +1,135 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2017 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.
from ctypes import addressof, c_int, c_long, c_short, c_ubyte, c_uint
from ctypes import cast, create_string_buffer, POINTER, pointer, sizeof
from ctypes import Structure
import fcntl
import pyghmi.ipmi.private.util as iutil
from select import select
class IpmiMsg(Structure):
_fields_ = [('netfn', c_ubyte),
('cmd', c_ubyte),
('data_len', c_short),
('data', POINTER(c_ubyte))]
class IpmiSystemInterfaceAddr(Structure):
_fields_ = [('addr_type', c_int),
('channel', c_short),
('lun', c_ubyte)]
class IpmiRecv(Structure):
_fields_ = [('recv_type', c_int),
('addr', POINTER(IpmiSystemInterfaceAddr)),
('addr_len', c_uint),
('msgid', c_long),
('msg', IpmiMsg)]
class IpmiReq(Structure):
_fields_ = [('addr', POINTER(IpmiSystemInterfaceAddr)),
('addr_len', c_uint),
('msgid', c_long),
('msg', IpmiMsg)]
_IONONE = 0
_IOWRITE = 1
_IOREAD = 2
IPMICTL_SET_MY_ADDRESS_CMD = _IOREAD << 30 | sizeof(c_uint) << 16 | \
ord('i') << 8 | 17 # from ipmi.h
IPMICTL_SEND_COMMAND = _IOREAD << 30 | sizeof(IpmiReq) << 16 | \
ord('i') << 8 | 13 # from ipmi.h
# next is really IPMICTL_RECEIVE_MSG_TRUNC, but will only use that
IPMICTL_RECV = (_IOWRITE | _IOREAD) << 30 | sizeof(IpmiRecv) << 16 | \
ord('i') << 8 | 11 # from ipmi.h
BMC_SLAVE_ADDR = c_uint(0x20)
CURRCHAN = 0xf
ADDRTYPE = 0xc
class Session(object):
def __init__(self, devnode='/dev/ipmi0'):
"""Create a local session inband
:param: devnode: The path to the ipmi device
"""
self.ipmidev = open(devnode, 'rw')
fcntl.ioctl(self.ipmidev, IPMICTL_SET_MY_ADDRESS_CMD, BMC_SLAVE_ADDR)
# the interface is initted, create some reusable memory for our session
self.databuffer = create_string_buffer(4096)
self.req = IpmiReq()
self.rsp = IpmiRecv()
self.addr = IpmiSystemInterfaceAddr()
self.req.msg.data = cast(addressof(self.databuffer), POINTER(c_ubyte))
self.rsp.msg.data = self.req.msg.data
self.userid = None
self.password = None
def await_reply(self):
rd, _, _ = select((self.ipmidev,), (), (), 1)
while not rd:
rd, _, _ = select((self.ipmidev,), (), (), 1)
@property
def parsed_rsp(self):
response = {'netfn': self.rsp.msg.netfn, 'command': self.rsp.msg.cmd,
'code': ord(self.databuffer.raw[0]),
'data': list(bytearray(
self.databuffer.raw[1:self.rsp.msg.data_len]))}
errorstr = iutil.get_ipmi_error(response)
if errorstr:
response['error'] = errorstr
return response
def raw_command(self,
netfn,
command,
data=(),
bridge_request=None,
retry=True,
delay_xmit=None,
timeout=None,
waitall=False):
self.addr.channel = CURRCHAN
self.addr.addr_type = ADDRTYPE
self.req.addr_len = sizeof(IpmiSystemInterfaceAddr)
self.req.addr = pointer(self.addr)
self.req.msg.netfn = netfn
self.req.msg.cmd = command
data = buffer(bytearray(data))
self.databuffer[:len(data)] = data[:len(data)]
self.req.msg.data_len = len(data)
fcntl.ioctl(self.ipmidev, IPMICTL_SEND_COMMAND, self.req)
self.await_reply()
self.rsp.msg.data_len = 4096
self.rsp.addr = pointer(self.addr)
self.rsp.addr_len = sizeof(IpmiSystemInterfaceAddr)
fcntl.ioctl(self.ipmidev, IPMICTL_RECV, self.rsp)
return self.parsed_rsp
def main():
a = Session('/dev/ipmi0')
print(repr(a.raw_command(0, 1)))
if __name__ == '__main__':
main()

View File

@ -1,7 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2013 IBM Corporation
# Copyright 2015-2016 Lenovo
# Copyright 2015-2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -17,7 +17,6 @@
# This represents the low layer message framing portion of IPMI
import collections
import ctypes
import hashlib
import hmac
import operator
@ -32,6 +31,7 @@ from Crypto.Cipher import AES
import pyghmi.exceptions as exc
from pyghmi.ipmi.private import constants
from pyghmi.ipmi.private.util import get_ipmi_error, _monotonic_time
try:
dict.iteritems
@ -205,11 +205,6 @@ def _io_recvfrom(mysocket, size):
except socket.error:
return None
wintime = None
try:
wintime = ctypes.windll.kernel32.GetTickCount64
except AttributeError:
pass
try:
IPPROTO_IPV6 = socket.IPPROTO_IPV6
@ -219,20 +214,6 @@ except AttributeError:
# targetting.
def _monotonic_time():
"""Provides a monotonic timer
This code is concerned with relative, not absolute time.
This function facilitates that prior to python 3.3
"""
# Python does not provide one until 3.3, so we make do
# for most OSes, os.times()[4] works well.
# for microsoft, GetTickCount64
if wintime:
return wintime() / 1000.0
return os.times()[4]
def _poller(timeout=0):
if sessionqueue:
return True
@ -258,25 +239,6 @@ def _aespad(data):
return newdata
def get_ipmi_error(response, suffix=""):
if 'error' in response:
return response['error'] + suffix
code = response['code']
if code == 0:
return False
command = response['command']
netfn = response['netfn']
if ((netfn, command) in constants.command_completion_codes
and code in constants.command_completion_codes[(netfn, command)]):
res = constants.command_completion_codes[(netfn, command)][code]
res += suffix
elif code in constants.ipmi_completion_codes:
res = constants.ipmi_completion_codes[code] + suffix
else:
res = "Unknown code 0x%2x encountered" % code
return res
def _checksum(*data): # Two's complement over the data
csum = sum(data)
csum ^= 0xff

View File

@ -1,6 +1,6 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2015 Lenovo
# Copyright 2015-2017 Lenovo
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
@ -13,10 +13,13 @@
# 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 ctypes
import os
import socket
import struct
from pyghmi.ipmi.private import constants
try:
range = xrange
except NameError:
@ -27,6 +30,13 @@ except NameError:
buffer = memoryview
wintime = None
try:
wintime = ctypes.windll.kernel32.GetTickCount64
except AttributeError:
pass
def decode_wireformat_uuid(rawguid):
"""Decode a wire format UUID
@ -64,3 +74,36 @@ def get_ipv4(hostname):
addrinfo = socket.getaddrinfo(hostname, None, socket.AF_INET,
socket.SOCK_STREAM)
return [addrinfo[x][4][0] for x in range(len(addrinfo))]
def get_ipmi_error(response, suffix=""):
if 'error' in response:
return response['error'] + suffix
code = response['code']
if code == 0:
return False
command = response['command']
netfn = response['netfn']
if ((netfn, command) in constants.command_completion_codes
and code in constants.command_completion_codes[(netfn, command)]):
res = constants.command_completion_codes[(netfn, command)][code]
res += suffix
elif code in constants.ipmi_completion_codes:
res = constants.ipmi_completion_codes[code] + suffix
else:
res = "Unknown code 0x%2x encountered" % code
return res
def _monotonic_time():
"""Provides a monotonic timer
This code is concerned with relative, not absolute time.
This function facilitates that prior to python 3.3
"""
# Python does not provide one until 3.3, so we make do
# for most OSes, os.times()[4] works well.
# for microsoft, GetTickCount64
if wintime:
return wintime() / 1000.0
return os.times()[4]