2
0
mirror of https://opendev.org/x/pyghmi synced 2025-10-29 18:35:24 +00:00

Pyghmi does not follow the pep8 standard

Pep8 check is necessory for our code, it also makes your
code easy to readable and understandable and beautiful.

Change-Id: I4bb8434db07bf8fe82a4f65dfdfe7f2114b00083
Closes-Bug: #1699298
This commit is contained in:
lijingxin
2017-06-22 16:00:16 +08:00
committed by lijingxin
parent 616fbe8276
commit d363fb3821
11 changed files with 293 additions and 272 deletions

View File

@@ -15,6 +15,8 @@
import os
import sys
from pyghmi.version import version_info
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
@@ -25,7 +27,7 @@ sys.path.insert(0, os.path.abspath('.'))
# -- General configuration ---------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
#needs_sphinx = '1.0'
# needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
@@ -38,7 +40,7 @@ templates_path = ['_templates']
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
@@ -51,7 +53,7 @@ copyright = u'2013, Jarrod Johnson <jbjohnso@us.ibm.com>'
# |version| and |release|, also used in various other places throughout the
# built documents.
#
from pyghmi.version import version_info
# The full version, including alpha/beta/rc tags.
release = version_info.release_string()
# The short X.Y version.
@@ -59,37 +61,37 @@ version = version_info.version_string()
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None
# language = None
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all documents
#default_role = None
# default_role = None
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# modindex_common_prefix = []
# -- Options for HTML output -------------------------------------------------
@@ -101,26 +103,26 @@ html_theme = 'default'
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
@@ -129,56 +131,55 @@ html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# html_file_suffix = None
# Output file base name for HTML help builder.
htmlhelp_basename = 'pyghmidoc'
# -- Options for LaTeX output -------------------------------------------------
# The paper size ('letter' or 'a4').
#latex_paper_size = 'letter'
# latex_paper_size = 'letter'
# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'
# latex_font_size = '10pt'
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass [howto/manual])
@@ -190,26 +191,26 @@ latex_documents = [
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# latex_show_urls = False
# Additional stuff for the LaTeX preamble.
#latex_preamble = ''
# latex_preamble = ''
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# latex_domain_indices = True
# -- Options for manual page output -------------------------------------------

View File

@@ -14,13 +14,13 @@
# See the License for the specific language governing permissions and
# limitations under the License.
__author__ = 'jjohnson2@lenovo.com'
import pyghmi.ipmi.command as ipmicommand
import pyghmi.ipmi.private.serversession as serversession
import pyghmi.ipmi.private.session as ipmisession
import traceback
__author__ = 'jjohnson2@lenovo.com'
class Bmc(serversession.IpmiServer):
def cold_reset(self):

View File

@@ -23,6 +23,7 @@ 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
try:
from pyghmi.ipmi.private import session
except ImportError:
@@ -42,7 +43,6 @@ try:
except NameError:
buffer = memoryview
boot_devices = {
'net': 4,
'network': 4,
@@ -90,7 +90,7 @@ def _mask_to_cidr(mask):
def _cidr_to_mask(prefix):
return struct.pack('>I', 2**prefix-1 << (32-prefix))
return struct.pack('>I', 2 ** prefix - 1 << (32 - prefix))
class Command(object):
@@ -435,7 +435,7 @@ class Command(object):
response = self.raw_command(netfn=0, command=1)
if 'error' in response:
raise exc.IpmiException(response['error'])
assert(response['command'] == 1 and response['netfn'] == 1)
assert (response['command'] == 1 and response['netfn'] == 1)
powerstate = 'on' if (response['data'][0] & 1) else 'off'
return {'powerstate': powerstate}
@@ -763,7 +763,7 @@ class Command(object):
1: 'Static',
2: 'DHCP',
3: 'BIOS',
4: 'Other',
4: 'Other',
}
retdata['ipv4_configuration'] = v4cfgmethods[self._fetch_lancfg_param(
channel, 4)]
@@ -825,7 +825,7 @@ class Command(object):
that
"""
if self._netchannel is None:
for channel in chain((0xe, ), range(1, 0xc)):
for channel in chain((0xe,), range(1, 0xc)):
try:
rsp = self.xraw_command(
netfn=6, command=0x42, data=(channel,))
@@ -1105,7 +1105,7 @@ class Command(object):
return retstr
def _chunkwise_dcmi_set(self, command, data):
chunks = [data[i:i+15] for i in range(0, len(data), 15)]
chunks = [data[i:i + 15] for i in range(0, len(data), 15)]
offset = 0
for chunk in chunks:
chunk = bytearray(chunk, 'utf-8')
@@ -1184,7 +1184,7 @@ class Command(object):
'dont_change': 0,
'non_volatile': 1,
'volatile': 2,
#'reserved': 3
# 'reserved': 3
}
b = 0
b |= (access_update_modes[access_update_mode] << 6) & 0b11000000
@@ -1207,7 +1207,7 @@ class Command(object):
'dont_change': 0,
'non_volatile': 1,
'volatile': 2,
#'reserved': 3
# 'reserved': 3
}
b |= (privilege_update_modes[privilege_update_mode] << 6) & 0b11000000
privilege_levels = {
@@ -1292,7 +1292,7 @@ class Command(object):
3: 'operator',
4: 'administrator',
5: 'proprietary',
#0x0F: 'no_access'
# 0x0F: 'no_access'
}
r['privilege_level'] = privilege_levels[data[1] & 0b00001111]
return r
@@ -1337,8 +1337,8 @@ class Command(object):
0x0a: 'reserved for USB 1.x',
0x0b: 'reserved for USB 2.x',
0x0c: 'System Interface (KCS, SMIC, or BT)',
## 60h-7Fh: OEM
## all other reserved
# 60h-7Fh: OEM
# all other reserved
}
t = data[1] & 0b01111111
if t in channel_medium_types:
@@ -1450,7 +1450,7 @@ class Command(object):
privilege_level: [reserved, callback, user,
operatorm administrator, proprietary, no_access]
"""
## user access available during call-in or callback direct connection
# user access available during call-in or callback direct connection
if channel is None:
channel = self.get_network_channel()
data = [channel, uid]
@@ -1636,7 +1636,7 @@ class Command(object):
channel = self.get_network_channel()
names = {}
max_ids = self.get_channel_max_user_count(channel)
for uid in range(1, max_ids+1):
for uid in range(1, max_ids + 1):
name = self.get_user_name(uid=uid)
if name is not None:
names[uid] = self.get_user(uid=uid, channel=channel)

View File

@@ -38,7 +38,7 @@ class Console(object):
:param kg: optional parameter for BMCs configured to require it
"""
#TODO(jbjohnso): still need an exit and a data callin function
# TODO(jbjohnso): still need an exit and a data callin function
def __init__(self, bmc, userid, password,
iohandler, port=623,
force=False, kg=None):
@@ -70,19 +70,19 @@ class Console(object):
if 'error' in response:
self._print_error(response['error'])
return
#Send activate sol payload directive
#netfn= 6 (application)
#command = 0x48 (activate payload)
#data = (1, sol payload type
# Send activate sol payload directive
# netfn= 6 (application)
# command = 0x48 (activate payload)
# data = (1, sol payload type
# 1, first instance
# 0b11000000, -encrypt, authenticate,
# disable serial/modem alerts, CTS fine
# 0, 0, 0 reserved
response = self.ipmi_session.raw_command(netfn=0x6, command=0x48,
data=(1, 1, 192, 0, 0, 0))
#given that these are specific to the command,
#it's probably best if one can grep the error
#here instead of in constants
# given that these are specific to the command,
# it's probably best if one can grep the error
# here instead of in constants
sol_activate_codes = {
0x81: 'SOL is disabled',
0x82: 'Maximum SOL session count reached',
@@ -118,19 +118,19 @@ class Console(object):
self._print_error(response['error'])
return
self.activated = True
#data[0:3] is reserved except for the test mode, which we don't use
# data[0:3] is reserved except for the test mode, which we don't use
data = response['data']
self.maxoutcount = (data[5] << 8) + data[4]
#BMC tells us this is the maximum allowed size
#data[6:7] is the promise of how small packets are going to be, but we
#don't have any reason to worry about it
# BMC tells us this is the maximum allowed size
# data[6:7] is the promise of how small packets are going to be, but we
# don't have any reason to worry about it
if (data[8] + (data[9] << 8)) not in (623, 28418):
#TODO(jbjohnso): support atypical SOL port number
# 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
#code anyway...
#NOTE(jbjohnso):
#We will use a special purpose keepalive
# ignore data[10:11] for now, the vlan detail, shouldn't matter to this
# code anyway...
# NOTE(jbjohnso):
# We will use a special purpose keepalive
if self.ipmi_session.sol_handler is not None:
# If there is erroneously another SOL handler already, notify
# it of newly established session
@@ -296,9 +296,9 @@ class Console(object):
def _got_sol_payload(self, payload):
"""SOL payload callback
"""
#TODO(jbjohnso) test cases to throw some likely scenarios at functions
#for example, retry with new data, retry with no new data
#retry with unexpected sequence number
# TODO(jbjohnso) test cases to throw some likely scenarios at functions
# for example, retry with new data, retry with no new data
# retry with unexpected sequence number
if type(payload) == dict: # we received an error condition
self.activated = False
self._print_error(payload)
@@ -310,15 +310,16 @@ class Console(object):
poweredoff = payload[3] & 0b100000
deactivated = payload[3] & 0b10000
breakdetected = payload[3] & 0b100
#for now, ignore overrun. I assume partial NACK for this reason or for
#no reason would be treated the same, new payload with partial data
# for now, ignore overrun. I assume partial NACK for this reason or
# for no reason would be treated the same, new payload with partial
# data.
remdata = ""
remdatalen = 0
if newseq != 0: # this packet at least has some data to send to us..
if len(payload) > 4:
remdatalen = len(payload[4:]) # store remote len before dupe
#retry logic, we must ack *this* many even if it is
#a retry packet with new partial data
# retry logic, we must ack *this* many even if it is
# a retry packet with new partial data
remdata = struct.pack("%dB" % remdatalen, *payload[4:])
if newseq == self.remseq: # it is a retry, but could have new data
if remdatalen > self.lastsize:
@@ -331,16 +332,16 @@ class Console(object):
if remdata: # Do not subject callers to empty data
self._print_data(remdata)
ackpayload = (0, self.remseq, remdatalen, 0)
#Why not put pending data into the ack? because it's rare
#and might be hard to decide what to do in the context of
#retry situation
# Why not put pending data into the ack? because it's rare
# and might be hard to decide what to do in the context of
# retry situation
try:
self.send_payload(ackpayload, retry=False)
except exc.IpmiException:
#if the session is broken, then close the SOL session
# if the session is broken, then close the SOL session
self.close()
if self.myseq != 0 and ackseq == self.myseq: # the bmc has something
# to say about last xmit
# to say about last xmit
self.awaitingack = False
if nacked and not breakdetected: # the BMC was in some way unhappy
if poweredoff:
@@ -379,9 +380,9 @@ class Console(object):
to provide their own event loop behavior, though this could be used
within the greenthread implementation of caller's choice if desired.
"""
#wait_for_rsp promises to return a false value when no sessions are
#alive anymore
#TODO(jbjohnso): wait_for_rsp is not returning a true value for our own
#session
# wait_for_rsp promises to return a false value when no sessions are
# alive anymore
# TODO(jbjohnso): wait_for_rsp is not returning a true value for our
# own session
while (1):
session.Session.wait_for_rsp(timeout=600)

View File

@@ -187,8 +187,8 @@ class FRU(object):
frusubtype = self.sdr.fru_type_and_modifier & 0xff
if frutype > 0x10 or frutype < 0x8 or frusubtype not in (0, 1, 2):
return
#TODO(jjohnson2): strict mode to detect pyghmi and BMC
#gaps
# TODO(jjohnson2): strict mode to detect pyghmi and BMC
# gaps
# raise iexc.PyghmiException(
# 'Unsupported FRU device: {0:x}h, {1:x}h'.format(frutype,
# frusubtype
@@ -199,7 +199,7 @@ class FRU(object):
return
if self.databytes[0] != 1:
return
#TODO(jjohnson2): strict mode to flag potential BMC errors
# TODO(jjohnson2): strict mode to flag potential BMC errors
# raise iexc.BmcErrorException("Invalid/Unsupported FRU format")
# Ignore the internal use even if present.
self._parse_chassis()

View File

@@ -14,11 +14,11 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import pyghmi.constants as const
IPMI_BMC_ADDRESS = 0x20
IPMI_SEND_MESSAGE_CMD = 0x34
import pyghmi.constants as const
payload_types = {
'ipmi': 0x0,

View File

@@ -42,11 +42,11 @@ class ServerSession(ipmisession.Session):
# role = request[1]
self.clientsessionid = request[4:8]
# TODO(jbjohnso): intelligently handle integrity/auth/conf
#for now, forcibly do cipher suite 3
# for now, forcibly do cipher suite 3
self.managedsessionid = os.urandom(4)
#table 13-17, 1 for now (hmac-sha1), 3 should also be supported
#table 13-18, integrity, 1 for now is hmac-sha1-96, 4 is sha256
#confidentiality: 1 is aes-cbc-128, the only one
# table 13-17, 1 for now (hmac-sha1), 3 should also be supported
# table 13-18, integrity, 1 for now is hmac-sha1-96, 4 is sha256
# confidentiality: 1 is aes-cbc-128, the only one
self.privlevel = 4
response = (bytearray([clienttag, 0, self.privlevel, 0]) +
self.clientsessionid + self.managedsessionid +
@@ -97,7 +97,7 @@ class ServerSession(ipmisession.Session):
self.maxpriv = self.rolem & 0b111
namepresent = data[27]
if namepresent == 0:
#ignore null username for now
# ignore null username for now
return
self.username = bytes(data[28:])
if self.username.decode('utf-8') not in self.authdata:
@@ -152,7 +152,7 @@ class ServerSession(ipmisession.Session):
).digest()
authcode = struct.pack("%dB" % len(data[8:]), *data[8:])
if expectedauthcode != authcode:
#TODO(jjohnson2): RMCP error back at invalid rakp3
# TODO(jjohnson2): RMCP error back at invalid rakp3
return
clienttag = data[0]
if data[1] != 0:
@@ -207,7 +207,7 @@ class ServerSession(ipmisession.Session):
After the session inactivity timeout, this invalidate the client
session.
"""
#for now, we will have a non-configurable 60 second timeout
# for now, we will have a non-configurable 60 second timeout
pass
def _handle_channel_auth_cap(self, request):
@@ -226,9 +226,9 @@ class ServerSession(ipmisession.Session):
class IpmiServer(object):
#auth capabilities for now is a static payload
#for now always completion code 0, otherwise ignore
#authentication type fixed to ipmi2, ipmi1 forbidden
# auth capabilities for now is a static payload
# for now always completion code 0, otherwise ignore
# authentication type fixed to ipmi2, ipmi1 forbidden
# 0b10000000
def __init__(self, authdata, port=623, bmcuuid=None, address='::'):

View File

@@ -42,31 +42,37 @@ except AttributeError:
def dictitems(d):
return d.items()
initialtimeout = 0.5 # minimum timeout for first packet to retry in any given
# session. This will be randomized to stagger out retries
# in case of congestion
iothread = None # the thread in which all IO will be performed
# While the model as-is works fine for it's own coroutine
# structure, when combined with threading or something like
# eventlet, it becomes difficult for the calling code to cope
# This thread will tuck away the threading situation such that
# calling code doesn't have to do any gymnastics to cope with
# the nature of things.
iothreadready = False # whether io thread is yet ready to work
iothreadwaiters = [] # threads waiting for iothreadready
# minimum timeout for first packet to retry in any given
# session. This will be randomized to stagger out retries
# in case of congestion
initialtimeout = 0.5
# the thread in which all IO will be performed
# While the model as-is works fine for it's own coroutine
# structure, when combined with threading or something like
# eventlet, it becomes difficult for the calling code to cope
# This thread will tuck away the threading situation such that
# calling code doesn't have to do any gymnastics to cope with
# the nature of things.
iothread = None
# whether io thread is yet ready to work
iothreadready = False
# threads waiting for iothreadready
iothreadwaiters = []
ioqueue = collections.deque([])
myself = None
ipv6support = None
selectdeadline = 0
running = True
iosockets = [] # set of iosockets that will be shared amongst Session objects
MAX_BMCS_PER_SOCKET = 64 # no more than this many BMCs will share a socket
# this could be adjusted based on rmem_max
# value, leading to fewer filehandles
# set of iosockets that will be shared amongst Session objects
iosockets = []
# no more than this many BMCs will share a socket
# this could be adjusted based on rmem_max
# value, leading to fewer filehandles
MAX_BMCS_PER_SOCKET = 64
MAX_IDLE = 29 # maximum time to allow idle, more than this and BMC may assume
# incorrect idle
# maximum time to allow idle, more than this and BMC may assume
MAX_IDLE = 29
# incorrect idle
def define_worker():
@@ -134,6 +140,7 @@ def define_worker():
directediowaiters[workitem[2]].append(workitem)
else:
directediowaiters[workitem[2]] = [workitem]
return _IOWorker
@@ -160,7 +167,7 @@ def _io_wait(timeout, myaddr=None, evq=None):
def _io_sendto(mysocket, packet, sockaddr):
#Want sendto to act reasonably sane..
# Want sendto to act reasonably sane..
mysocket.setblocking(1)
if hasattr(mysocket, 'fd'):
mysocket = mysocket.fd
@@ -235,7 +242,7 @@ def _aespad(data):
currlen = len(data) + 1 # need to count the pad length field as well
neededpad = currlen % 16
if neededpad: # if it happens to be zero, hurray, but otherwise invert the
# sense of the padding
# sense of the padding
neededpad = 16 - neededpad
padval = 1
while padval <= neededpad:
@@ -279,11 +286,11 @@ class Session(object):
keepalive_sessions = {}
peeraddr_to_nodes = {}
iterwaiters = []
#NOTE(jbjohnso):
#socketpool is a mapping of sockets to usage count
# NOTE(jbjohnso):
# socketpool is a mapping of sockets to usage count
socketpool = {}
#this will be a lock. Delay the assignment so that a calling framework
#can do something like reassign our threading and select modules
# this will be a lock. Delay the assignment so that a calling framework
# can do something like reassign our threading and select modules
socketchecking = None
@classmethod
@@ -436,8 +443,8 @@ class Session(object):
self.cleaningup = False
self.lastpayload = None
self._customkeepalives = None
self.evq = collections.deque([]) # queue of events denoting line to
# run a cmd
# queue of events denoting line to run a cmd
self.evq = collections.deque([])
self.bmc = bmc
self.broken = False
# a private queue for packets for which this session handler
@@ -492,11 +499,11 @@ class Session(object):
self.logging = False
if self._customkeepalives:
for ka in list(self._customkeepalives):
# Be thorough and notify parties through their custom
# keepalives. In practice, this *should* be the same, but
# if a code somehow makes duplicate SOL handlers,
# this would notify all the handlers rather than just the
# last one to take ownership
# Be thorough and notify parties through their custom
# keepalives. In practice, this *should* be the same, but
# if a code somehow makes duplicate SOL handlers,
# this would notify all the handlers rather than just the
# last one to take ownership
self._customkeepalives[ka][1](
{'error': 'Session Disconnected'})
self._customkeepalives = None
@@ -568,10 +575,10 @@ class Session(object):
head = [constants.IPMI_BMC_ADDRESS,
constants.netfn_codes['application'] << 2]
check_sum = _checksum(*head)
#NOTE(fengqian): according IPMI Figure 14-11, rqSWID is set to 81h
# NOTE(fengqian): according IPMI Figure 14-11, rqSWID is set to 81h
boday = [0x81, self.seqlun, constants.IPMI_SEND_MESSAGE_CMD,
0x40 | channel]
#NOTE(fengqian): Track request
# NOTE(fengqian): Track request
self._add_request_entry((constants.netfn_codes['application'] + 1,
self.seqlun, constants.IPMI_SEND_MESSAGE_CMD))
return head + [check_sum] + boday
@@ -597,17 +604,17 @@ class Session(object):
"""
bridge_msg = []
self.expectedcmd = command
self.expectednetfn = netfn + \
1 # in ipmi, the response netfn is always one
# higher than the request payload, we assume
# we are always the requestor for now
# in ipmi, the response netfn is always one
self.expectednetfn = netfn + 1
# higher than the request payload, we assume
# we are always the requestor for now
seqincrement = 7 # IPMI spec forbids gaps bigger then 7 in seq number.
# Risk the taboo rather than violate the rules
# Risk the taboo rather than violate the rules
while (not self.servermode and
(netfn, command, self.seqlun) in self.tabooseq and
self.tabooseq[(netfn, command, self.seqlun)] and seqincrement):
self.tabooseq[(self.expectednetfn, command, self.seqlun)] -= 1
# Allow taboo to eventually expire after a few rounds
# Allow taboo to eventually expire after a few rounds
self.seqlun += 4 # the last two bits are lun, so add 4 to add 1
self.seqlun &= 0xff # we only have one byte, wrap when exceeded
seqincrement -= 1
@@ -616,7 +623,7 @@ class Session(object):
addr = bridge_request.get('addr', 0x0)
channel = bridge_request.get('channel', 0x0)
bridge_msg = self._make_bridge_request_msg(channel, netfn, command)
#NOTE(fengqian): For bridge request, rsaddr is specified and
# NOTE(fengqian): For bridge request, rsaddr is specified and
# rqaddr is BMC address.
rqaddr = constants.IPMI_BMC_ADDRESS
rsaddr = addr
@@ -625,7 +632,7 @@ class Session(object):
rsaddr = constants.IPMI_BMC_ADDRESS
if self.servermode:
rsaddr = self.clientaddr
#figure 13-4, first two bytes are rsaddr and
# figure 13-4, first two bytes are rsaddr and
# netfn, for non-bridge request, rsaddr is always 0x20 since we are
# addressing BMC while rsaddr is specified forbridge request
header = [rsaddr, netfn << 2]
@@ -636,7 +643,7 @@ class Session(object):
payload = header + [headsum] + reqbody + [bodysum]
if bridge_request:
payload = bridge_msg + payload
#NOTE(fengqian): For bridge request, another check sum is needed.
# NOTE(fengqian): For bridge request, another check sum is needed.
tail_csum = _checksum(*payload[3:])
payload.append(tail_csum)
@@ -714,12 +721,12 @@ class Session(object):
timeout = None
else: # if not retry, give it a second before surrending
timeout = 1
#The event loop is shared amongst pyghmi session instances
#within a process. In this way, synchronous usage of the interface
#plays well with asynchronous use. In fact, this produces the behavior
#of only the constructor needing a callback. From then on,
#synchronous usage of the class acts in a greenthread style governed by
#order of data on the network
# The event loop is shared amongst pyghmi session instances
# within a process. In this way, synchronous usage of the interface
# plays well with asynchronous use. In fact, this produces the
# behavior of only the constructor needing a callback. From then on,
# synchronous usage of the class acts in a greenthread style governed
# by order of data on the network
self.awaitresponse(retry, waitall)
lastresponse = self.lastresponse
self.incommand = False
@@ -759,12 +766,12 @@ class Session(object):
:param timeout: Specify a custom timeout for long-running request
"""
if payload and self.lastpayload:
# we already have a packet outgoing, make this
# a pending payload
# this way a simplistic BMC won't get confused
# and we also avoid having to do more complicated
# retry mechanism where each payload is
# retried separately
# we already have a packet outgoing, make this
# a pending payload
# this way a simplistic BMC won't get confused
# and we also avoid having to do more complicated
# retry mechanism where each payload is
# retried separately
self.pendingpayloads.append((payload, payload_type, retry))
return
if payload_type is None:
@@ -784,7 +791,7 @@ class Session(object):
if self.ipmiversion == 2.0:
message.append(payload_type)
if baretype == 2:
#TODO(jbjohnso): OEM payload types
# TODO(jbjohnso): OEM payload types
raise NotImplementedError("OEM Payloads")
elif baretype not in constants.payload_types.values():
raise NotImplementedError(
@@ -797,23 +804,22 @@ class Session(object):
message += self._ipmi15authcode(payload)
message.append(len(payload))
message += payload
totlen = 34 + \
len(message) # Guessing the ipmi spec means the whole
# packet and assume no tag in old 1.5 world
# Guessing the ipmi spec means the whole
totlen = 34 + len(message)
# packet and assume no tag in old 1.5 world
if totlen in (56, 84, 112, 128, 156):
message.append(0) # Legacy pad as mandated by ipmi spec
elif self.ipmiversion == 2.0:
psize = len(payload)
if self.confalgo:
pad = (
psize + 1) % 16 # pad has to cope with one byte field like
# the _aespad function
pad = (psize + 1) % 16 # pad has to cope with one byte
# field like the _aespad function
if pad: # if no pad needed, then we take no more action
pad = 16 - pad
newpsize = psize + pad + \
17 # new payload size grew according to pad
# size, plus pad length, plus 16 byte IV
#(Table 13-20)
# new payload size grew according to pad
newpsize = psize + pad + 17
# size, plus pad length, plus 16 byte IV
# (Table 13-20)
message.append(newpsize & 0xff)
message.append(newpsize >> 8)
iv = os.urandom(16)
@@ -830,26 +836,26 @@ class Session(object):
message.append(psize >> 8)
message += list(payload)
if self.integrityalgo: # see table 13-8,
# RMCP+ packet format
# TODO(jbjohnso): SHA256 which is now
# allowed
# RMCP+ packet format
# TODO(jbjohnso): SHA256 which is now
# allowed
neededpad = (len(message) - 2) % 4
if neededpad:
neededpad = 4 - neededpad
message += [0xff] * neededpad
message.append(neededpad)
message.append(7) # reserved, 7 is the required value for the
# specification followed
# specification followed
integdata = message[4:]
authcode = hmac.new(self.k1,
struct.pack("%dB" % len(integdata),
*integdata),
hashlib.sha1).digest()[:12] # SHA1-96
# per RFC2404 truncates to 96 bits
# per RFC2404 truncates to 96 bits
message += struct.unpack("12B", authcode)
self.netpacket = struct.pack("!%dB" % len(message), *message)
#advance idle timer since we don't need keepalive while sending packets
#out naturally
# advance idle timer since we don't need keepalive while sending
# packets out naturally
if (self in Session.keepalive_sessions and not needskeepalive and
not self._customkeepalives):
Session.keepalive_sessions[self]['timeout'] = _monotonic_time() + \
@@ -857,10 +863,11 @@ class Session(object):
self._xmit_packet(retry, delay_xmit=delay_xmit, timeout=timeout)
def _ipmi15authcode(self, payload, checkremotecode=False):
#checkremotecode is used to verify remote code,
#otherwise this function is used to general authcode for local
if self.authtype == 0: # Only for things before auth in ipmi 1.5, not
# like 2.0 cipher suite 0
# checkremotecode is used to verify remote code,
# otherwise this function is used to general authcode for local
if self.authtype == 0:
# Only for things before auth in ipmi 1.5, not
# like 2.0 cipher suite 0
return ()
password = self.password
padneeded = 16 - len(password)
@@ -920,13 +927,14 @@ class Session(object):
self.sessionid = struct.unpack("<I", struct.pack("4B", *data[0:4]))[0]
self.authtype = 2
self._activate_session(data[4:])
# NOTE(jbjohnso):
# This sends the activate session payload. We pick '1' as the requested
# sequence number without perturbing our real sequence number
def _activate_session(self, data):
rqdata = [2, 4] + list(data) + [1, 0, 0, 0]
# TODO(jbjohnso): this always requests admin level (1.5)
# TODO(jbjohnso): this always requests admin level (1.5)
self.ipmicallback = self._activated_session
self._send_ipmi_net_payload(netfn=0x6, command=0x3a, data=rqdata)
@@ -981,8 +989,9 @@ class Session(object):
def _open_rmcpplus_request(self):
self.authtype = 6
self.localsid += 1 # have unique local session ids to ignore aborted
# login attempts from the past
# have unique local session ids to ignore aborted
# login attempts from the past
self.localsid += 1
self.rmcptag += 1
data = [
self.rmcptag,
@@ -994,7 +1003,7 @@ class Session(object):
0, 0, 0, 8, 1, 0, 0, 0, # table 13-17, SHA-1
1, 0, 0, 8, 1, 0, 0, 0, # SHA-1 integrity
2, 0, 0, 8, 1, 0, 0, 0, # AES privacy
#2,0,0,8,0,0,0,0, #no privacy confalgo
# 2,0,0,8,0,0,0,0, #no privacy confalgo
]
self.sessioncontext = 'OPENSESSION'
self.send_payload(
@@ -1035,12 +1044,12 @@ class Session(object):
unspecified, will autodetect based on earliest timeout
"""
global iosockets
#Assume:
#Instance A sends request to packet B
#Then Instance C sends request to BMC D
#BMC D was faster, so data comes back before BMC B
#Instance C gets to go ahead of Instance A, because
#Instance C can get work done, but instance A cannot
# Assume:
# Instance A sends request to packet B
# Then Instance C sends request to BMC D
# BMC D was faster, so data comes back before BMC B
# Instance C gets to go ahead of Instance A, because
# Instance C can get work done, but instance A cannot
curtime = _monotonic_time()
# There ar a number of parties that each has their own timeout
@@ -1098,12 +1107,11 @@ class Session(object):
session._keepalive()
for session, parms in dictitems(cls.waiting_sessions):
if parms['timeout'] < curtime: # timeout has expired, time to
# give up on it and trigger timeout
# response in the respective
# session
sessionstodel.append(
session) # defer deletion until after loop
# to avoid confusing the for loop
# give up on it and trigger timeout
# response in the respective session
# defer deletion until after loop
sessionstodel.append(session)
# to avoid confusing the for loop
for session in sessionstodel:
cls.waiting_sessions.pop(session, None)
# one loop iteration to make sure recursion doesn't induce redundant
@@ -1113,7 +1121,7 @@ class Session(object):
return len(cls.waiting_sessions)
def register_keepalive(self, cmd, callback):
'''Register custom keepalive IPMI command
"""Register custom keepalive IPMI command
This is mostly intended for use by the console code.
calling code would have an easier time just scheduling in their
@@ -1124,7 +1132,7 @@ class Session(object):
:param callback: A function to be called with results of the keepalive
:returns: value to identify registration for unregister_keepalive
'''
"""
regid = random.random()
if self._customkeepalives is None:
self._customkeepalives = {regid: (cmd, callback)}
@@ -1198,8 +1206,8 @@ class Session(object):
sockaddr is not None and
self.sockaddr != sockaddr):
return # here, we might have sent an ipv4 and ipv6 packet to kick
# things off ignore the second reply since we have one
# satisfactory answer
# things off ignore the second reply since we have one
# satisfactory answer
if data[4] in (0, 2): # This is an ipmi 1.5 paylod
remsequencenumber = struct.unpack('<I', bytes(data[5:9]))[0]
remsessid = struct.unpack("<I", bytes(data[9:13]))[0]
@@ -1218,9 +1226,10 @@ class Session(object):
return -5 # remote sequence number is too low, reject it
self.remsequencenumber = remsequencenumber
if data[4] != self.authtype:
return -2 # BMC responded with mismatch authtype, for
# mutual authentication reject it. If this causes
# legitimate issues, it's the vendor's fault
# BMC responded with mismatch authtype, for
# mutual authentication reject it. If this causes
# legitimate issues, it's the vendor's fault
return -2
if remsessid != self.sessionid:
return -1 # does not match our session id, drop it
@@ -1231,7 +1240,7 @@ class Session(object):
if data[4] == 2: # we have authcode in this ipmi 1.5 packet
authcode = data[13:29]
del rsp[13:29]
# this is why we needed a mutable representation
# this is why we needed a mutable representation
payload = list(rsp[14:14 + rsp[13]])
if authcode:
expectedauthcode = self._ipmi15authcode(payload,
@@ -1251,7 +1260,7 @@ class Session(object):
pass
def _got_rakp3(self, data):
#stub, client sessions ignore rakp3
# stub, client sessions ignore rakp3
pass
def _got_rmcp_openrequest(self, data):
@@ -1277,8 +1286,8 @@ class Session(object):
# If endorsing a shared secret scheme, then at the very least it
# needs to do mutual assurance
if not (data[5] & 0b01000000): # This would be the line that might
# trip up some insecure BMC
# implementation
# trip up some insecure BMC
# implementation
return
encrypted = 0
if data[5] & 0b10000000:
@@ -1378,9 +1387,10 @@ class Session(object):
def _got_rakp2(self, data):
if not (self.sessioncontext in ('EXPECTINGRAKP2', 'EXPECTINGRAKP4')):
return -9 # if we are not expecting rakp2, ignore. In a retry
# scenario, replying from stale RAKP2 after sending
# RAKP3 seems to be best
# if we are not expecting rakp2, ignore. In a retry
# scenario, replying from stale RAKP2 after sending
# RAKP3 seems to be best
return -9
if data[0] != self.rmcptag: # ignore mismatched tags for retry logic
return -9
if data[1] != 0: # if not successful, consider next move
@@ -1390,8 +1400,9 @@ class Session(object):
self.privlevel = 3
self.login()
return
if data[1] == 2: # invalid sessionid 99% of the time means a retry
# scenario invalidated an in-flight transaction
# invalid sessionid 99% of the time means a retry
# scenario invalidated an in-flight transaction
if data[1] == 2:
return
if data[1] in constants.rmcp_codes:
errstr = constants.rmcp_codes[data[1]]
@@ -1456,12 +1467,13 @@ class Session(object):
return -9
if data[1] != 0:
if data[1] == 2 and self.logontries: # if we retried RAKP3 because
# RAKP4 got dropped, BMC can consider it done and we must
# restart
# RAKP4 got dropped, BMC can consider it done and we must
# restart
self._relog()
if data[1] == 15 and self.logontries: # ignore 15 value if we are
# retrying. xCAT did but I can't recall why exactly
# TODO(jbjohnso) jog my memory to update the comment
# ignore 15 value if we are retrying.
# xCAT did but I can't recall why exactly
if data[1] == 15 and self.logontries:
# TODO(jbjohnso) jog my memory to update the comment
return
if data[1] in constants.rmcp_codes:
errstr = constants.rmcp_codes[data[1]]
@@ -1506,18 +1518,18 @@ class Session(object):
if self._lookup_request_entry(entry):
self._remove_request_entry(entry)
#NOTE(fengqian): for bridge request, we need to handle the response
#twice. First response shows if message send correctly, second
#response is the real response.
#If the message is send crrectly, we will discard the first
#response or else error message will be parsed and return.
if ((entry[0] in [0x06, 0x07]) and (entry[2] == 0x34)
and (payload[-2] == 0x0)):
# NOTE(fengqian): for bridge request, we need to handle the
# response twice. First response shows if message send correctly,
# second response is the real response.
# If the message is send crrectly, we will discard the first
# response or else error message will be parsed and return.
if ((entry[0] in [0x06, 0x07]) and (entry[2] == 0x34) and
(payload[-2] == 0x0)):
return -1
else:
self._parse_payload(payload)
#NOTE(fengqian): recheck if the certain entry is removed in
#case that bridge request failed.
# NOTE(fengqian): recheck if the certain entry is removed in
# case that bridge request failed.
if self.request_entry:
self._remove_request_entry((self.expectednetfn,
self.seqlun, self.expectedcmd))
@@ -1529,25 +1541,28 @@ class Session(object):
def _parse_payload(self, payload):
if hasattr(self, 'hasretried') and self.hasretried:
self.hasretried = 0
# try to skip it for at most 16 cycles of overflow
self.tabooseq[
(self.expectednetfn, self.expectedcmd, self.seqlun)] = 16
# try to skip it for at most 16 cycles of overflow
# We want to now remember that we do not have an expected packet
self.expectednetfn = 0x1ff # bigger than one byte means it can never
# match the one byte value by mistake
# bigger than one byte means it can never match the one byte value
# by mistake
self.expectednetfn = 0x1ff
self.expectedcmd = 0x1ff
if not self.servermode:
self.seqlun += 4 # prepare seqlun for next transmit
self.seqlun &= 0xff # when overflowing, wrap around
Session.waiting_sessions.pop(self, None)
self.lastpayload = None # render retry mechanism utterly incapable of
# doing anything, though it shouldn't matter
# render retry mechanism utterly incapable of
# doing anything, though it shouldn't matter
self.lastpayload = None
self.last_payload_type = None
response = {}
response['netfn'] = payload[1] >> 2
del payload[0:5]
# ^^ remove header of rsaddr/netfn/lun/checksum/rq/seq/lun
del payload[-1] # remove the trailing checksum
del payload[0:5]
# remove the trailing checksum
del payload[-1]
response['command'] = payload[0]
if self.servermode:
del payload[0:1]
@@ -1593,21 +1608,21 @@ class Session(object):
self.lastpayload = None
self._relog()
else: # in IPMI case, the only recourse is to act as if the packet is
# idempotent. SOL has more sophisticated retry handling
# the biggest risks are reset sp which is often fruitless to retry
# and chassis reset, which sometimes will shoot itself
# systematically in the head in a shared port case making replies
# impossible
# idempotent. SOL has more sophisticated retry handling
# the biggest risks are reset sp which is often fruitless to retry
# and chassis reset, which sometimes will shoot itself
# systematically in the head in a shared port case making replies
# impossible
self.hasretried = 1 # remember so that we can track taboo
# combinations
# of sequence number, netfn, and lun due to
# ambiguity on the wire
# combinations
# of sequence number, netfn, and lun due to
# ambiguity on the wire
self.send_payload()
self.nowait = False
def _xmit_packet(self, retry=True, delay_xmit=None, timeout=None):
if self.sequencenumber: # seq number of zero will be left alone, it is
# special, otherwise increment
# special, otherwise increment
self.sequencenumber += 1
if delay_xmit is not None:
Session.waiting_sessions[self] = {}
@@ -1617,8 +1632,9 @@ class Session(object):
return # skip transmit, let retry timer do it's thing
if self.sockaddr:
_io_sendto(self.socket, self.netpacket, self.sockaddr)
else: # he have not yet picked a working sockaddr for this connection,
# try all the candidates that getaddrinfo provides
else:
# he have not yet picked a working sockaddr for this connection,
# try all the candidates that getaddrinfo provides
self.allsockaddrs = []
try:
for res in socket.getaddrinfo(self.bmc,
@@ -1667,6 +1683,7 @@ class Session(object):
if __name__ == "__main__":
import sys
ipmis = Session(bmc=sys.argv[1],
userid=sys.argv[2],
password=os.environ['IPMIPASS'])

View File

@@ -84,8 +84,8 @@ def get_ipmi_error(response, suffix=""):
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)]):
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:

View File

@@ -46,8 +46,8 @@ def ones_complement(value, bits):
# complement prevalent in ipmi spec
signbit = 0b1 << (bits - 1)
if value & signbit:
#if negative, subtract 1, then take 1s
#complement given bits width
# if negative, subtract 1, then take 1s
# complement given bits width
return 0 - (value ^ ((0b1 << bits) - 1))
else:
return value
@@ -58,8 +58,8 @@ def twos_complement(value, bits):
# complement prevalent in ipmi spec
signbit = 0b1 << (bits - 1)
if value & signbit:
#if negative, subtract 1, then take 1s
#complement given bits width
# if negative, subtract 1, then take 1s
# complement given bits width
return 0 - ((value - 1) ^ ((0b1 << bits) - 1))
else:
return value
@@ -261,7 +261,7 @@ class SDREntry(object):
raise NotImplementedError
self.rectype = entrybytes[3]
self.linearization = None
#most important to get going are 1, 2, and 11
# most important to get going are 1, 2, and 11
self.sdrtype = TYPE_SENSOR # assume a sensor
if self.rectype == 1: # full sdr
self.full_decode(entrybytes[5:])
@@ -312,7 +312,7 @@ class SDREntry(object):
def association_decode(self, entry):
# table 43-4 Entity Associaition Record
#TODO(jbjohnso): actually represent this data
# TODO(jbjohnso): actually represent this data
self.sdrtype = TYPE_UNKNOWN
def compact_decode(self, entry):
@@ -337,10 +337,10 @@ class SDREntry(object):
except KeyError:
self.sensor_type = "UNKNOWN type " + str(entry[7])
self.reading_type = entry[8] # table 42-1
# 0: unspecified
# 1: generic threshold based
# 0x6f: discrete sensor-specific from table 42-3, sensor offsets
# all others per table 42-2, generic discrete
# 0: unspecified
# 1: generic threshold based
# 0x6f: discrete sensor-specific from table 42-3, sensor offsets
# all others per table 42-2, generic discrete
# numeric format is one of:
# 0 - unsigned, 1 - 1s complement, 2 - 2s complement, 3 - ignore number
# compact records are supposed to always write it as '3', presumably
@@ -371,8 +371,8 @@ class SDREntry(object):
self.modunit
def full_decode(self, entry):
#offsets are table from spec, minus 6
#TODO(jbjohnso): table 43-13, put in constants to interpret entry[3]
# offsets are table from spec, minus 6
# TODO(jbjohnso): table 43-13, put in constants to interpret entry[3]
self._common_decode(entry)
# now must extract the formula data to transform values
# entry[18 to entry[24].
@@ -543,7 +543,7 @@ class SDREntry(object):
(entry[4] & 0b11110000) << 2
self.accuracyexp = (entry[4] & 0b1100) >> 2
self.direction = entry[4] & 0b11
#0 = n/a, 1 = input, 2 = output
# 0 = n/a, 1 = input, 2 = output
self.resultexponent = twos_complement((entry[5] & 0b11110000) >> 4, 4)
bexponent = twos_complement(entry[5] & 0b1111, 4)
# might as well do the math to 'b' now rather than wait for later
@@ -594,7 +594,7 @@ class SDR(object):
self.read_info()
def read_info(self):
#first, we want to know the device id
# first, we want to know the device id
rsp = self.ipmicmd.xraw_command(netfn=6, command=1)
rsp['data'] = bytearray(rsp['data'])
self.device_id = rsp['data'][0]
@@ -618,9 +618,10 @@ class SDR(object):
# be able to proceed
# However at the moment, we haven't done so
raise NotImplementedError
return # We have Device SDR, without SDR Repository device, but
# also without sensor device support, no idea how to
# continue
return
# We have Device SDR, without SDR Repository device, but
# also without sensor device support, no idea how to
# continue
self.get_sdr()
def get_sdr_reservation(self):
@@ -636,14 +637,14 @@ class SDR(object):
# 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'
# 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
# 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
# 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

View File

@@ -16,7 +16,10 @@
# sake of typical internal management devices. Compatibility back to python
# 2.6 as is found in commonly used enterprise linux distributions.
__author__ = 'jjohnson2'
import json
import pyghmi.exceptions as pygexc
import socket
import ssl
try:
import Cookie
@@ -24,10 +27,8 @@ try:
except ImportError:
import http.client as httplib
import http.cookies as Cookie
import json
import pyghmi.exceptions as pygexc
import socket
import ssl
__author__ = 'jjohnson2'
class SecureHTTPConnection(httplib.HTTPConnection, object):