3 # Copyright (c) 2016 Cisco and/or its affiliates.
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
17 import xml.etree.ElementTree as et
20 from requests.packages.urllib3.exceptions import InsecureRequestWarning
21 requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
23 BASEDN = "sys/rack-unit-1"
26 ### Helper function - iterate through a list in pairs
28 def chunks(lst, chunksize):
29 """Yield successive n-sized chunks from l."""
30 for i in range(0, len(lst), chunksize):
31 yield lst[i:i+chunksize]
34 ### Helper function: Perform an XML request to CIMC
36 def xml_req(ip, xml, debug=False):
38 print "DEBUG: XML-REQUEST:"
40 headers = {'Content-Type': 'text/xml'}
41 req = requests.post('https://' + ip + '/nuova', headers=headers,
42 verify=False, data=et.tostring(xml))
43 resp = et.fromstring(req.content)
45 print "DEBUG: XML-RESPONSE:"
48 if resp.tag == 'error':
50 print "XML response contains error:"
52 raise RuntimeError('XML response contains error')
56 ### Authenticate (Log-In) to CIMC and obtain a cookie
58 def login(ip, username, password):
59 reqxml = et.Element('aaaLogin',
60 attrib={'inName':username, 'inPassword':password})
61 respxml = xml_req(ip, reqxml)
63 cookie = respxml.attrib['outCookie']
65 print "Cannot find cookie in CIMC server response."
66 print "CIMC server output:"
73 ### Log out from CIMC.
75 ### Note: There is a maximum session limit in CIMC and sessions to take a long
76 ### time (10 minutes) to time out. Therefore, calling this function during
77 ### testing is essential, otherwise one will quickly exhaust all available
80 def logout(ip, cookie):
81 reqxml = et.Element('aaaLogout', attrib={'cookie': cookie,
86 ### Power off the host
88 def powerOff(ip, cookie, debug=False):
89 reqxml = et.Element('configConfMo',
90 attrib={'cookie': cookie, 'inHierarchical': 'false',
92 inconfig = et.SubElement(reqxml, 'inConfig')
93 et.SubElement(inconfig, 'computeRackUnit',
94 attrib={'adminPower': 'down', 'dn': BASEDN})
95 respxml = xml_req(ip, reqxml, debug)
101 def powerOn(ip, cookie, debug=False):
102 reqxml = et.Element('configConfMo',
103 attrib={'cookie': cookie, 'inHierarchical': 'false',
105 inconfig = et.SubElement(reqxml, 'inConfig')
106 et.SubElement(inconfig, 'computeRackUnit',
107 attrib={'adminPower': 'up', 'dn': BASEDN})
108 respxml = xml_req(ip, reqxml, debug)
112 ### Restore BIOS to default settings
114 def restoreBiosDefaultSettings(ip, cookie, debug=False):
115 reqxml = et.Element('configResolveClass',
116 attrib={'cookie': cookie, 'inHierarchical': 'true',
117 'classId': 'biosPlatformDefaults'})
118 respxml = xml_req(ip, reqxml, debug)
120 configs = respxml.find('outConfigs')
121 defaults = configs.find('biosPlatformDefaults')
123 reqxml = et.Element('configConfMo',
124 attrib={'cookie': cookie, 'inHierarchical': 'true',
125 'dn': "{}/bios/bios-settings".format(BASEDN)})
126 inconfig = et.SubElement(reqxml, 'inConfig')
127 biosset = et.SubElement(inconfig, 'biosSettings')
128 biosset.extend(defaults)
130 respxml = xml_req(ip, reqxml, debug)
134 ### Apply specified BIOS settings.
136 ### These must be a list of strings in XML format. Not currently very
137 ### user friendly. Format can either be obtained from CIMC
138 ### documention, or by setting them manually and then fetching
139 ### BIOS settings via CIMC XML API.
141 def setBiosSettings(ip, cookie, settings, debug=False):
142 reqxml = et.Element('configConfMo',
143 attrib={'cookie': cookie, 'inHierarchical': 'true',
144 'dn': "{}/bios/bios-settings".format(BASEDN)})
145 inconfig = et.SubElement(reqxml, 'inConfig')
146 biosset = et.SubElement(inconfig, 'biosSettings')
147 print "Applying settings:"
152 biosset.append(et.fromstring(s))
154 respxml = xml_req(ip, reqxml, debug)
157 ### Delete any existing virtual drives
159 ### WARNING: THIS WILL ERASE ALL DATA ON ALL DISKS, WITHOUT ANY CONFIRMATION
162 ### The server must be POWERED ON for this to succeed.
164 def deleteAllVirtualDrives(ip, cookie, debug=False):
165 reqxml = et.Element('configResolveClass',
166 attrib={'cookie': cookie, 'inHierarchical': 'true',
167 'classId': 'storageController'})
168 respxml = xml_req(ip, reqxml, debug)
170 configs = respxml.find('outConfigs')
171 for sc in configs.iter('storageController'):
173 print "DEBUG: SC DN {} ID {}".format(sc.attrib['dn'],
175 reqxml = et.Element('configConfMo',
176 attrib={'cookie': cookie, 'inHierarchical': 'true',
177 'dn': sc.attrib['dn']})
178 inconfig = et.SubElement(reqxml, 'inConfig')
179 et.SubElement(inconfig, 'storageController',
180 attrib={'adminAction': 'delete-all-vds-reset-pds',
181 'dn': sc.attrib['dn']})
182 xml_req(ip, reqxml, debug)
185 ### Create a single RAID-10 across all drives.
187 ### The server must be POWERED ON for this to succeed.
189 def createRaid10_all(ip, cookie, debug=False):
190 reqxml = et.Element('configResolveClass',
191 attrib={'cookie': cookie, 'inHierarchical': 'true',
192 'classId': 'storageController'})
193 respxml = xml_req(ip, reqxml, debug)
195 configs = respxml.find('outConfigs')
196 for sc in configs.iter('storageController'):
198 print "DEBUG: SC DN {} ID {}".format(sc.attrib['dn'],
201 # Find disk size and number of disks
205 for pd in sc.iter('storageLocalDisk'):
207 print "DEBUG: PD {} size {}".format(pd.attrib['id'],
208 pd.attrib['coercedSize'])
209 disks.append(pd.attrib['id'])
210 total_size += int(pd.attrib['coercedSize'].split(' ')[0])
213 # Create a RAID10 array of all available disks, as in:
214 # [1,2][3,4][5,6][7,8][9,10][11,12][13,14][15,16][17,18]
216 raid_size = total_size/2
218 for p in list(chunks(disks, 2)):
219 raid_span += "[{},{}]".format(p[0], p[1])
221 reqxml = et.Element('configConfMo',
222 attrib={'cookie': cookie, 'inHierarchical': 'true',
223 'dn': sc.attrib['dn']})
224 inconfig = et.SubElement(reqxml, 'inConfig')
225 et.SubElement(inconfig,
226 'storageVirtualDriveCreatorUsingUnusedPhysicalDrive',
227 attrib={'virtualDriveName': 'raid10-all',
228 'size': str(raid_size)+' MB',
229 'raidLevel': '10', 'driveGroup': raid_span,
230 'adminState': 'trigger'})
232 xml_req(ip, reqxml, debug)
235 ### Create a single RAID across from empty drives as provided.
237 ### The server must be POWERED ON for this to succeed.
239 def createRaid(ip, cookie, name, raidlevel, size, drives, debug=False):
240 reqxml = et.Element('configResolveClass',
241 attrib={'cookie': cookie, 'inHierarchical': 'true',
242 'classId': 'storageController'})
243 respxml = xml_req(ip, reqxml, debug)
245 configs = respxml.find('outConfigs')
246 for sc in configs.iter('storageController'):
248 print "DEBUG: SC DN {} ID {}".format(sc.attrib['dn'],
251 reqxml = et.Element('configConfMo',
252 attrib={'cookie': cookie, 'inHierarchical': 'true',
253 'dn': sc.attrib['dn']})
254 inconfig = et.SubElement(reqxml, 'inConfig')
255 et.SubElement(inconfig,
256 'storageVirtualDriveCreatorUsingUnusedPhysicalDrive',
257 attrib={'virtualDriveName': name,
258 'size': str(size)+' MB',
259 'raidLevel': raidlevel,
260 'driveGroup': drives,
261 'adminState': 'trigger'})
263 xml_req(ip, reqxml, debug)
266 ### Enable Serial-Over-LAN (SOL) console and redirect BIOS output to
269 def enableConsoleRedir(ip, cookie, debug=False):
270 reqxml = et.Element('configConfMo',
271 attrib={'cookie': cookie, 'inHierarchical': 'false',
272 'dn': "{}/bios/bios-settings".format(BASEDN)})
273 inconfig = et.SubElement(reqxml, 'inConfig')
274 bs = et.SubElement(inconfig, 'biosSettings',
275 attrib={'dn': "{}/bios/bios-settings".format(BASEDN)})
277 'biosVfConsoleRedirection',
278 attrib={'vpConsoleRedirection': 'com-0',
279 'vpBaudRate': '115200'})
280 respxml = xml_req(ip, reqxml, debug)
281 reqxml = et.Element('configConfMo',
282 attrib={'cookie': cookie, 'inHierarchical': 'false',
283 'dn': BASEDN+'/sol-if'})
284 inconfig = et.SubElement(reqxml, 'inConfig')
285 et.SubElement(inconfig, 'solIf',
286 attrib={'dn': BASEDN+'/sol-if', 'adminState': 'enable',
287 'speed': '115200', 'comport': 'com0'})
288 respxml = xml_req(ip, reqxml, debug)
292 ### Boot into UEFI bootloader (we may use this to "park" the host in
293 ### powered-on state)
295 def bootIntoUefi(ip, cookie, debug=False):
296 reqxml = et.Element('configConfMo',
297 attrib={'cookie': cookie, 'inHierarchical': 'false',
298 'dn': BASEDN+'/boot-policy'})
299 inconfig = et.SubElement(reqxml, 'inConfig')
300 bootDef = et.SubElement(inconfig, 'lsbootDef',
301 attrib={'dn': BASEDN+'/boot-policy',
302 'rebootOnUpdate': 'yes'})
303 et.SubElement(bootDef, 'lsbootEfi',
304 attrib={'rn': 'efi-read-only', 'order': '1',
307 respxml = xml_req(ip, reqxml, debug)
311 ### Boot via PXE. Reboot immediately.
313 def bootPXE(ip, cookie, debug=False):
314 reqxml = et.Element('configConfMo',
315 attrib={'cookie': cookie, 'inHierarchical': 'false',
316 'dn': BASEDN+'/boot-policy'})
317 inconfig = et.SubElement(reqxml, 'inConfig')
318 bootDef = et.SubElement(inconfig, 'lsbootDef',
319 attrib={'dn': BASEDN+'/boot-policy',
320 'rebootOnUpdate': 'yes'})
321 et.SubElement(bootDef, 'lsbootLan',
322 attrib={'rn': 'lan-read-only', 'order': '1',
323 'type': 'lan', 'prot': 'pxe'})
325 respxml = xml_req(ip, reqxml, debug)
330 ### Boot via Local HDD first, then via PXE. Do not reboot immediately.
332 def bootHDDPXE(ip, cookie, debug=False):
333 reqxml = et.Element('configConfMo',
334 attrib={'cookie': cookie, 'inHierarchical': 'false',
335 'dn': BASEDN+'/boot-policy'})
336 inconfig = et.SubElement(reqxml, 'inConfig')
337 bootDef = et.SubElement(inconfig, 'lsbootDef',
338 attrib={'dn': BASEDN+'/boot-policy',
339 'rebootOnUpdate': 'no'})
340 storage = et.SubElement(bootDef, 'lsbootStorage',
341 attrib={'rn': 'storage-read-write',
342 'access': 'read-write',
343 'order': '1', 'type': 'storage'})
344 et.SubElement(storage, 'lsbootLocalStorage',
345 attrib={'rn': 'local-storage'})
346 et.SubElement(bootDef, 'lsbootLan',
347 attrib={'rn': 'lan-read-only', 'order': '2',
348 'type': 'lan', 'prot': 'pxe'})
350 respxml = xml_req(ip, reqxml, debug)
354 ### Return LOM port 1 MAC address
356 def getLOMMacAddress(ip, cookie, debug=False):
357 reqxml = et.Element('configResolveClass',
358 attrib={'cookie': cookie, 'inHierarchical': 'true',
359 'classId': 'networkAdapterUnit'})
360 respxml = xml_req(ip, reqxml, debug)
361 reqxml = et.Element('configResolveDn',
362 attrib={'cookie': cookie, 'inHierarchical': 'true',
363 'dn': BASEDN+'/network-adapter-L/eth-1'})
364 respxml = xml_req(ip, reqxml, debug)
366 oc = respxml.find('outConfig')
367 netw = oc.find('networkAdapterEthIf')
369 print "DEBUG: MAC address is {}".format(netw.get('mac'))
370 return netw.get('mac')
373 ### Return all port MAC addresses
375 def getMacAddresses(ip, cookie, debug=False):
377 reqxml = et.Element('configResolveClass',
378 attrib={'cookie': cookie, 'inHierarchical': 'true',
379 'classId': 'networkAdapterUnit'})
380 respxml = xml_req(ip, reqxml, debug)
381 oc = respxml.find('outConfigs')
382 for adapter in oc.iter('networkAdapterUnit'):
384 print "DEBUG: ADAPTER SLOT {} MODEL {}".format(adapter.attrib['slot'],
385 adapter.attrib['model'])
386 slot = adapter.attrib['slot']
388 for port in adapter.iter('networkAdapterEthIf'):
390 print "DEBUG: SLOT {} PORT {} MAC {}".format(slot,
393 maclist[slot][port.attrib['id']] = port.attrib['mac'].lower()
395 reqxml = et.Element('configResolveClass',
396 attrib={'cookie': cookie, 'inHierarchical': 'true',
397 'classId': 'adaptorUnit'})
398 respxml = xml_req(ip, reqxml, debug)
399 oc = respxml.find('outConfigs')
400 for adapter in oc.iter('adaptorUnit'):
402 print "DEBUG: VIC ADAPTER SLOT {} MODEL {}".format(adapter.attrib['pciSlot'],
403 adapter.attrib['model'])
404 slot = adapter.attrib['pciSlot']
406 for port in adapter.iter('adaptorHostEthIf'):
407 portnum = int(re.sub('eth([0-9]+)', '\\1', port.attrib['name']))+1
409 print "DEBUG: VIC SLOT {} PORT {} MAC {}".format(slot,
412 maclist[slot][portnum] = port.attrib['mac'].lower()