2
0
mirror of https://opendev.org/x/pyghmi synced 2025-01-27 19:37:44 +00:00

Provide attach remote media function

Provide a function for indicating a remote media by URI.
Implements the underpinniings with respect to MegaRAC
underpinnings.

Change-Id: Icbaa8f91fad26632834335cc29eff57d14ee6450
This commit is contained in:
Jarrod Johnson 2016-03-22 16:52:07 -04:00
parent ddbbe53f78
commit 7d782e7cd5
7 changed files with 194 additions and 1 deletions

View File

@ -42,3 +42,9 @@ class InvalidParameterValue(PyghmiException):
class BmcErrorException(IpmiException):
# This denotes when library detects an invalid BMC behavior
pass
class UnsupportedFunctionality(PyghmiException):
# Indicates when functionality is requested that is not supported by
# current endpoint
pass

View File

@ -1709,3 +1709,18 @@ class Command(object):
"""Get graphical console launcher"""
self.oem_init()
return self._oem.get_graphical_console()
def attach_remote_media(self, url, username=None, password=None):
"""Attach remote media by url
Given a url, attach remote media (cd/usb image) to the target system.
:param url: URL to indicate where to find image (protocol support
varies by BMC)
:param username: Username for endpoint to use when accessing the URL.
If applicable, 'domain' would be indicated by '@' or
'\' syntax.
:param password: Password for endpoint to use when accessing the URL.
"""
self.oem_init()
self._oem.attach_remote_media(url, username, password)

View File

@ -14,6 +14,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import pyghmi.exceptions as exc
class OEMHandler(object):
"""Handler class for OEM capabilities.
@ -221,3 +223,6 @@ class OEMHandler(object):
:param netdata: Dictionary to store additional network data
"""
return
def attach_remote_media(self, imagename, username, password):
raise exc.UnsupportedFunctionality()

View File

