aboutsummaryrefslogtreecommitdiffstats
path: root/resources/tools/testbed-setup/cimc/cimclib.py
diff options
context:
space:
mode:
authorCarsten Koester <ckoester@cisco.com>2016-05-08 01:27:47 -0400
committerDave Wallace <dwallacelf@gmail.com>2016-06-02 17:20:05 +0000
commitbca7bcede75d4fa3713a2317b87820d4d3439b7b (patch)
treebb122fd8298b30cefb79798706cfa4788054d5aa /resources/tools/testbed-setup/cimc/cimclib.py
parent55b4465a5c7ab8d8b340d67f1e11ba06a3ce39ea (diff)
Add documentation and files related to initial host setup
Change-Id: I73deeb79e57ac7eca208faa49d04be37c7034163 Signed-off-by: Carsten Koester <ckoester@cisco.com>
Diffstat (limited to 'resources/tools/testbed-setup/cimc/cimclib.py')
-rwxr-xr-xresources/tools/testbed-setup/cimc/cimclib.py414
1 files changed, 414 insertions, 0 deletions
diff --git a/resources/tools/testbed-setup/cimc/cimclib.py b/resources/tools/testbed-setup/cimc/cimclib.py
new file mode 100755
index 0000000000..f91832e0c9
--- /dev/null
+++ b/resources/tools/testbed-setup/cimc/cimclib.py
@@ -0,0 +1,414 @@
+#!/usr/bin/python
+#
+# Copyright (c) 2016 Cisco and/or its affiliates.
+# 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 requests
+import xml.etree.ElementTree as et
+import re
+
+from requests.packages.urllib3.exceptions import InsecureRequestWarning
+requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
+
+BASEDN = "sys/rack-unit-1"
+
+###
+### Helper function - iterate through a list in pairs
+###
+def chunks(lst, chunksize):
+ """Yield successive n-sized chunks from l."""
+ for i in range(0, len(lst), chunksize):
+ yield lst[i:i+chunksize]
+
+###
+### Helper function: Perform an XML request to CIMC
+###
+def xml_req(ip, xml, debug=False):
+ if debug:
+ print "DEBUG: XML-REQUEST:"
+ et.dump(xml)
+ headers = {'Content-Type': 'text/xml'}
+ req = requests.post('https://' + ip + '/nuova', headers=headers,
+ verify=False, data=et.tostring(xml))
+ resp = et.fromstring(req.content)
+ if debug:
+ print "DEBUG: XML-RESPONSE:"
+ et.dump(resp)
+
+ if resp.tag == 'error':
+ if debug:
+ print "XML response contains error:"
+ et.dump(error)
+ raise RuntimeError('XML response contains error')
+ return resp
+
+###
+### Authenticate (Log-In) to CIMC and obtain a cookie
+###
+def login(ip, username, password):
+ reqxml = et.Element('aaaLogin',
+ attrib={'inName':username, 'inPassword':password})
+ respxml = xml_req(ip, reqxml)
+ try:
+ cookie = respxml.attrib['outCookie']
+ except:
+ print "Cannot find cookie in CIMC server response."
+ print "CIMC server output:"
+ et.dump(respxml)
+ raise
+
+ return cookie
+
+###
+### Log out from CIMC.
+###
+### Note: There is a maximum session limit in CIMC and sessions to take a long
+### time (10 minutes) to time out. Therefore, calling this function during
+### testing is essential, otherwise one will quickly exhaust all available
+### sessions.
+###
+def logout(ip, cookie):
+ reqxml = et.Element('aaaLogout', attrib={'cookie': cookie,
+ 'inCookie': cookie})
+ xml_req(ip, reqxml)
+
+###
+### Power off the host
+###
+def powerOff(ip, cookie, debug=False):
+ reqxml = et.Element('configConfMo',
+ attrib={'cookie': cookie, 'inHierarchical': 'false',
+ 'dn': BASEDN})
+ inconfig = et.SubElement(reqxml, 'inConfig')
+ et.SubElement(inconfig, 'computeRackUnit',
+ attrib={'adminPower': 'down', 'dn': BASEDN})
+ respxml = xml_req(ip, reqxml, debug)
+ return respxml
+
+###
+### Power on the host
+###
+def powerOn(ip, cookie, debug=False):
+ reqxml = et.Element('configConfMo',
+ attrib={'cookie': cookie, 'inHierarchical': 'false',
+ 'dn': BASEDN})
+ inconfig = et.SubElement(reqxml, 'inConfig')
+ et.SubElement(inconfig, 'computeRackUnit',
+ attrib={'adminPower': 'up', 'dn': BASEDN})
+ respxml = xml_req(ip, reqxml, debug)
+ return respxml
+
+###
+### Restore BIOS to default settings
+###
+def restoreBiosDefaultSettings(ip, cookie, debug=False):
+ reqxml = et.Element('configResolveClass',
+ attrib={'cookie': cookie, 'inHierarchical': 'true',
+ 'classId': 'biosPlatformDefaults'})
+ respxml = xml_req(ip, reqxml, debug)
+
+ configs = respxml.find('outConfigs')
+ defaults = configs.find('biosPlatformDefaults')
+
+ reqxml = et.Element('configConfMo',
+ attrib={'cookie': cookie, 'inHierarchical': 'true',
+ 'dn': "{}/bios/bios-settings".format(BASEDN)})
+ inconfig = et.SubElement(reqxml, 'inConfig')
+ biosset = et.SubElement(inconfig, 'biosSettings')
+ biosset.extend(defaults)
+
+ respxml = xml_req(ip, reqxml, debug)
+ return respxml
+
+###
+### Apply specified BIOS settings.
+###
+### These must be a list of strings in XML format. Not currently very
+### user friendly. Format can either be obtained from CIMC
+### documention, or by setting them manually and then fetching
+### BIOS settings via CIMC XML API.
+###
+def setBiosSettings(ip, cookie, settings, debug=False):
+ reqxml = et.Element('configConfMo',
+ attrib={'cookie': cookie, 'inHierarchical': 'true',
+ 'dn': "{}/bios/bios-settings".format(BASEDN)})
+ inconfig = et.SubElement(reqxml, 'inConfig')
+ biosset = et.SubElement(inconfig, 'biosSettings')
+ print "Applying settings:"
+ print settings
+ for s in settings:
+ x = et.fromstring(s)
+ et.dump(x)
+ biosset.append(et.fromstring(s))
+
+ respxml = xml_req(ip, reqxml, debug)
+ return respxml
+###
+### Delete any existing virtual drives
+###
+### WARNING: THIS WILL ERASE ALL DATA ON ALL DISKS, WITHOUT ANY CONFIRMATION
+### QUESTION.
+###
+### The server must be POWERED ON for this to succeed.
+###
+def deleteAllVirtualDrives(ip, cookie, debug=False):
+ reqxml = et.Element('configResolveClass',
+ attrib={'cookie': cookie, 'inHierarchical': 'true',
+ 'classId': 'storageController'})
+ respxml = xml_req(ip, reqxml, debug)
+
+ configs = respxml.find('outConfigs')
+ for sc in configs.iter('storageController'):
+ if debug:
+ print "DEBUG: SC DN {} ID {}".format(sc.attrib['dn'],
+ sc.attrib['id'])
+ reqxml = et.Element('configConfMo',
+ attrib={'cookie': cookie, 'inHierarchical': 'true',
+ 'dn': sc.attrib['dn']})
+ inconfig = et.SubElement(reqxml, 'inConfig')
+ et.SubElement(inconfig, 'storageController',
+ attrib={'adminAction': 'delete-all-vds-reset-pds',
+ 'dn': sc.attrib['dn']})
+ xml_req(ip, reqxml, debug)
+
+###
+### Create a single RAID-10 across all drives.
+###
+### The server must be POWERED ON for this to succeed.
+###
+def createRaid10_all(ip, cookie, debug=False):
+ reqxml = et.Element('configResolveClass',
+ attrib={'cookie': cookie, 'inHierarchical': 'true',
+ 'classId': 'storageController'})
+ respxml = xml_req(ip, reqxml, debug)
+
+ configs = respxml.find('outConfigs')
+ for sc in configs.iter('storageController'):
+ if debug:
+ print "DEBUG: SC DN {} ID {}".format(sc.attrib['dn'],
+ sc.attrib['id'])
+ #
+ # Find disk size and number of disks
+ #
+ disks = []
+ total_size = 0
+ for pd in sc.iter('storageLocalDisk'):
+ if debug:
+ print "DEBUG: PD {} size {}".format(pd.attrib['id'],
+ pd.attrib['coercedSize'])
+ disks.append(pd.attrib['id'])
+ total_size += int(pd.attrib['coercedSize'].split(' ')[0])
+
+ #
+ # Create a RAID10 array of all available disks, as in:
+ # [1,2][3,4][5,6][7,8][9,10][11,12][13,14][15,16][17,18]
+ #
+ raid_size = total_size/2
+ raid_span = ''
+ for p in list(chunks(disks, 2)):
+ raid_span += "[{},{}]".format(p[0], p[1])
+
+ reqxml = et.Element('configConfMo',
+ attrib={'cookie': cookie, 'inHierarchical': 'true',
+ 'dn': sc.attrib['dn']})
+ inconfig = et.SubElement(reqxml, 'inConfig')
+ et.SubElement(inconfig,
+ 'storageVirtualDriveCreatorUsingUnusedPhysicalDrive',
+ attrib={'virtualDriveName': 'raid10-all',
+ 'size': str(raid_size)+' MB',
+ 'raidLevel': '10', 'driveGroup': raid_span,
+ 'adminState': 'trigger'})
+
+ xml_req(ip, reqxml, debug)
+
+###
+### Create a single RAID across from empty drives as provided.
+###
+### The server must be POWERED ON for this to succeed.
+###
+def createRaid(ip, cookie, name, raidlevel, size, drives, debug=False):
+ reqxml = et.Element('configResolveClass',
+ attrib={'cookie': cookie, 'inHierarchical': 'true',
+ 'classId': 'storageController'})
+ respxml = xml_req(ip, reqxml, debug)
+
+ configs = respxml.find('outConfigs')
+ for sc in configs.iter('storageController'):
+ if debug:
+ print "DEBUG: SC DN {} ID {}".format(sc.attrib['dn'],
+ sc.attrib['id'])
+
+ reqxml = et.Element('configConfMo',
+ attrib={'cookie': cookie, 'inHierarchical': 'true',
+ 'dn': sc.attrib['dn']})
+ inconfig = et.SubElement(reqxml, 'inConfig')
+ et.SubElement(inconfig,
+ 'storageVirtualDriveCreatorUsingUnusedPhysicalDrive',
+ attrib={'virtualDriveName': name,
+ 'size': str(size)+' MB',
+ 'raidLevel': raidlevel,
+ 'driveGroup': drives,
+ 'adminState': 'trigger'})
+
+ xml_req(ip, reqxml, debug)
+
+###
+### Enable Serial-Over-LAN (SOL) console and redirect BIOS output to
+### serial console
+###
+def enableConsoleRedir(ip, cookie, debug=False):
+ reqxml = et.Element('configConfMo',
+ attrib={'cookie': cookie, 'inHierarchical': 'false',
+ 'dn': "{}/bios/bios-settings".format(BASEDN)})
+ inconfig = et.SubElement(reqxml, 'inConfig')
+ bs = et.SubElement(inconfig, 'biosSettings',
+ attrib={'dn': "{}/bios/bios-settings".format(BASEDN)})
+ et.SubElement(bs,
+ 'biosVfConsoleRedirection',
+ attrib={'vpConsoleRedirection': 'com-0',
+ 'vpBaudRate': '115200'})
+ respxml = xml_req(ip, reqxml, debug)
+ reqxml = et.Element('configConfMo',
+ attrib={'cookie': cookie, 'inHierarchical': 'false',
+ 'dn': BASEDN+'/sol-if'})
+ inconfig = et.SubElement(reqxml, 'inConfig')
+ et.SubElement(inconfig, 'solIf',
+ attrib={'dn': BASEDN+'/sol-if', 'adminState': 'enable',
+ 'speed': '115200', 'comport': 'com0'})
+ respxml = xml_req(ip, reqxml, debug)
+ return respxml
+
+###
+### Boot into UEFI bootloader (we may use this to "park" the host in
+### powered-on state)
+###
+def bootIntoUefi(ip, cookie, debug=False):
+ reqxml = et.Element('configConfMo',
+ attrib={'cookie': cookie, 'inHierarchical': 'false',
+ 'dn': BASEDN+'/boot-policy'})
+ inconfig = et.SubElement(reqxml, 'inConfig')
+ bootDef = et.SubElement(inconfig, 'lsbootDef',
+ attrib={'dn': BASEDN+'/boot-policy',
+ 'rebootOnUpdate': 'yes'})
+ et.SubElement(bootDef, 'lsbootEfi',
+ attrib={'rn': 'efi-read-only', 'order': '1',
+ 'type': 'efi'})
+
+ respxml = xml_req(ip, reqxml, debug)
+ return respxml
+
+###
+### Boot via PXE. Reboot immediately.
+###
+def bootPXE(ip, cookie, debug=False):
+ reqxml = et.Element('configConfMo',
+ attrib={'cookie': cookie, 'inHierarchical': 'false',
+ 'dn': BASEDN+'/boot-policy'})
+ inconfig = et.SubElement(reqxml, 'inConfig')
+ bootDef = et.SubElement(inconfig, 'lsbootDef',
+ attrib={'dn': BASEDN+'/boot-policy',
+ 'rebootOnUpdate': 'yes'})
+ et.SubElement(bootDef, 'lsbootLan',
+ attrib={'rn': 'lan-read-only', 'order': '1',
+ 'type': 'lan', 'prot': 'pxe'})
+
+ respxml = xml_req(ip, reqxml, debug)
+ return respxml
+
+
+###
+### Boot via Local HDD first, then via PXE. Do not reboot immediately.
+###
+def bootHDDPXE(ip, cookie, debug=False):
+ reqxml = et.Element('configConfMo',
+ attrib={'cookie': cookie, 'inHierarchical': 'false',
+ 'dn': BASEDN+'/boot-policy'})
+ inconfig = et.SubElement(reqxml, 'inConfig')
+ bootDef = et.SubElement(inconfig, 'lsbootDef',
+ attrib={'dn': BASEDN+'/boot-policy',
+ 'rebootOnUpdate': 'no'})
+ storage = et.SubElement(bootDef, 'lsbootStorage',
+ attrib={'rn': 'storage-read-write',
+ 'access': 'read-write',
+ 'order': '1', 'type': 'storage'})
+ et.SubElement(storage, 'lsbootLocalStorage',
+ attrib={'rn': 'local-storage'})
+ et.SubElement(bootDef, 'lsbootLan',
+ attrib={'rn': 'lan-read-only', 'order': '2',
+ 'type': 'lan', 'prot': 'pxe'})
+
+ respxml = xml_req(ip, reqxml, debug)
+ return respxml
+
+###
+### Return LOM port 1 MAC address
+###
+def getLOMMacAddress(ip, cookie, debug=False):
+ reqxml = et.Element('configResolveClass',
+ attrib={'cookie': cookie, 'inHierarchical': 'true',
+ 'classId': 'networkAdapterUnit'})
+ respxml = xml_req(ip, reqxml, debug)
+ reqxml = et.Element('configResolveDn',
+ attrib={'cookie': cookie, 'inHierarchical': 'true',
+ 'dn': BASEDN+'/network-adapter-L/eth-1'})
+ respxml = xml_req(ip, reqxml, debug)
+
+ oc = respxml.find('outConfig')
+ netw = oc.find('networkAdapterEthIf')
+ if debug:
+ print "DEBUG: MAC address is {}".format(netw.get('mac'))
+ return netw.get('mac')
+
+###
+### Return all port MAC addresses
+###
+def getMacAddresses(ip, cookie, debug=False):
+ maclist = {}
+ reqxml = et.Element('configResolveClass',
+ attrib={'cookie': cookie, 'inHierarchical': 'true',
+ 'classId': 'networkAdapterUnit'})
+ respxml = xml_req(ip, reqxml, debug)
+ oc = respxml.find('outConfigs')
+ for adapter in oc.iter('networkAdapterUnit'):
+ if debug:
+ print "DEBUG: ADAPTER SLOT {} MODEL {}".format(adapter.attrib['slot'],
+ adapter.attrib['model'])
+ slot = adapter.attrib['slot']
+ maclist[slot] = {}
+ for port in adapter.iter('networkAdapterEthIf'):
+ if debug:
+ print "DEBUG: SLOT {} PORT {} MAC {}".format(slot,
+ port.attrib['id'],
+ port.attrib['mac'])
+ maclist[slot][port.attrib['id']] = port.attrib['mac'].lower()
+
+ reqxml = et.Element('configResolveClass',
+ attrib={'cookie': cookie, 'inHierarchical': 'true',
+ 'classId': 'adaptorUnit'})
+ respxml = xml_req(ip, reqxml, debug)
+ oc = respxml.find('outConfigs')
+ for adapter in oc.iter('adaptorUnit'):
+ if debug:
+ print "DEBUG: VIC ADAPTER SLOT {} MODEL {}".format(adapter.attrib['pciSlot'],
+ adapter.attrib['model'])
+ slot = adapter.attrib['pciSlot']
+ maclist[slot] = {}
+ for port in adapter.iter('adaptorHostEthIf'):
+ portnum = int(re.sub('eth([0-9]+)', '\\1', port.attrib['name']))+1
+ if debug:
+ print "DEBUG: VIC SLOT {} PORT {} MAC {}".format(slot,
+ portnum,
+ port.attrib['mac'])
+ maclist[slot][portnum] = port.attrib['mac'].lower()
+
+ return maclist