f91832e0c95083e5ef16a6ba8cce3f0dae9add01
[csit.git] / resources / tools / testbed-setup / cimc / cimclib.py
1 #!/usr/bin/python
2 #
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:
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
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.
15
16 import requests
17 import xml.etree.ElementTree as et
18 import re
19
20 from requests.packages.urllib3.exceptions import InsecureRequestWarning
21 requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
22
23 BASEDN = "sys/rack-unit-1"
24
25 ###
26 ### Helper function - iterate through a list in pairs
27 ###
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]
32
33 ###
34 ### Helper function: Perform an XML request to CIMC
35 ###
36 def xml_req(ip, xml, debug=False):
37     if debug:
38         print "DEBUG: XML-REQUEST:"
39         et.dump(xml)
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)
44     if debug:
45         print "DEBUG: XML-RESPONSE:"
46         et.dump(resp)
47
48     if resp.tag == 'error':
49         if debug:
50             print "XML response contains error:"
51             et.dump(error)
52         raise RuntimeError('XML response contains error')
53     return resp
54
55 ###
56 ### Authenticate (Log-In) to CIMC and obtain a cookie
57 ###
58 def login(ip, username, password):
59     reqxml = et.Element('aaaLogin',
60                         attrib={'inName':username, 'inPassword':password})
61     respxml = xml_req(ip, reqxml)
62     try:
63         cookie = respxml.attrib['outCookie']
64     except:
65         print "Cannot find cookie in CIMC server response."
66         print "CIMC server output:"
67         et.dump(respxml)
68         raise
69
70     return cookie
71
72 ###
73 ### Log out from CIMC.
74 ###
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
78 ### sessions.
79 ###
80 def logout(ip, cookie):
81     reqxml = et.Element('aaaLogout', attrib={'cookie': cookie,
82                                              'inCookie': cookie})
83     xml_req(ip, reqxml)
84
85 ###
86 ### Power off the host
87 ###
88 def powerOff(ip, cookie, debug=False):
89     reqxml = et.Element('configConfMo',
90                         attrib={'cookie': cookie, 'inHierarchical': 'false',
91                                 'dn': BASEDN})
92     inconfig = et.SubElement(reqxml, 'inConfig')
93     et.SubElement(inconfig, 'computeRackUnit',
94                   attrib={'adminPower': 'down', 'dn': BASEDN})
95     respxml = xml_req(ip, reqxml, debug)
96     return respxml
97
98 ###
99 ### Power on the host
100 ###
101 def powerOn(ip, cookie, debug=False):
102     reqxml = et.Element('configConfMo',
103                         attrib={'cookie': cookie, 'inHierarchical': 'false',
104                                 'dn': BASEDN})
105     inconfig = et.SubElement(reqxml, 'inConfig')
106     et.SubElement(inconfig, 'computeRackUnit',
107                   attrib={'adminPower': 'up', 'dn': BASEDN})
108     respxml = xml_req(ip, reqxml, debug)
109     return respxml
110
111 ###
112 ### Restore BIOS to default settings
113 ###
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)
119
120     configs = respxml.find('outConfigs')
121     defaults = configs.find('biosPlatformDefaults')
122
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)
129
130     respxml = xml_req(ip, reqxml, debug)
131     return respxml
132
133 ###
134 ### Apply specified BIOS settings.
135 ###
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.
140 ###
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:"
148     print settings
149     for s in settings:
150         x = et.fromstring(s)
151         et.dump(x)
152         biosset.append(et.fromstring(s))
153
154     respxml = xml_req(ip, reqxml, debug)
155     return respxml
156 ###
157 ### Delete any existing virtual drives
158 ###
159 ### WARNING: THIS WILL ERASE ALL DATA ON ALL DISKS, WITHOUT ANY CONFIRMATION
160 ### QUESTION.
161 ###
162 ### The server must be POWERED ON for this to succeed.
163 ###
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)
169
170     configs = respxml.find('outConfigs')
171     for sc in configs.iter('storageController'):
172         if debug:
173             print "DEBUG: SC DN {} ID {}".format(sc.attrib['dn'],
174                                                  sc.attrib['id'])
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)
183
184 ###
185 ### Create a single RAID-10 across all drives.
186 ###
187 ### The server must be POWERED ON for this to succeed.
188 ###
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)
194
195     configs = respxml.find('outConfigs')
196     for sc in configs.iter('storageController'):
197         if debug:
198             print "DEBUG: SC DN {} ID {}".format(sc.attrib['dn'],
199                                                  sc.attrib['id'])
200         #
201         # Find disk size and number of disks
202         #
203         disks = []
204         total_size = 0
205         for pd in sc.iter('storageLocalDisk'):
206             if debug:
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])
211
212         #
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]
215         #
216         raid_size = total_size/2
217         raid_span = ''
218         for p in list(chunks(disks, 2)):
219             raid_span += "[{},{}]".format(p[0], p[1])
220
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'})
231
232         xml_req(ip, reqxml, debug)
233
234 ###
235 ### Create a single RAID across from empty drives as provided.
236 ###
237 ### The server must be POWERED ON for this to succeed.
238 ###
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)
244
245     configs = respxml.find('outConfigs')
246     for sc in configs.iter('storageController'):
247         if debug:
248             print "DEBUG: SC DN {} ID {}".format(sc.attrib['dn'],
249                                                  sc.attrib['id'])
250
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'})
262
263         xml_req(ip, reqxml, debug)
264
265 ###
266 ### Enable Serial-Over-LAN (SOL) console and redirect BIOS output to
267 ### serial console
268 ###
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)})
276     et.SubElement(bs,
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)
289     return respxml
290
291 ###
292 ### Boot into UEFI bootloader (we may use this to "park" the host in
293 ### powered-on state)
294 ###
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',
305                           'type': 'efi'})
306
307     respxml = xml_req(ip, reqxml, debug)
308     return respxml
309
310 ###
311 ### Boot via PXE. Reboot immediately.
312 ###
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'})
324
325     respxml = xml_req(ip, reqxml, debug)
326     return respxml
327
328
329 ###
330 ### Boot via Local HDD first, then via PXE. Do not reboot immediately.
331 ###
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'})
349
350     respxml = xml_req(ip, reqxml, debug)
351     return respxml
352
353 ###
354 ### Return LOM port 1 MAC address
355 ###
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)
365
366     oc = respxml.find('outConfig')
367     netw = oc.find('networkAdapterEthIf')
368     if debug:
369         print "DEBUG: MAC address is {}".format(netw.get('mac'))
370     return netw.get('mac')
371
372 ###
373 ### Return all port MAC addresses
374 ###
375 def getMacAddresses(ip, cookie, debug=False):
376     maclist = {}
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'):
383         if debug:
384             print "DEBUG: ADAPTER SLOT {} MODEL {}".format(adapter.attrib['slot'],
385                                                            adapter.attrib['model'])
386         slot = adapter.attrib['slot']
387         maclist[slot] = {}
388         for port in adapter.iter('networkAdapterEthIf'):
389             if debug:
390                 print "DEBUG:    SLOT {} PORT {} MAC {}".format(slot,
391                                                                 port.attrib['id'],
392                                                                 port.attrib['mac'])
393             maclist[slot][port.attrib['id']] = port.attrib['mac'].lower()
394
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'):
401         if debug:
402             print "DEBUG: VIC ADAPTER SLOT {} MODEL {}".format(adapter.attrib['pciSlot'],
403                                                                adapter.attrib['model'])
404         slot = adapter.attrib['pciSlot']
405         maclist[slot] = {}
406         for port in adapter.iter('adaptorHostEthIf'):
407             portnum = int(re.sub('eth([0-9]+)', '\\1', port.attrib['name']))+1
408             if debug:
409                 print "DEBUG:    VIC SLOT {} PORT {} MAC {}".format(slot,
410                                                                     portnum,
411                                                                     port.attrib['mac'])
412             maclist[slot][portnum] = port.attrib['mac'].lower()
413
414     return maclist