@ -40,6 +40,7 @@ from pyghmi.ipmi.oem.lenovo import raid_drive
import pyghmi.util.webclient as wc
import socket
import struct
inventory.register_inventory_category(cpu)
inventory.register_inventory_category(dimm)
@ -114,6 +115,12 @@ led_status = {
led_status_default = "Blink"
def _megarac_abbrev_image(name):
if len(name) <= 16:
return name
return name[:16] + name[-4:]
class OEMHandler(generic.OEMHandler):
# noinspection PyUnusedLocal
def __init__(self, oemid, ipmicmd):
@ -121,6 +128,7 @@ class OEMHandler(generic.OEMHandler):
# variations. For example System X versus Thinkserver
self.oemid = oemid
self.ipmicmd = ipmicmd
self._has_megarac = None
self.oem_inventory_info = None
def get_video_launchdata(self):
@ -543,3 +551,128 @@ class OEMHandler(generic.OEMHandler):
ipv6str = ':'.join([ipv6str[x:x+4] for x in xrange(0, 32, 4)])
netdata['ipv6_addresses'] = [
'{0}/{1}'.format(ipv6str, ipv6_prefix)]
@property
def has_megarac(self):
# if there is functionality that is the same for tsm or generic
# megarac, then this is appropriate. If there's a TSM specific
# preferred, use has_tsm first
if self._has_megarac is not None:
return self._has_megarac
self._has_megarac = False
try:
rsp = self.ipmicmd.xraw_command(netfn=0x32, command=0x7e)
# We don't have a handy classify-only, so use get sel policy
# rsp should have a length of one, and be either '\x00' or '\x01'
if len(rsp['data'][:]) == 1 and rsp['data'][0] in ('\x00', '\x01'):
self._has_megarac = True
except pygexc.IpmiException:
pass # Means that it's not going to be a megarac
return self._has_megarac
def _set_short_ris_string(self, selector, value):
data = (1, selector, 0) + struct.unpack('{0}B'.format(len(value)),
value)
self.ipmicmd.xraw_command(netfn=0x32, command=0x9f, data=data)
def _set_ris_string(self, selector, value):
if len(value) > 256:
raise pygexc.UnsupportedFunctionality(
'Value exceeds 256 characters: {0}'.format(value))
padded = value + (256 - len(value)) * '\x00'
padded = list(struct.unpack('256B', padded))
# 8 = RIS, 4 = hd, 2 = fd, 1 = cd
try: # try and clear in-progress if left incomplete
self.ipmicmd.xraw_command(netfn=0x32, command=0x9f,
data=(1, selector, 0, 0))
except pygexc.IpmiException:
pass
# set in-progress
self.ipmicmd.xraw_command(netfn=0x32, command=0x9f,
data=(1, selector, 0, 1))
# now do the set
for x in xrange(0, 256, 64):
currdata = padded[x:x+64]
currchunk = x // 64 + 1
cmddata = [1, selector, currchunk] + currdata
self.ipmicmd.xraw_command(netfn=0x32, command=0x9f, data=cmddata)
# unset in-progress
self.ipmicmd.xraw_command(netfn=0x32, command=0x9f,
data=(1, selector, 0, 0))
def _megarac_fetch_image_shortnames(self):
rsp = self.ipmicmd.xraw_command(netfn=0x32, command=0xd8,
data=(7, 1, 0))
imgnames = rsp['data'][1:]
shortnames = []
for idx in xrange(0, len(imgnames), 22):
shortnames.append(imgnames[idx+2:idx+22].rstrip('\0'))
return shortnames
def _megarac_media_waitforready(self, imagename):
# first, we have, sadly, a 10 second grace period for some invisible
# async activity to get far enough long to monitor
self.ipmicmd.ipmi_session.pause(10)
risenabled = '\x00'
mountok = '\xff'
while risenabled != '\x01':
risenabled = self.ipmicmd.xraw_command(
netfn=0x32, command=0x9e, data=(8, 10))['data'][2]
while mountok == '\xff':
mountok = self.ipmicmd.xraw_command(
netfn=0x32, command=0x9e, data=(1, 8))['data'][2]
targshortname = _megarac_abbrev_image(imagename)
shortnames = self._megarac_fetch_image_shortnames()
while targshortname not in shortnames:
self.ipmicmd.wait_for_rsp(1)
shortnames = self._megarac_fetch_image_shortnames()
self.ipmicmd.ipmi_session.pause(10)
try:
self.ipmicmd.xraw_command(netfn=0x32, command=0xa0, data=(1, 0))
self.ipmicmd.ipmi_session.pause(5)
except pygexc.IpmiException:
pass
def _megarac_attach_media(self, proto, username, password, imagename,
domain, path, host):
# First we must ensure that the RIS is actually enabled
self.ipmicmd.xraw_command(netfn=0x32, command=0x9f, data=(8, 10, 0, 1))
if username is not None:
self._set_ris_string(3, username)
if password is not None:
self._set_short_ris_string(4, password)
if domain is not None:
self._set_ris_string(6, domain)
self._set_ris_string(1, path)
ip = util.get_ipv4(host)[0]
self._set_short_ris_string(2, ip)
self._set_short_ris_string(5, proto)
# now to restart RIS to have changes take effect...
self.ipmicmd.xraw_command(netfn=0x32, command=0x9f, data=(8, 11))
# now to kick off the requested mount
self._megarac_media_waitforready(imagename)
self._set_ris_string(0, imagename)
self.ipmicmd.xraw_command(netfn=0x32, command=0xa0,
data=(1, 1))
def attach_remote_media(self, url, username, password):
if self.has_megarac:
proto, host, path = util.urlsplit(url)
if proto == 'smb':
proto = 'cifs'
domain = None
path, imagename = path.rsplit('/', 1)
if username is not None and '@' in username:
username, domain = username.split('@', 1)
elif username is not None and '\\' in username:
domain, username = username.split('\\', 1)
try:
self._megarac_attach_media(proto, username, password,
imagename, domain, path, host)
except pygexc.IpmiException as ie:
if ie.ipmicode in (0x92, 0x99):
# if starting from scratch, this can happen...
self._megarac_attach_media(proto, username, password,
imagename, domain, path, host)
else:
raise

View File

@ -21,6 +21,8 @@ import pyghmi.ipmi.oem.lenovo.handler as lenovo
oemmap = {
20301: lenovo, # IBM x86 (and System X at Lenovo)
19046: lenovo, # Lenovo x86 (e.g. Thinkserver)
7154: lenovo, # Technically, standard IPMI, but give lenovo a chance
# to check for MegaRAC
}

View File

@ -678,7 +678,7 @@ class Session(object):
raise exc.IpmiException('Session no longer connected')
return lastresponse
def _send_ipmi_net_payload(self, netfn=None, command=None, data=[], code=0,
def _send_ipmi_net_payload(self, netfn=None, command=None, data=(), code=0,
bridge_request=None,
retry=None, delay_xmit=None, timeout=None):
if retry is None:
@ -966,6 +966,12 @@ class Session(object):
self._initsession()
self._get_channel_auth_cap()
@classmethod
def pause(cls, timeout):
starttime = _monotonic_time()
while _monotonic_time() - starttime < timeout:
cls.wait_for_rsp(timeout - (_monotonic_time() - starttime))
@classmethod
def wait_for_rsp(cls, timeout=None, callout=True):
"""IPMI Session Event loop iteration

View File

@ -14,6 +14,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import socket
import struct
@ -29,3 +30,28 @@ def decode_wireformat_uuid(rawguid):
bebytes = struct.unpack_from('>HHI', buffer(rawguid[8:]))
return '{0:04X}-{1:02X}-{2:02X}-{3:02X}-{4:02X}{5:04X}'.format(
lebytes[0], lebytes[1], lebytes[2], bebytes[0], bebytes[1], bebytes[2])
def urlsplit(url):
"""Split an arbitrary url into protocol, host, rest
The standard urlsplit does not want to provide 'netloc' for arbitrary
protocols, this works around that.
:param url: The url to split into component parts
"""
proto, rest = url.split(':', 1)
host = ''
if rest[:2] == '//':
host, rest = rest[2:].split('/', 1)
rest = '/' + rest
return proto, host, rest
def get_ipv4(hostname):
"""Get list of ipv4 addresses for hostname
"""
addrinfo = socket.getaddrinfo(hostname, None, socket.AF_INET,
socket.SOCK_STREAM)
return [addrinfo[x][4][0] for x in xrange(len(addrinfo))]