mirror of
https://opendev.org/x/pyghmi
synced 2025-01-27 19:37:44 +00:00
Merge "Support in-band IPMI for Linux systems"
This commit is contained in:
commit
a363bd92fb
@ -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,
|
||||
|
@ -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
|
||||
|
135
pyghmi/ipmi/private/localsession.py
Normal file
135
pyghmi/ipmi/private/localsession.py
Normal 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()
|
@ -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
|
||||
|
@ -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]
|
||||
|
Loading…
x
Reference in New Issue
Block a user