mirror of
https://github.com/xcat2/confluent.git
synced 2024-11-26 19:40:12 +00:00
184727408a
There has been some confusion when configbmc changes are deferred until later. Reduce confusion by waiting for the settings to take effect, but avoiding checking each parameter to preserve most of the speedup.
415 lines
14 KiB
Plaintext
415 lines
14 KiB
Plaintext
# 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.
|
|
|
|
import argparse
|
|
import ctypes
|
|
import fcntl
|
|
import json
|
|
from select import select
|
|
import socket
|
|
import struct
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import time
|
|
|
|
if os.path.exists('/opt/confluent/bin/apiclient'):
|
|
apiclient = '/opt/confluent/bin/apiclient'
|
|
elif os.path.exists('/etc/confluent/apiclient'):
|
|
apiclient = '/etc/confluent/apiclient'
|
|
|
|
class IpmiMsg(ctypes.Structure):
|
|
_fields_ = [('netfn', ctypes.c_ubyte),
|
|
('cmd', ctypes.c_ubyte),
|
|
('data_len', ctypes.c_short),
|
|
('data', ctypes.POINTER(ctypes.c_ubyte))]
|
|
|
|
|
|
class IpmiSystemInterfaceAddr(ctypes.Structure):
|
|
_fields_ = [('addr_type', ctypes.c_int),
|
|
('channel', ctypes.c_short),
|
|
('lun', ctypes.c_ubyte)]
|
|
|
|
|
|
class IpmiRecv(ctypes.Structure):
|
|
_fields_ = [('recv_type', ctypes.c_int),
|
|
('addr', ctypes.POINTER(IpmiSystemInterfaceAddr)),
|
|
('addr_len', ctypes.c_uint),
|
|
('msgid', ctypes.c_long),
|
|
('msg', IpmiMsg)]
|
|
|
|
|
|
class IpmiReq(ctypes.Structure):
|
|
_fields_ = [('addr', ctypes.POINTER(IpmiSystemInterfaceAddr)),
|
|
('addr_len', ctypes.c_uint),
|
|
('msgid', ctypes.c_long),
|
|
('msg', IpmiMsg)]
|
|
|
|
|
|
_IONONE = 0
|
|
_IOWRITE = 1
|
|
_IOREAD = 2
|
|
IPMICTL_SET_MY_ADDRESS_CMD = (
|
|
_IOREAD << 30 | ctypes.sizeof(ctypes.c_uint) << 16
|
|
| ord('i') << 8 | 17) # from ipmi.h
|
|
IPMICTL_SEND_COMMAND = (
|
|
_IOREAD << 30 | ctypes.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 | ctypes.sizeof(IpmiRecv) << 16
|
|
| ord('i') << 8 | 11) # from ipmi.h
|
|
BMC_SLAVE_ADDR = ctypes.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, 'r+')
|
|
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 = ctypes.create_string_buffer(4096)
|
|
self.req = IpmiReq()
|
|
self.rsp = IpmiRecv()
|
|
self.addr = IpmiSystemInterfaceAddr()
|
|
self.req.msg.data = ctypes.cast(
|
|
ctypes.addressof(self.databuffer),
|
|
ctypes.POINTER(ctypes.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)
|
|
|
|
def pause(self, seconds):
|
|
time.sleep(seconds)
|
|
|
|
@property
|
|
def parsed_rsp(self):
|
|
response = {'netfn': self.rsp.msg.netfn, 'command': self.rsp.msg.cmd,
|
|
'code': bytearray(self.databuffer.raw)[0],
|
|
'data': bytearray(
|
|
self.databuffer.raw[1:self.rsp.msg.data_len])}
|
|
return response
|
|
|
|
def await_config(s, bmccfg, channel):
|
|
vlan = bmccfg.get('bmcvlan', None)
|
|
ipv4 = bmccfg.get('bmcipv4', None)
|
|
prefix = bmccfg.get('prefixv4', None)
|
|
gw = bmccfg.get('bmcgw', None)
|
|
|
|
|
|
|
|
def raw_command(self,
|
|
netfn,
|
|
command,
|
|
data=(),
|
|
bridge_request=None,
|
|
retry=True,
|
|
delay_xmit=None,
|
|
timeout=None,
|
|
waitall=False, rslun=0):
|
|
self.addr.channel = CURRCHAN
|
|
self.addr.addr_type = ADDRTYPE
|
|
self.addr.lun = rslun
|
|
self.req.addr_len = ctypes.sizeof(IpmiSystemInterfaceAddr)
|
|
self.req.addr = ctypes.pointer(self.addr)
|
|
self.req.msg.netfn = netfn
|
|
self.req.msg.cmd = command
|
|
if data:
|
|
data = memoryview(bytearray(data))
|
|
try:
|
|
self.databuffer[:len(data)] = data[:len(data)]
|
|
except ValueError:
|
|
self.databuffer[:len(data)] = data[:len(data)].tobytes()
|
|
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 = ctypes.pointer(self.addr)
|
|
self.rsp.addr_len = ctypes.sizeof(IpmiSystemInterfaceAddr)
|
|
fcntl.ioctl(self.ipmidev, IPMICTL_RECV, self.rsp)
|
|
return self.parsed_rsp
|
|
|
|
|
|
def _is_tsm(model):
|
|
return model in ('7y00', '7z01', '7y98', '7y99')
|
|
|
|
def set_port(s, port, vendor, model):
|
|
oport = port
|
|
if vendor not in ('IBM', 'Lenovo'):
|
|
raise Exception('{0} not implemented'.format(vendor))
|
|
if _is_tsm(model):
|
|
return set_port_tsm(s, port, model)
|
|
else:
|
|
set_port_xcc(s, port, model)
|
|
return 1
|
|
|
|
|
|
def set_port_tsm(s, port, model):
|
|
oport = port
|
|
sys.stdout.write('Setting TSM port to "{}"...'.format(oport))
|
|
sys.stdout.flush()
|
|
if port == 'ocp':
|
|
s.raw_command(0x32, 0x71, b'\x00\x01\x00')
|
|
elif port == 'dedicated':
|
|
s.raw_command(0x32, 0x71, b'\x00\x00\x00')
|
|
else:
|
|
raise Exception("Unsupported port for TSM")
|
|
timer = 15
|
|
while timer:
|
|
timer = timer - 1
|
|
time.sleep(1.0)
|
|
sys.stdout.write('.')
|
|
sys.stdout.flush()
|
|
if port == 'ocp':
|
|
iface = 0
|
|
s.raw_command(0x32, 0x71, b'\x00\x00\x03')
|
|
elif port == 'dedicated':
|
|
iface = 1
|
|
s.raw_command(0x32, 0x71, b'\x00\x01\x03')
|
|
rsp = s.raw_command(0x32, 0x72, bytearray([4, iface, 0]))
|
|
print('Complete')
|
|
return int(rsp['data'][0])
|
|
|
|
|
|
def set_port_xcc(s, port, model):
|
|
oport = port
|
|
if port.lower() == 'dedicated':
|
|
port = b'\x01'
|
|
elif port.lower() in ('ml2', 'ocp'):
|
|
port = b'\x02\x00'
|
|
elif port.lower() == 'lom':
|
|
if model == '7x58':
|
|
port = b'\x00\x02'
|
|
else:
|
|
port = b'\x00\x00'
|
|
else:
|
|
port = port.split(' ')
|
|
port = bytes(bytearray([int(x) for x in port]))
|
|
currport = bytes(s.raw_command(0xc, 2, b'\x01\xc0\x00\x00')['data'][1:])
|
|
if port == currport:
|
|
sys.stdout.write('XCC port already set to "{}"\n'.format(oport))
|
|
return
|
|
sys.stdout.write('Setting XCC port to "{}"...'.format(oport))
|
|
sys.stdout.flush()
|
|
s.raw_command(0xc, 1, b'\x01\xc0' + port)
|
|
tries = 60
|
|
while currport != port and tries:
|
|
tries -= 1
|
|
time.sleep(0.5)
|
|
sys.stdout.write('.')
|
|
sys.stdout.flush()
|
|
currport = bytes(s.raw_command(0xc, 2, b'\x01\xc0\x00\x00')['data'][1:])
|
|
if not tries:
|
|
raise Exception('Timeout attempting to set port')
|
|
sys.stdout.write('Complete\n')
|
|
|
|
|
|
def check_vlan(s, vlan, channel):
|
|
if vlan == 'off':
|
|
vlan = b'\x00\x00'
|
|
else:
|
|
vlan = int(vlan)
|
|
if vlan:
|
|
vlan = vlan | 32768
|
|
vlan = struct.pack('<H', vlan)
|
|
currvlan = bytes(s.raw_command(0xc, 2, bytearray([channel, 0x14 ,0, 0]))['data'][1:])
|
|
if bytearray(currvlan)[1] & 0b10000000 == 0:
|
|
currvlan = b'\x00\x00'
|
|
return currvlan == vlan
|
|
|
|
|
|
def set_vlan(s, vlan, channel):
|
|
ovlan = vlan
|
|
if vlan == 'off':
|
|
vlan = b'\x00\x00'
|
|
else:
|
|
vlan = int(vlan)
|
|
if vlan:
|
|
vlan = vlan | 32768
|
|
vlan = struct.pack('<H', vlan)
|
|
if check_vlan(s, ovlan, channel):
|
|
sys.stdout.write('VLAN already configured to "{0}"\n'.format(ovlan))
|
|
return False
|
|
rsp = s.raw_command(0xc, 1, bytearray([channel, 0x14]) + vlan)
|
|
if rsp.get('code', 1) == 0:
|
|
print('VLAN configured to "{}"'.format(ovlan))
|
|
else:
|
|
print('Error setting vlan: ' + repr(rsp))
|
|
return True
|
|
|
|
|
|
def get_lan_channel(s):
|
|
for chan in range(1, 16):
|
|
rsp = s.raw_command(6, 0x42, bytearray([chan]))['data']
|
|
if not rsp:
|
|
continue
|
|
medtype = int(rsp[1]) & 0b1111111
|
|
if medtype not in (4, 6):
|
|
continue
|
|
rsp = s.raw_command(0xc, 2, bytearray([2, chan, 5, 0, 0]))
|
|
if rsp.get('code', 1) == 0:
|
|
return chan
|
|
return 1
|
|
|
|
|
|
def check_ipv4(s, ipaddr, channel):
|
|
ipaddr = bytearray(socket.inet_aton(ipaddr))
|
|
rsp = s.raw_command(0xc, 2, bytearray([channel, 3, 0, 0]))['data'][-4:]
|
|
return rsp == ipaddr
|
|
|
|
def set_ipv4(s, ipaddr, channel):
|
|
oipaddr = ipaddr
|
|
ipaddr = bytearray(socket.inet_aton(ipaddr))
|
|
if check_ipv4(s, oipaddr, channel):
|
|
print('IP Address already set to {}'.format(oipaddr))
|
|
return False
|
|
rsp = int(s.raw_command(0xc, 2, bytearray([channel, 4, 0, 0]))['data'][1]) & 0b1111
|
|
if rsp != 1:
|
|
sys.stdout.write("Changing configuration to static...")
|
|
sys.stdout.flush()
|
|
resp = s.raw_command(0xc, 1, bytearray([channel, 4, 1]))
|
|
tries = 0
|
|
while rsp != 1 and tries < 30:
|
|
sys.stdout.write('.')
|
|
sys.stdout.flush()
|
|
tries += 1
|
|
time.sleep(0.5)
|
|
rsp = int(s.raw_command(0xc, 2, bytearray([channel, 4, 0, 0]))['data'][1]) & 0b1111
|
|
sys.stdout.write('Complete\n')
|
|
sys.stdout.flush()
|
|
print('Setting IP to {}'.format(oipaddr))
|
|
s.raw_command(0xc, 1, bytearray([channel, 3]) + ipaddr)
|
|
return True
|
|
|
|
|
|
def check_subnet(s, prefix, channel):
|
|
prefix = int(prefix)
|
|
mask = bytearray(struct.pack('!I', (2**32 - 1) ^ (2**(32 - prefix) - 1)))
|
|
rsp = s.raw_command(0xc, 2, bytearray([channel, 6, 0, 0]))['data'][-4:]
|
|
return rsp == mask
|
|
|
|
def set_subnet(s, prefix, channel):
|
|
oprefix = prefix
|
|
prefix = int(prefix)
|
|
mask = bytearray(struct.pack('!I', (2**32 - 1) ^ (2**(32 - prefix) - 1)))
|
|
if check_subnet(s, prefix, channel):
|
|
print('Subnet Mask already set to /{}'.format(oprefix))
|
|
return False
|
|
print('Setting subnet mask to /{}'.format(oprefix))
|
|
s.raw_command(0xc, 1, bytearray([channel, 6]) + mask)
|
|
return True
|
|
|
|
|
|
def check_gateway(s, gw, channel):
|
|
gw = bytearray(socket.inet_aton(gw))
|
|
rsp = s.raw_command(0xc, 2, bytearray([channel, 12, 0, 0]))['data'][-4:]
|
|
return rsp == gw
|
|
|
|
def set_gateway(s, gw, channel):
|
|
ogw = gw
|
|
gw = bytearray(socket.inet_aton(gw))
|
|
rsp = s.raw_command(0xc, 2, bytearray([channel, 12, 0, 0]))['data'][-4:]
|
|
if check_gateway(s, ogw, channel):
|
|
print('Gateway already set to {}'.format(ogw))
|
|
return False
|
|
print('Setting gateway to {}'.format(ogw))
|
|
s.raw_command(0xc, 1, bytearray([channel, 12]) + gw)
|
|
return True
|
|
|
|
def dotwait():
|
|
sys.stdout.write('.')
|
|
sys.stdout.flush()
|
|
time.sleep(0.5)
|
|
|
|
def main():
|
|
a = argparse.ArgumentParser(description='Locally configure a BMC device')
|
|
a.add_argument('-v', help='vlan id or off to disable vlan tagging')
|
|
a.add_argument('-p', help='Which port to use (dedicated, lom, ocp, ml2)')
|
|
a.add_argument('-i', help='JSON configuration file to read for '
|
|
'configuration')
|
|
a.add_argument('-c', help='Use Confluent API to direct BMC configuration',
|
|
action='store_true')
|
|
args = a.parse_args()
|
|
if args.i:
|
|
bmccfg = json.load(open(args.i))
|
|
elif args.c:
|
|
bmccfgsrc = subprocess.check_output(
|
|
[sys.executable, apiclient, '/confluent-api/self/bmcconfig', '-j'])
|
|
bmccfg = json.loads(bmccfgsrc)
|
|
else:
|
|
bmccfg = {}
|
|
if args.p is not None:
|
|
bmccfg['bmcport'] = args.p
|
|
if args.v is not None:
|
|
bmccfg['bmcvlan'] = args.v
|
|
vendor = open('/sys/devices/virtual/dmi/id/sys_vendor').read()
|
|
vendor = vendor.strip()
|
|
try:
|
|
model = open('/sys/devices/virtual/dmi/id/product_sku').read().strip()
|
|
if model == 'none':
|
|
raise Exception('No SKU')
|
|
except Exception:
|
|
model = open('/sys/devices/virtual/dmi/id/product_name').read()
|
|
if vendor in ('Lenovo', 'IBM'):
|
|
if '[' in model and ']' in model:
|
|
model = model.split('[')[1].split(']')[0]
|
|
model = model[:4].lower()
|
|
s = Session('/dev/ipmi0')
|
|
if not bmccfg:
|
|
print("No BMC configuration specified, exiting.")
|
|
return
|
|
if bmccfg.get('bmcport', None):
|
|
channel = set_port(s, bmccfg['bmcport'], vendor, model)
|
|
else:
|
|
channel = get_lan_channel(s)
|
|
awaitvlan = False
|
|
awaitip = False
|
|
awaitprefix = False
|
|
awaitgw = False
|
|
if bmccfg.get('bmcvlan', None):
|
|
awaitvlan = set_vlan(s, bmccfg['bmcvlan'], channel)
|
|
if bmccfg.get('bmcipv4', None):
|
|
awaitip = set_ipv4(s, bmccfg['bmcipv4'], channel)
|
|
if bmccfg.get('prefixv4', None):
|
|
awaitprefix = set_subnet(s, bmccfg['prefixv4'], channel)
|
|
if bmccfg.get('bmcgw', None):
|
|
awaitgw = set_gateway(s, bmccfg['bmcgw'], channel)
|
|
sys.stdout.write('Waiting for changes to take effect...')
|
|
sys.stdout.flush()
|
|
while awaitvlan and not check_vlan(s, bmccfg['bmcvlan'], channel):
|
|
dotwait()
|
|
while awaitip and not check_ipv4(s, bmccfg['bmcipv4'], channel):
|
|
dotwait()
|
|
while awaitprefix and not check_subnet(s, bmccfg['prefixv4'], channel):
|
|
dotwait()
|
|
while awaitprefix and not check_gateway(s, bmccfg['bmcgw'], channel):
|
|
dotwait()
|
|
sys.stdout.write('done\n')
|
|
sys.stdout.flush()
|
|
#await_config(s, bmccfg, channel)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|