1# (c) 2011-2021 Dennis Kaarsemaker <dennis@kaarsemaker.net>
2# see COPYING for license details
3
4__version__ = "4.4.3"
5
6import codecs
7import io
8import os
9import errno
10import platform
11import random
12import re
13import socket
14import ssl
15import subprocess
16import sys
17import types
18import xml.etree.ElementTree as etree
19import warnings
20import hpilo_fw
21
22PY3 = sys.version_info[0] >= 3
23if PY3:
24    import urllib.request as urllib2
25    class Bogus(Exception): pass
26    socket.sslerror = Bogus
27    basestring = str
28    from os import fsencode
29else:
30    import urllib2
31    fsencode = lambda x: x
32
33# Python 2.7.13 renamed PROTOCOL_SSLv23 to PROTOCOL_TLS
34if not hasattr(ssl, 'PROTOCOL_TLS'):
35    ssl.PROTOCOL_TLS = ssl.PROTOCOL_SSLv23
36
37# Oh the joys of monkeypatching...
38# - We need a CDATA element in set_security_msg, but ElementTree doesn't support it
39# - We need to disable escaping of the PASSWORD attribute, because iLO doesn't
40#   unescape it properly
41def CDATA(text=None):
42    element = etree.Element('![CDATA[')
43    element.text = text
44    return element
45
46# Adding this tag to RIBCL scripts should make this hack unnecessary in newer
47# iLO firmware versions. TODO: Check compatibility.
48# <?ilo entity-processing="standard"?>
49class DoNotEscapeMe(str):
50    pass
51
52etree._original_escape_attrib = etree._escape_attrib
53def _escape_attrib(text, *args, **kwargs):
54    if isinstance(text, DoNotEscapeMe):
55        return str(text)
56    else:
57        return etree._original_escape_attrib(text, *args, **kwargs)
58etree._escape_attrib = _escape_attrib
59
60# Python 2.7 and 3
61if hasattr(etree, '_serialize_xml'):
62    etree._original_serialize_xml = etree._serialize_xml
63    def _serialize_xml(write, elem, *args, **kwargs):
64        if elem.tag == '![CDATA[':
65            write("\n<%s%s]]>\n" % (elem.tag, elem.text))
66            return
67        return etree._original_serialize_xml(write, elem, *args, **kwargs)
68    etree._serialize_xml = etree._serialize['xml'] = _serialize_xml
69# Python 2.6, and non-stdlib ElementTree
70elif hasattr(etree.ElementTree, '_write'):
71    etree.ElementTree._orig_write = etree.ElementTree._write
72    def _write(self, file, node, encoding, namespaces):
73        if node.tag == '![CDATA[':
74            file.write("\n<![CDATA[%s]]>\n" % node.text.encode(encoding))
75        else:
76            self._orig_write(file, node, encoding, namespaces)
77    etree.ElementTree._write = _write
78else:
79    raise RuntimeError("Don't know how to monkeypatch XML serializer workarounds. Please report a bug at https://github.com/seveas/python-hpilo")
80
81# We handle non-ascii characters in the returned XML by replacing them with XML
82# character references. This likely results in bogus data, but avoids crashes.
83# The iLO should never do this, but firmware bugs may cause it to do so.
84def iloxml_replace(error):
85    ret = ""
86    for pos in range(error.start, len(error.object)):
87        b = error.object[pos]
88        if not isinstance(b, int):
89            b = ord(b)
90        if b < 128:
91            break
92        ret += u'?'
93    warnings.warn("Invalid ascii data found: %s, replaced with %s" % (repr(error.object[error.start:pos]), ret), IloWarning)
94    return (ret, pos)
95codecs.register_error('iloxml_replace', iloxml_replace)
96
97# Which protocol to use
98ILO_RAW  = 1
99ILO_HTTP = 2
100ILO_LOCAL = 3
101
102class IloErrorMeta(type):
103    def __new__(cls, name, parents, attrs):
104        if 'possible_messages' not in attrs:
105            attrs['possible_messages'] = []
106        if 'possible_codes' not in attrs:
107            attrs['possible_codes'] = []
108        klass = super(IloErrorMeta, cls).__new__(cls, name, parents, attrs)
109        if name != 'IloError':
110            IloError.known_subclasses.append(klass)
111        return klass
112
113class IloError(Exception):
114    __metaclass__ = IloErrorMeta
115    def __init__(self, message, errorcode=None):
116        if issubclass(IloError, object):
117            super(IloError, self).__init__(message)
118        else:
119            Exception.__init__(self, message)
120        self.errorcode = errorcode
121    known_subclasses = []
122
123if PY3:
124    # Python 3 ignores __metaclass__ but wants class foo(metaclass=bar) But
125    # that syntax is an error on older python, so recreate IloError properly
126    # the manual way.
127    IloError = IloErrorMeta('IloError', (Exception,), {'known_subclasses': [], '__init__': IloError.__init__})
128
129class IloCommunicationError(IloError):
130    pass
131
132class IloGeneratingCSR(IloError):
133    possible_messages = ['The iLO subsystem is currently generating a Certificate Signing Request(CSR), run script after 10 minutes or more to receive the CSR.']
134    possible_codes = [0x0088]
135
136class IloLoginFailed(IloError):
137    possible_messages = ['Login failed', 'Login credentials rejected']
138    possible_codes = [0x005f]
139
140class IloUserNotFound(IloError):
141    possible_codes = [0x000a]
142
143class IloPermissionError(IloError):
144    possible_codes = [0x0023]
145
146class IloNotARackServer(IloError):
147    possible_codes = [0x002a]
148
149class IloLicenseKeyError(IloError):
150    possible_codes = [0x002e]
151
152class IloFeatureNotSupported(IloError):
153    possible_codes = [0x003c]
154
155class IloNotConfigured(IloError):
156    possible_codes = [0x006d]
157
158class IloWarning(Warning):
159    pass
160
161class IloXMLWarning(Warning):
162    pass
163
164class IloTestWarning(Warning):
165    pass
166
167class Ilo(object):
168    """Represents an iLO/iLO2/iLO3/iLO4/RILOE II management interface on a
169        specific host. A new connection using the specified login, password and
170        timeout will be made for each API call. The library will detect which
171        protocol to use, but you can override this by setting protocol to
172        ILO_RAW or ILO_HTTP. Use ILO_LOCAL to avoid using a network connection
173        and use hponcfg instead. Username and password are ignored for ILO_LOCAL
174        connections. Set delayed to True to make python-hpilo not send requests
175        immediately, but group them together. See :func:`call_delayed`"""
176
177    XML_HEADER = b'<?xml version="1.0"?>\r\n'
178    HTTP_HEADER = b"POST /ribcl HTTP/1.1\r\nHost: localhost\r\nContent-Length: %d\r\nConnection: Close%s\r\n\r\n"
179    HTTP_UPLOAD_HEADER = b"POST /cgi-bin/uploadRibclFiles HTTP/1.1\r\nHost: localhost\r\nConnection: Close\r\nContent-Length: %d\r\nContent-Type: multipart/form-data; boundary=%s\r\n\r\n"
180    BLOCK_SIZE = 64 * 1024
181
182    def __init__(self, hostname, login=None, password=None, timeout=60, port=443, protocol=None, delayed=False, ssl_verify=False, ssl_context=None, ssl_version=None):
183        self.hostname = hostname
184        self.login    = login or 'Administrator'
185        self.password = password or 'Password'
186        self.timeout  = timeout
187        self.debug    = 0
188        self.port     = port
189        self.protocol = protocol
190        self.ssl_verify = False
191        self.ssl_context = ssl_context
192        self.cookie   = None
193        self.delayed  = delayed
194        self._elements = None
195        self._processors = []
196        self.save_response = None
197        self.read_response = None
198        self.save_request = None
199        self._protect_passwords = os.environ.get('HPILO_DONT_PROTECT_PASSWORDS', None) != 'YesPlease'
200        self.firmware_mirror = None
201        self.hponcfg = "/sbin/hponcfg"
202        hponcfg = 'hponcfg'
203        if platform.system() == 'Windows':
204            self.hponcfg = 'C:\Program Files\HP Lights-Out Configuration Utility\cpqlocfg.exe'
205            hponcfg = 'cpqlocfg.exe'
206        for path in os.environ.get('PATH','').split(os.pathsep):
207            maybe = os.path.join(path, hponcfg)
208            if os.access(maybe, os.X_OK):
209                self.hponcfg = maybe
210                break
211        if self.ssl_verify:
212            if sys.version_info < (2,7,9):
213                raise EnvironmentError("SSL verification only works with python 2.7.9 or newer")
214            if not self.ssl_context:
215                self.ssl_context = ssl.create_default_context()
216                # Sadly, ancient iLO's aren't dead yet, so let's enable sslv3 by default
217                self.ssl_context.options &= ~ssl.OP_NO_SSLv3
218
219    def __str__(self):
220        return "iLO interface of %s" % self.hostname
221
222    def _debug(self, level, message):
223        if message.__class__.__name__ == 'bytes':
224            message = message.decode('ascii')
225        if self.debug >= level:
226            if self._protect_passwords:
227                message = re.sub(r'PASSWORD=".*?"', 'PASSWORD="********"', message)
228            sys.stderr.write(message)
229            if message.startswith('\r'):
230                sys.stderr.flush()
231            else:
232                sys.stderr.write('\n')
233
234    def _request(self, xml, progress=None):
235        """Given an ElementTree.Element, serialize it and do the request.
236           Returns an ElementTree.Element containing the response"""
237        if not self.protocol and not self.read_response:
238            self._detect_protocol()
239
240        # Serialize the XML
241        if hasattr(etree, 'tostringlist'):
242            xml = b"\r\n".join(etree.tostringlist(xml)) + b'\r\n'
243        else:
244            xml = etree.tostring(xml)
245
246        header, data =  self._communicate(xml, self.protocol, progress=progress)
247
248        # This thing usually contains multiple XML messages
249        messages = []
250        while data:
251            pos = data.find('<?xml', 5)
252            if pos == -1:
253                message = self._parse_message(data)
254                data = None
255            else:
256                message = self._parse_message(data[:pos])
257                data = data[pos:]
258
259            # _parse_message returns None if a message has no useful content
260            if message is not None:
261                messages.append(message)
262
263        if not messages:
264            return header, None
265        elif len(messages) == 1:
266            return header, messages[0]
267        else:
268            return header, messages
269
270    def _detect_protocol(self):
271        # Use hponcfg when 'connecting' to localhost
272        if self.hostname == 'localhost':
273            self.protocol = ILO_LOCAL
274            return
275        # Do a bogus request, using the HTTP protocol. If there is no
276        # header (see special case in communicate(), we should be using the
277        # raw protocol
278        header, data = self._communicate(b'<RIBCL VERSION="2.0"></RIBCL>', ILO_HTTP, save=False)
279        if header:
280            self.protocol = ILO_HTTP
281        else:
282            self.protocol = ILO_RAW
283
284    def _upload_file(self, filename, progress):
285        with open(filename, 'rb') as fd:
286            firmware = fd.read()
287        boundary = b'------hpiLO3t%dz' % random.randint(100000,1000000)
288        while boundary in firmware:
289            boundary = b'------hpiLO3t%dz' % str(random.randint(100000,1000000))
290        parts = [
291            b"""--%s\r\nContent-Disposition: form-data; name="fileType"\r\n\r\n""" % boundary,
292            b"""\r\n--%s\r\nContent-Disposition: form-data; name="fwimgfile"; filename="%s"\r\nContent-Type: application/octet-stream\r\n\r\n""" % (boundary, fsencode(filename)),
293            firmware,
294            b"\r\n--%s--\r\n" % boundary,
295        ]
296        total_bytes = sum([len(x) for x in parts])
297        sock = self._get_socket()
298
299        self._debug(2, self.HTTP_UPLOAD_HEADER % (total_bytes, boundary))
300        sock.write(self.HTTP_UPLOAD_HEADER % (total_bytes, boundary))
301        for part in parts:
302            if len(part) < self.BLOCK_SIZE:
303                self._debug(2, part)
304                sock.write(part)
305            else:
306                sent = 0
307                fwlen = len(part)
308                while sent < fwlen:
309                    written = sock.write(part[sent:sent+self.BLOCK_SIZE])
310                    if written is None:
311                        plen = len(part[sent:sent+self.BLOCK_SIZE])
312                        raise IloCommunicationError("Unexpected EOF while sending %d bytes (%d of %d sent before)" % (plen, sent, fwlen))
313
314                    sent += written
315                    if callable(progress):
316                        progress("Sending request %d/%d bytes (%d%%)" % (sent, fwlen, 100.0*sent/fwlen))
317
318        data = ''
319        try:
320            while True:
321                d = sock.read()
322                data += d.decode('ascii')
323                if not d:
324                    break
325        except socket.sslerror as exc: # Connection closed
326            if not data:
327                raise IloCommunicationError("Communication with %s:%d failed: %s" % (self.hostname, self.port, str(exc)))
328
329        self._debug(1, "Received %d bytes" % len(data))
330        self._debug(2, data)
331        if 'Set-Cookie:' not in data:
332            # Seen on ilo3 with corrupt filesystem
333            body = re.search('<body>(.*)</body>', data, flags=re.DOTALL).group(1)
334            body = re.sub('<[^>]*>', '', body).strip()
335            body = re.sub('Return to last page', '', body).strip()
336            body = re.sub('\s+', ' ', body).strip()
337            raise IloError(body)
338        self.cookie = re.search('Set-Cookie: *(.*)', data).group(1)
339        self._debug(2, "Cookie: %s" % self.cookie)
340
341    def _get_socket(self):
342        """Set up a subprocess or an https connection and do an HTTP/raw socket request"""
343        if self.read_response or self.save_request:
344            class FakeSocket(object):
345                def __init__(self, rfile=None, wfile=None):
346                    self.input = rfile and open(rfile, 'rb') or io.BytesIO()
347                    self.output = wfile and open(wfile, 'ab') or io.BytesIO()
348                    self.read = self.input.read
349                    self.write = self.output.write
350                    data = self.input.read(4)
351                    self.input.seek(0)
352                    self.protocol = data == b'HTTP' and ILO_HTTP or ILO_RAW
353                def close(self):
354                    self.input.close()
355                    self.output.close()
356                shutdown = lambda *args: None
357            sock = FakeSocket(self.read_response, self.save_request)
358            if self.read_response:
359                self.protocol = sock.protocol
360            return sock
361
362        if self.protocol == ILO_LOCAL:
363            self._debug(1, "Launching hponcfg")
364            try:
365                sp = subprocess.Popen([self.hponcfg, '--input', '--xmlverbose'], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
366            except OSError as exc:
367                raise IloCommunicationError("Cannot run %s: %s" % (self.hponcfg, str(exc)))
368            sp.write = sp.stdin.write
369            sp.read = sp.stdout.read
370            return sp
371
372        self._debug(1, "Connecting to %s port %d" % (self.hostname, self.port))
373        err = None
374        for res in socket.getaddrinfo(self.hostname, self.port, 0, socket.SOCK_STREAM):
375            af, socktype, proto, canonname, sa = res
376            sock = None
377            try:
378                sock = socket.socket(af, socktype, proto)
379                sock.settimeout(self.timeout)
380                self._debug(2, "Connecting to %s port %d" % sa[:2])
381                sock.connect(sa)
382            except socket.timeout:
383                if sock is not None:
384                    sock.close()
385                err = IloCommunicationError("Timeout connecting to %s port %d" % (self.hostname, self.port))
386            except socket.error as exc:
387                if sock is not None:
388                    sock.close()
389                err = IloCommunicationError("Error connecting to %s port %d: %s" % (self.hostname, self.port, str(exc)))
390
391        if err is not None:
392            raise err
393
394        if not sock:
395            raise IloCommunicationError("Unable to resolve %s" % self.hostname)
396
397        try:
398            if not self.ssl_context:
399                self.ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS)
400                # Even more sadly, some iLOs are still using RC4-SHA
401                # which was dropped from the default cipher suite in
402                # Python 2.7.10 and Python 3.4.4. Add it back here :(
403                self.ssl_context.set_ciphers(ssl._DEFAULT_CIPHERS + ":RC4-SHA")
404            return self.ssl_context.wrap_socket(
405                sock, server_hostname=self.hostname)
406        except ssl.SSLError as exc:
407            raise IloCommunicationError("Cannot establish ssl session with %s:%d: %s" % (self.hostname, self.port, str(exc)))
408
409    def _communicate(self, xml, protocol, progress=None, save=True):
410        sock = self._get_socket()
411        if self.read_response:
412            protocol = sock.protocol
413        msglen = len(self.XML_HEADER + xml)
414        if protocol == ILO_HTTP:
415            extra_header = b''
416            if self.cookie:
417                extra_header = b"\r\nCookie: %s" % self.cookie.encode('ascii')
418            http_header = self.HTTP_HEADER % (msglen, extra_header)
419            msglen += len(http_header)
420        self._debug(1, "Sending XML request, %d bytes" % msglen)
421
422        if protocol == ILO_HTTP:
423            self._debug(2, http_header)
424            sock.write(http_header)
425
426        self._debug(2, self.XML_HEADER + xml)
427
428        # XML header and data need to arrive in 2 distinct packets
429        if self.protocol != ILO_LOCAL:
430            sock.write(self.XML_HEADER)
431        if b'$EMBED' in xml:
432            pre, name, post = re.compile(b'(.*)\$EMBED:(.*)\$(.*)', re.DOTALL).match(xml).groups()
433            sock.write(pre)
434            sent = 0
435            fwlen = os.path.getsize(name)
436            with open(name, 'rb') as fd:
437                fw = fd.read()
438            while sent < fwlen:
439                written = sock.write(fw[sent:sent+self.BLOCK_SIZE])
440                sent += written
441                if callable(progress):
442                    progress("Sending request %d/%d bytes (%d%%)" % (sent, fwlen, 100.0*sent/fwlen))
443            sock.write(post.strip())
444        else:
445            sock.write(xml)
446
447        # And grab the data
448        if self.save_request and save:
449            sock.close()
450            return None, None
451        if self.protocol == ILO_LOCAL:
452            # hponcfg doesn't return data until stdin is closed
453            sock.stdin.close()
454        data = ''
455        try:
456            while True:
457                d = sock.read().decode('ascii', 'iloxml_replace')
458                data += d
459                if not d:
460                    break
461                if callable(progress) and d.strip().endswith('</RIBCL>'):
462                    d = d[d.find('<?xml'):]
463                    while '<?xml' in d:
464                        end = d.find('<?xml', 5)
465                        if end == -1:
466                            msg = self._parse_message(d, include_inform=True)
467                            if msg:
468                                progress(msg)
469                            break
470                        else:
471                            msg = self._parse_message(d[:end], include_inform=True)
472                            if msg:
473                                progress(msg)
474                            d = d[end:]
475        except socket.sslerror as exc: # Connection closed
476            if not data:
477                raise IloCommunicationError("Communication with %s:%d failed: %s" % (self.hostname, self.port, str(exc)))
478
479        self._debug(1, "Received %d bytes" % len(data))
480        if self.protocol == ILO_LOCAL:
481            sock.stdout.close()
482            sock.wait()
483        elif sock.shutdown:
484            # On OSX this may cause an ENOTCONN, Linux/Windows ignore that situation
485            try:
486                sock.shutdown(socket.SHUT_RDWR)
487            except socket.error as exc:
488                if exc.errno == errno.ENOTCONN:
489                    pass
490                else:
491                    raise
492            sock.close()
493
494        # Stript out garbage from hponcfg
495        if self.protocol == ILO_LOCAL:
496            data = data[data.find('<'):data.rfind('>')+1]
497
498        if self.save_response and save:
499            with open(self.save_response, 'a') as fd:
500                fd.write(data)
501
502        # Do we have HTTP?
503        header_ = ''
504        if protocol == ILO_HTTP and data.startswith('HTTP/1.1 200'):
505            header, data = data.split('\r\n\r\n', 1)
506            header_ = header
507            header = [x.split(':', 1) for x in header.split('\r\n')[1:]]
508            header = dict([(x[0].lower(), x[1].strip()) for x in header])
509            if header['transfer-encoding'] == 'chunked':
510                _data, data = data, ''
511                while _data:
512                    clen, _data = _data.split('\r\n', 1)
513                    clen = int(clen, 16)
514                    if clen == 0:
515                        break
516                    data += _data[:clen]
517                    _data = _data[clen+2:]
518
519        elif data.startswith('HTTP/1.1 404'):
520            # We must be using iLO2 or older, they don't do HTTP for XML requests
521            # This case is only triggered by the protocol detection
522            header = None
523
524        elif not data.startswith('<?xml'):
525            if protocol == ILO_LOCAL:
526                raise IloError(sock.stderr.read().strip())
527            raise IloError("Remote returned bogus data, maybe it's not an iLO")
528
529        else:
530            header = None
531
532        self._debug(2, "%s\r\n\r\n%s" % (header_, data))
533        return header, data
534
535
536    def _root_element(self, element, **attrs):
537        """Create a basic XML structure for a message. Return root and innermost element"""
538        if not self.delayed or not self._elements:
539            root = etree.Element('RIBCL', VERSION="2.0")
540            login = etree.SubElement(root, 'LOGIN', USER_LOGIN=self.login, PASSWORD=DoNotEscapeMe(self.password))
541        if self.delayed:
542            if self._elements:
543                root, login = self._elements
544            else:
545                self._elements = (root, login)
546        if self.delayed and len(login) and login[-1].tag == element and login[-1].attrib == attrs:
547            element = login[-1]
548        else:
549            element = etree.SubElement(login, element, **attrs)
550        return root, element
551
552    def _attempt_to_fix_broken_xml(self, data):
553        """ Many iLO versions have bugs that causes them to emit malformed XML.
554        This is a collection of workarounds and kludges to try and fix up the
555        data so ElementTree has a chance to parse it correctly"""
556
557        warnings.warn("iLO returned malformed XML, attempting to fix. Please contact HP to report a bug", IloXMLWarning)
558
559        if '<RIBCL VERSION="2.22"/>' in data:
560            data = data.replace('<RIBCL VERSION="2.22"/>', '<RIBCL VERSION="2.22">')
561
562        # Remove binary 01 in xml output. This bug was seen on a faulty PSU.
563        if '\x01' in data:
564            data = data.replace('\x01', '')
565
566        # Quite a few unescaped quotation mark bugs keep appearing. Let's try
567        # to fix up the XML by replacing the last occurence of a quotation mark
568        # *before* the position of the error.
569        #
570        # Definitely not an optimal algorithm, but this is not a hot path.
571        # Let's favour correctness over hacks.
572        last_position = None
573        position = (0, 0)
574        while position != last_position:
575            last_position = position
576            try:
577                return etree.fromstring(data)
578            except etree.ParseError as e:
579                position = e.position
580                x = position[0]-1
581                y = position[1]
582                lines = data.splitlines()
583                y = lines[x].rfind('"', 0, y)
584                lines[x] = lines[x][:y] + '&quot;' + lines[x][y+1:]
585                data = '\n'.join(lines)
586        # Couldn't fix it :(
587        raise
588
589    def _parse_message(self, data, include_inform=False):
590        """Parse iLO responses into Element instances and remove useless messages"""
591        data = data.strip()
592        if not data:
593            return None
594        try:
595            message = etree.fromstring(data)
596        except etree.ParseError:
597            message = self._attempt_to_fix_broken_xml(data)
598        if message.tag == 'RIBCL':
599            for child in message:
600                if child.tag == 'INFORM':
601                    if include_inform:
602                        # Filter useless message:
603                        if 'should be updated' in child.text:
604                            return None
605                        return child.text
606                # RESPONSE with status 0 also adds no value
607                # Maybe start adding <?xmlilo output-format="xml"?> to requests. TODO: check compatibility
608                elif child.tag == 'RESPONSE' and int(child.get('STATUS'), 16) == 0:
609                    if child.get('MESSAGE') != 'No error':
610                        warnings.warn(child.get('MESSAGE'), IloWarning)
611                # These are interesting, something went wrong
612                elif child.tag == 'RESPONSE':
613                    if 'syntax error' in child.get('MESSAGE') and not self.protocol:
614                        # This is triggered when doing protocol detection, ignore
615                        pass
616                    else:
617                        status = int(child.get('STATUS'), 16)
618                        message = child.get('MESSAGE')
619                        if 'syntax error' in message:
620                            message += '. You may have tried to use a feature this iLO version or firmware version does not support.'
621                        for subclass in IloError.known_subclasses:
622                            if status in subclass.possible_codes or message in subclass.possible_messages:
623                                raise subclass(message, status)
624                        raise IloError(message, status)
625                # And this type of message is the actual payload.
626                else:
627                    return message
628            return None
629        # This shouldn't be reached as all messages are RIBCL messages. But who knows!
630        return message
631
632    def _element_children_to_dict(self, element):
633        """Returns a dict with tag names of all child elements as keys and the
634           VALUE attributes as values"""
635        retval = {}
636        keys = [elt.tag.lower() for elt in element]
637        if len(keys) != 1 and len(set(keys)) == 1:
638            # Can't return a dict
639            retval = []
640        for elt in element:
641            # There are some special tags
642            fname =  '_parse_%s_%s' % (element.tag.lower(), elt.tag.lower())
643            if hasattr(self, fname):
644                retval.update(getattr(self, fname)(elt))
645                continue
646            key, val, unit, description = elt.tag.lower(), elt.get('VALUE', elt.get('value', None)), elt.get('UNIT', None), elt.get('DESCRIPTION', None)
647            if val is None:
648                # HP is not best friends with consistency. Sometimes there are
649                # attributes, sometimes child tags and sometimes text nodes. Oh
650                # well, deal with it :)
651                if element.tag.lower() == 'rimp' or elt.tag.lower() in self.xmldata_ectd.get(element.tag.lower(), []) or elt.tag.lower() == 'temps':
652                    val = self._element_children_to_dict(elt)
653                elif elt.attrib and list(elt):
654                    val = self._element_to_dict(elt)
655                elif list(elt):
656                    val = self._element_to_list(elt)
657                elif elt.text:
658                    val = elt.text.strip()
659                elif elt.attrib:
660                    val = self._element_to_dict(elt)
661
662            val = self._coerce(val)
663            if unit:
664                val = (val, unit)
665            if description and isinstance(val, str):
666                val = (val, description)
667            if isinstance(retval, list):
668                retval.append(val)
669            elif key in retval:
670                if isinstance(retval[key], dict):
671                    retval[key].update(val)
672                elif not isinstance(retval[key], list):
673                    retval[key] = [retval[key], val]
674                else:
675                    retval[key].append(val)
676            else:
677                retval[key] = val
678        return retval
679
680    def _element_to_dict(self, element):
681        """Returns a dict with tag attributes as items"""
682        retval = {}
683        for key, val in element.attrib.items():
684            retval[key.lower()] = self._coerce(val)
685        if list(element):
686            fields = []
687            for child in element:
688                if child.tag == 'FIELD':
689                    fields.append(self._element_to_dict(child))
690            if fields:
691                names = [x['name'] for x in fields]
692                if len(names) == len(set(names)):
693                    # Field names are unique, treat them like attributes
694                    for field in fields:
695                        retval[field['name']] = field['value']
696                else:
697                    # Field names are not unique, such as the name "MAC"
698                    retval['fields'] = fields
699        return retval
700
701    def _element_to_list(self, element):
702        tagnames = [x.tag for x in element]
703        if len(set(tagnames)) == 1:
704            return [self._element_children_to_dict(x) for x in element]
705        else:
706            return [(child.tag.lower(), self._element_to_dict(child)) for child in element]
707
708    def _coerce(self, val):
709        """Do some data type coercion: unquote, turn integers into integers and
710           Y/N into booleans"""
711        if isinstance(val, basestring):
712            if val.startswith('"') and val.endswith('"'):
713                val = val[1:-1]
714            if val.isdigit():
715                val = int(val)
716            else:
717                val = {'Y': True, 'N': False, 'true': True, 'false': False}.get(val, val)
718        return val
719
720    def _raw(self, *tags):
721        if self.delayed:
722            raise IloError("Cannot use raw tags in delayed mode")
723        root, inner = self._root_element(tags[0][0], **(tags[0][1]))
724        for t in tags[1:]:
725            inner = etree.SubElement(inner, t[0], **t[1])
726        header, message = self._request(root)
727        fd = io.BytesIO()
728        etree.ElementTree(message).write(fd)
729        ret = fd.getvalue()
730        fd.close()
731        return ret
732
733    def _info_tag(self, infotype, tagname, returntags=None, attrib={}, process=lambda x: x):
734        root, inner = self._root_element(infotype, MODE='read')
735        etree.SubElement(inner, tagname, **attrib)
736        if self.delayed:
737            self._processors.append([self._process_info_tag, returntags or [tagname], process])
738            return
739        header, message = self._request(root)
740        if self.save_request:
741            return
742        return self._process_info_tag(message, returntags or [tagname], process)
743
744    def _process_info_tag(self, message, returntags, process):
745        if isinstance(returntags, basestring):
746            returntags = [returntags]
747
748        for tag in returntags:
749            if message.find(tag) is None:
750                continue
751            message = message.find(tag)
752            if list(message):
753                return process(self._element_children_to_dict(message))
754            else:
755                return process(self._element_to_dict(message))
756        raise IloError("Expected tag '%s' not found" % "' or '".join(returntags))
757
758    def _control_tag(self, controltype, tagname, returntag=None, attrib={}, elements=[], text=None):
759        root, inner = self._root_element(controltype, MODE='write')
760        inner = etree.SubElement(inner, tagname, **attrib)
761        if text:
762            inner.text = text
763        for element in elements:
764            inner.append(element)
765        if self.delayed:
766            if tagname == 'CERTIFICATE_SIGNING_REQUEST':
767                self._processors.append([self._process_control_tag, returntag or tagname])
768            return
769        header, message = self._request(root)
770        return self._process_control_tag(message, returntag or tagname)
771
772    def _process_control_tag(self, message, returntag):
773        if message is None:
774            return None
775        message = message.find(returntag)
776        if message.text.strip():
777            return message.text.strip()
778        if not message.attrib and not list(message):
779            return None
780        raise IloError("You've reached unknown territories, please report a bug")
781        if list(message):
782            return self._element_children_to_dict(message)
783        else:
784            return self._element_to_dict(message)
785
786    def call_delayed(self):
787        """In delayed mode, calling a method on an iLO object will not cause an
788           immediate callout to the iLO. Instead, the method and parameters are
789           stored for future calls of this method. This method makes one
790           connection to the iLO and sends all commands as one XML document.
791           This speeds up applications that make many calls to the iLO by
792           removing seconds of overhead per call.
793
794           The return value of call_delayed is a list of return values for
795           individual methods that don't return None. This means that there may
796           be fewer items returned than methods called as only `get_*` methods
797           return data
798
799           Delayed calls only work on iLO 2 or newer"""
800
801        if not self._elements:
802            raise ValueError("No commands scheduled")
803        try:
804            root, inner = self._elements
805            header, message = self._request(root)
806            ret = []
807            if message is not None:
808                if not isinstance(message, list):
809                    message = [message]
810                for message, processor in zip(message, self._processors):
811                    ret.append(processor.pop(0)(message, *processor))
812        finally:
813            self._processors = []
814            self._elements = None
815        return ret
816
817    def abort_dir_test(self):
818        """Abort authentication directory test"""
819        return self._control_tag('DIR_INFO', 'ABORT_DIR_TEST')
820
821    def activate_license(self, key):
822        """Activate an iLO advanced license"""
823        license = etree.Element('ACTIVATE', KEY=key)
824        return self._control_tag('RIB_INFO', 'LICENSE', elements=[license])
825
826    def add_federation_group(self, group_name, group_key, admin_priv=False,
827            remote_cons_priv=True, reset_server_priv=False,
828            virtual_media_priv=False, config_ilo_priv=True, login_priv=False):
829        """Add a new federation group"""
830        attrs = locals()
831        elements = []
832        for attribute in [x for x in attrs.keys() if x.endswith('_priv')]:
833            val = ['No', 'Yes'][bool(attrs[attribute])]
834            elements.append(etree.Element(attribute.upper(), VALUE=val))
835
836        return self._control_tag('RIB_INFO', 'ADD_FEDERATION_GROUP', elements=elements,
837                attrib={'GROUP_NAME': group_name, 'GROUP_KEY': group_key})
838
839    def add_sso_server(self, server=None, import_from=None, certificate=None):
840        """Add an SSO server by name (only if SSO trust level is lowered) or by
841           importing a certificate from a server or directly"""
842        if [server, import_from, certificate].count(None) != 2:
843            raise ValueError("You must specify exactly one of server, import_from or certificate")
844        if server:
845            return self._control_tag('SSO_INFO', 'SSO_SERVER', attrib={'NAME': server})
846        if import_from:
847            return self._control_tag('SSO_INFO', 'SSO_SERVER', attrib={'IMPORT_FROM': import_from})
848        if certificate:
849            return self._control_tag('SSO_INFO', 'IMPORT_CERTIFICATE', text=certificate)
850
851    def add_user(self, user_login, user_name, password, admin_priv=False,
852            remote_cons_priv=True, reset_server_priv=False,
853            virtual_media_priv=False, config_ilo_priv=True):
854        """Add a new user to the iLO interface with the specified name,
855           password and permissions. Permission attributes should be boolean
856           values."""
857        attrs = locals()
858        elements = []
859        for attribute in [x for x in attrs.keys() if x.endswith('_priv')]:
860            val = ['No', 'Yes'][bool(attrs[attribute])]
861            elements.append(etree.Element(attribute.upper(), VALUE=val))
862
863        return self._control_tag('USER_INFO', 'ADD_USER', elements=elements,
864                attrib={'USER_LOGIN': user_login, 'USER_NAME': user_name, 'PASSWORD': DoNotEscapeMe(password)})
865
866    def ahs_clear_data(self):
867        """Clears Active Health System information log"""
868        return self._control_tag('RIB_INFO', 'AHS_CLEAR_DATA')
869
870    def cert_fqdn(self, use_fqdn):
871        """Configure whether to use the fqdn or the short hostname for certificate requests"""
872        use_fqdn = str({True: 'Yes', False: 'No'}.get(use_fqdn, use_fqdn))
873        return self._control_tag('RIB_INFO', 'CERT_FQDN', attrib={'VALUE': use_fqdn})
874
875    def certificate_signing_request(self, country=None, state=None, locality=None, organization=None,
876            organizational_unit=None, common_name=None):
877        """Get a certificate signing request from the iLO"""
878        vars = locals()
879        del vars['self']
880        vars = [('CSR_' + x.upper(), vars[x]) for x in vars if vars[x]]
881        elements = map(lambda x: etree.Element(x[0], attrib={'VALUE': str(x[1])}), vars)
882        return self._control_tag('RIB_INFO', 'CERTIFICATE_SIGNING_REQUEST', elements=elements)
883
884    def clear_ilo_event_log(self):
885        """Clears the iLO event log"""
886        return self._control_tag('RIB_INFO', 'CLEAR_EVENTLOG')
887
888    def clear_server_event_log(self):
889        """Clears the server event log"""
890        return self._control_tag('SERVER_INFO', 'CLEAR_IML')
891
892    def clear_server_power_on_time(self):
893        """Clears the server power on time"""
894        return self._control_tag('SERVER_INFO', 'CLEAR_SERVER_POWER_ON_TIME')
895
896    def computer_lock_config(self, computer_lock=None, computer_lock_key=None):
897        """Configure the computer lock settings"""
898        if computer_lock_key:
899            computer_lock = "custom"
900        if not computer_lock:
901            raise ValueError("A value must be specified for computer_lock")
902        elements = [etree.Element('COMPUTER_LOCK', VALUE=computer_lock)]
903        if computer_lock_key:
904            elements.append(etree.Element('COMPUTER_LOCK_KEY', VALUE=computer_lock_key))
905        return self._control_tag('RIB_INFO', 'COMPUTER_LOCK_CONFIG', elements=elements)
906
907    def dc_registration_complete(self):
908        """Complete the ERS registration of your device after calling
909           set_ers_direct_connect"""
910        return self._control_tag('RIB_INFO', 'DC_REGISTRATION_COMPLETE')
911
912    def delete_federation_group(self, group_name):
913        """Delete the specified federation group membership"""
914        return self._control_tag('RIB_INFO', 'DELETE_FEDERATION_GROUP', attrib={'GROUP_NAME': group_name})
915
916    def delete_sso_server(self, index):
917        """Delete an SSO server by index"""
918        return self._control_tag('SSO_INFO', 'DELETE_SERVER',
919                                 attrib={'INDEX': str(index)})
920
921    def delete_user(self, user_login):
922        """Delete the specified user from the ilo"""
923        return self._control_tag('USER_INFO', 'DELETE_USER', attrib={'USER_LOGIN': user_login})
924
925    def deactivate_license(self):
926        """Delete the license key from the iLO"""
927        element = etree.Element('DEACTIVATE')
928        return self._control_tag('RIB_INFO', 'LICENSE', elements=[element])
929
930    def disable_ers(self):
931        """Disable Insight Remote Support functionality and unregister the server"""
932        return self._control_tag('RIB_INFO', 'DISABLE_ERS')
933
934    def eject_virtual_floppy(self):
935        """Eject the virtual floppy"""
936        return self._control_tag('RIB_INFO', 'EJECT_VIRTUAL_FLOPPY')
937
938    def eject_virtual_media(self, device="cdrom"):
939        """Eject the virtual media attached to the specified device"""
940        return self._control_tag('RIB_INFO', 'EJECT_VIRTUAL_MEDIA',
941                attrib={"DEVICE": device.upper()})
942
943    def ers_ahs_submit(self, message_id, bb_days):
944        """Submity AHS data to the insight remote support server"""
945        elements = [
946            etree.Element('MESSAGE_ID', attrib={'VALUE': str(message_id)}),
947            etree.Element('BB_DAYS', attrib={'VALUE': str(bb_days)}),
948        ]
949        return self._control_tag('RIB_INFO', 'TRIGGER_BB_DATA', elements=elements)
950
951    def fips_enable(self):
952        """Enable FIPS standard to enforce AES/3DES encryption, can only be
953           reset with a call to factory_defaults. Resets Administrator password
954           and license key"""
955        return self._control_tag('RIB_INFO', 'FIPS_ENABLE')
956
957    def factory_defaults(self):
958        """Reset the iLO to factory default settings"""
959        return self._control_tag('RIB_INFO', 'FACTORY_DEFAULTS')
960
961    def force_format(self):
962        """Forcefully format the iLO's internal NAND flash. Only use this when
963           the iLO is having severe problems and its self-test fails"""
964        return self._control_tag('RIB_INFO', 'FORCE_FORMAT', attrib={'VALUE': 'all'})
965
966    def get_ahs_status(self):
967        """Get active health system logging status"""
968        return self._info_tag('RIB_INFO', 'GET_AHS_STATUS')
969
970    def get_all_users(self):
971        """Get a list of all loginnames"""
972        def process(data):
973            if isinstance(data, dict):
974                data = data.values()
975            return [x for x in data if x]
976
977        return self._info_tag('USER_INFO', 'GET_ALL_USERS', process=process)
978
979    def get_all_user_info(self):
980        """Get basic and authorization info of all users"""
981        def process(data):
982            if isinstance(data, dict):
983                data = data.values()
984            return dict([(x['user_login'], x) for x in data])
985        return self._info_tag('USER_INFO', 'GET_ALL_USER_INFO', process=process)
986
987    def get_asset_tag(self):
988        """Gets the server asset tag"""
989        # The absence of an asset tag is communicated in a warning and there
990        # will be *NO* returntag, hence the AttributeError.
991        try:
992            return self._info_tag('SERVER_INFO', 'GET_ASSET_TAG')
993        except AttributeError:
994            return {'asset_tag': None}
995
996    def get_cert_subject_info(self):
997        """Get ssl certificate subject information"""
998        return self._info_tag('RIB_INFO', 'GET_CERT_SUBJECT_INFO', 'CSR_CERT_SETTINGS')
999
1000    def get_critical_temp_remain_off(self):
1001        """Get whether the server will remain powered off after a critical temperature shutdown"""
1002        return self._info_tag('SERVER_INFO', 'GET_CRITICAL_TEMP_REMAIN_OFF')
1003
1004    def get_current_boot_mode(self):
1005        """Get the current boot mode (legaci or uefi)"""
1006        return self._info_tag('SERVER_INFO', 'GET_CURRENT_BOOT_MODE', process=lambda data: data['boot_mode'])
1007
1008    def get_diagport_settings(self):
1009        """Get the blade diagport settings"""
1010        return self._info_tag('RACK_INFO', 'GET_DIAGPORT_SETTINGS')
1011
1012    def get_dir_config(self):
1013        """Get directory authentication configuration"""
1014        return self._info_tag('DIR_INFO', 'GET_DIR_CONFIG')
1015
1016    def get_dir_test_results(self):
1017        """Get the results of the authentication directory test"""
1018        def process(data):
1019            for item in data:
1020                data[item] = dict([(x[0], x[1]['value']) for x in data[item]])
1021            return data
1022        return self._info_tag('DIR_INFO', 'GET_DIR_TEST_RESULTS', process=process)
1023
1024    def get_embedded_health(self):
1025        """Get server health information"""
1026        def process(data):
1027            for category in data:
1028                if category == 'health_at_a_glance':
1029                    health = {}
1030                    for key, val in data[category]:
1031                        if key not in health:
1032                            health[key] = val
1033                        else:
1034                            health[key].update(val)
1035                    data[category] = health
1036                    continue
1037                elif isinstance(data[category], list) and data[category]:
1038                    for tag in ('label', 'location'):
1039                        if tag in data[category][0]:
1040                            data[category] = dict([(x[tag], x) for x in data[category]])
1041                            break
1042                elif data[category] in ['', []]:
1043                    data[category] = None
1044            return data
1045        return self._info_tag('SERVER_INFO', 'GET_EMBEDDED_HEALTH', 'GET_EMBEDDED_HEALTH_DATA',
1046                process=process)
1047
1048    # Ok, special XML structures. Yay.
1049    def _parse_get_embedded_health_data_drives(self, element):
1050        ret = []
1051        for bp in element:
1052            if bp.tag != 'BACKPLANE':
1053                raise IloError("Unexpected data returned: %s" % bp.tag)
1054            backplane =  obj = {'drive_bays': {}}
1055            ret.append(backplane)
1056            for elt in bp:
1057                if elt.tag == 'DRIVE_BAY':
1058                    obj = {}
1059                    backplane['drive_bays'][int(elt.get('VALUE'))] = obj
1060                else:
1061                    obj[elt.tag.lower()] = elt.get('VALUE')
1062        return {'drives_backplanes': ret}
1063
1064    def _parse_get_embedded_health_data_memory(self, element):
1065        ret = {}
1066        for elt in element:
1067            fname =  '_parse_%s_%s' % (element.tag.lower(), elt.tag.lower())
1068            if hasattr(self, fname):
1069                ret.update(getattr(self, fname)(elt))
1070                continue
1071            ret[elt.tag.lower()] = self._element_children_to_dict(elt)
1072        return {element.tag.lower(): ret}
1073    _parse_memory_memory_details_summary = _parse_get_embedded_health_data_memory
1074
1075    def _parse_memory_memory_details(self, element):
1076        ret = {}
1077        for elt in element:
1078            if elt.tag not in ret:
1079                ret[elt.tag] = {}
1080            data = self._element_children_to_dict(elt)
1081            ret[elt.tag]["socket %d" % data["socket"]] = data
1082        return {element.tag.lower(): ret}
1083
1084    def _parse_get_embedded_health_data_nic_information(self, element):
1085        return {'nic_information': [self._element_children_to_dict(elt) for elt in element]}
1086    # Can you notice the misspelling?Yes, this is an actual bug in the HP firmware, seen in at least ilo3 1.70
1087    _parse_get_embedded_health_data_nic_infomation = _parse_get_embedded_health_data_nic_information
1088
1089    def _parse_get_embedded_health_data_firmware_information(self, element):
1090        ret = {}
1091        for elt in element:
1092            data = self._element_children_to_dict(elt)
1093            ret[data['firmware_name']] = data['firmware_version']
1094        return {element.tag.lower(): ret}
1095
1096    def _parse_get_embedded_health_data_storage(self, element):
1097        key = element.tag.lower()
1098        ret = {key: []}
1099        for ctrl in element:
1100            if ctrl.tag == 'DISCOVERY_STATUS':
1101                ret['%s_%s' % (key, ctrl.tag.lower())] = self._element_children_to_dict(ctrl)['status']
1102                continue
1103            data = {}
1104            for elt in ctrl:
1105                tag = elt.tag.lower()
1106                if tag in ('drive_enclosure', 'logical_drive'):
1107                    tag += 's'
1108                    if tag not in data:
1109                        data[tag] = []
1110                    if tag == 'drive_enclosures':
1111                        data[tag].append(self._element_children_to_dict(elt))
1112                    else:
1113                        data[tag].append(self._parse_logical_drive(elt))
1114                else:
1115                    data[tag] = elt.get('VALUE')
1116            ret[key].append(data)
1117        return ret
1118
1119    def _parse_logical_drive(self, element):
1120        data = {}
1121        for elt in element:
1122            tag = elt.tag.lower()
1123            if tag == 'physical_drive':
1124                tag += 's'
1125                if tag not in data:
1126                    data[tag] = []
1127                data[tag].append(self._element_children_to_dict(elt))
1128            else:
1129                data[tag] = elt.get('VALUE')
1130        return data
1131
1132    def _parse_get_embedded_health_data_power_supplies(self, element):
1133        key = element.tag.lower()
1134        ret = {key: {}}
1135        for elt in element:
1136            data = self._element_children_to_dict(elt)
1137            if 'label' in data:
1138                ret[key][data['label']] = data
1139            else:
1140                ret[elt.tag.lower()] = data
1141        return ret
1142
1143    def get_enclosure_ip_settings(self):
1144        """Get the enclosure bay static IP settings"""
1145        return self._info_tag('RACK_INFO', 'GET_ENCLOSURE_IP_SETTINGS')
1146
1147    def get_encrypt_settings(self):
1148        """Get the iLO encryption settings"""
1149        return self._info_tag('RIB_INFO', 'GET_ENCRYPT_SETTINGS')
1150
1151    def get_ers_settings(self):
1152        """Get the ERS Insight Remote Support settings"""
1153        return self._info_tag('RIB_INFO', 'GET_ERS_SETTINGS')
1154
1155    def get_federation_all_groups(self):
1156        """Get all federation group names"""
1157        def process(data):
1158            if isinstance(data, dict):
1159                data = data.values()
1160            return data
1161        return self._info_tag('RIB_INFO', 'GET_FEDERATION_ALL_GROUPS', process=process)
1162
1163    def get_federation_all_groups_info(self):
1164        """Get all federation group names and associated privileges"""
1165        def process(data):
1166            if isinstance(data, dict):
1167                data = data.values()
1168            data = [dict([(key, {'yes': True, 'no': False}.get(val['value'].lower(), val['value'])) for (key, val) in group]) for group in data]
1169            return dict([(x['group_name'], x) for x in data])
1170        return self._info_tag('RIB_INFO', 'GET_FEDERATION_ALL_GROUPS_INFO', process=process)
1171
1172    def get_federation_group(self, group_name):
1173        """Get privileges for a specific federation group"""
1174        def process(data):
1175            return dict([(key, {'yes': True, 'no': False}.get(val['value'].lower(), val['value'])) for (key, val) in data.values()[0]])
1176        return self._info_tag('RIB_INFO', 'GET_FEDERATION_GROUP', attrib={'GROUP_NAME': group_name}, process=process)
1177
1178    def get_federation_multicast(self):
1179        """Get the iLO federation mulicast settings"""
1180        return self._info_tag('RIB_INFO', 'GET_FEDERATION_MULTICAST')
1181
1182    def get_fips_status(self):
1183        """Is the FIPS-mandated AES/3DESencryption enforcement in place"""
1184        return self._info_tag('RIB_INFO', 'GET_FIPS_STATUS')
1185
1186    def get_fw_version(self):
1187        """Get the iLO type and firmware version, use get_product_name to get the server model"""
1188        return self._info_tag('RIB_INFO', 'GET_FW_VERSION')
1189
1190    def get_global_settings(self):
1191        """Get global iLO settings"""
1192        return self._info_tag('RIB_INFO', 'GET_GLOBAL_SETTINGS')
1193
1194    def get_host_data(self, decoded_only=True):
1195        """Get SMBIOS records that describe the host. By default only the ones
1196           where human readable information is available are returned. To get
1197           all records pass :attr:`decoded_only=False` """
1198
1199        def process(data):
1200            if decoded_only:
1201                data = [x for x in data if len(x) > 2]
1202            return data
1203        return self._info_tag('SERVER_INFO', 'GET_HOST_DATA', process=process)
1204
1205    def get_host_power_reg_info(self):
1206        """Get power regulator information"""
1207        return self._control_tag('SERVER_INFO', 'GET_HOST_POWER_REG_INFO')
1208
1209    def get_host_power_saver_status(self):
1210        """Get the configuration of the ProLiant power regulator"""
1211        return self._info_tag('SERVER_INFO', 'GET_HOST_POWER_SAVER_STATUS', 'GET_HOST_POWER_SAVER')
1212
1213    def get_host_power_status(self):
1214        """Whether the server is powered on or not"""
1215        return self._info_tag('SERVER_INFO', 'GET_HOST_POWER_STATUS', 'GET_HOST_POWER',
1216                process=lambda data: data['host_power'])
1217
1218    def get_host_pwr_micro_ver(self):
1219        """Get the version of the power micro firmware"""
1220        return self._info_tag('SERVER_INFO', 'GET_HOST_PWR_MICRO_VER',
1221                process=lambda data: data['pwr_micro']['version'])
1222
1223    def get_ilo_event_log(self):
1224        """Get the full iLO event log"""
1225        def process(data):
1226            if isinstance(data, dict) and 'event' in data:
1227                return [data['event']]
1228            return data
1229        return self._info_tag('RIB_INFO', 'GET_EVENT_LOG', 'EVENT_LOG', process=process)
1230
1231    def get_language(self):
1232        """Get the default language set"""
1233        return self._info_tag('RIB_INFO', 'GET_LANGUAGE')
1234
1235    def get_all_languages(self):
1236        """Get the list of installed languages - broken because iLO returns invalid XML"""
1237        return self._info_tag('RIB_INFO', 'GET_ALL_LANGUAGES')
1238
1239    def get_all_licenses(self):
1240        """Get a list of all license types and licenses"""
1241        def process(data):
1242            if not isinstance(data, list):
1243                data = data.values()
1244            return [dict([(x[0], x[1]['value']) for x in row]) for row in data]
1245        return self._info_tag('RIB_INFO', 'GET_ALL_LICENSES', process=process)
1246
1247    def get_hotkey_config(self):
1248        """Retrieve hotkeys available for use in remote console sessions"""
1249        return self._info_tag('RIB_INFO', 'GET_HOTKEY_CONFIG')
1250
1251    def get_network_settings(self):
1252        """Get the iLO network settings"""
1253        return self._info_tag('RIB_INFO', 'GET_NETWORK_SETTINGS')
1254
1255    def get_oa_info(self):
1256        """Get information about the Onboard Administrator of the enclosing chassis"""
1257        return self._info_tag('BLADESYSTEM_INFO', 'GET_OA_INFO')
1258
1259    def get_one_time_boot(self):
1260        """Get the one time boot state of the host"""
1261        # Inconsistency between iLO 2 and 3, let's fix that
1262        def process(data):
1263            if 'device' in data['boot_type']:
1264                data['boot_type'] = data['boot_type']['device']
1265            return data['boot_type'].lower()
1266        return self._info_tag('SERVER_INFO', 'GET_ONE_TIME_BOOT', ('ONE_TIME_BOOT', 'GET_ONE_TIME_BOOT'), process=process)
1267
1268    def get_pending_boot_mode(self):
1269        """Get the pending boot mode (legaci or uefi)"""
1270        return self._info_tag('SERVER_INFO', 'GET_PENDING_BOOT_MODE', process=lambda data: data['boot_mode'])
1271
1272    def get_persistent_boot(self):
1273        """Get the boot order of the host. For uEFI hosts (gen9+), this returns
1274           a list of tuples (name, description. For older host it returns a
1275           list of names"""
1276        def process(data):
1277            if isinstance(data, dict):
1278                data = list(data.items())
1279                data.sort(key=lambda x: x[1])
1280                return [x[0].lower() for x in data]
1281            elif isinstance(data[0], tuple):
1282                return data
1283            return [x.lower() for x in data]
1284        return self._info_tag('SERVER_INFO', 'GET_PERSISTENT_BOOT', ('PERSISTENT_BOOT', 'GET_PERSISTENT_BOOT'), process=process)
1285
1286    def get_pers_mouse_keyboard_enabled(self):
1287        """Returns whether persistent mouse and keyboard are enabled"""
1288        return self._info_tag('SERVER_INFO', 'GET_PERS_MOUSE_KEYBOARD_ENABLED', process=lambda data: data['persmouse_enabled'])
1289
1290    def get_power_cap(self):
1291        """Get the power cap setting"""
1292        return self._info_tag('SERVER_INFO', 'GET_POWER_CAP', process=lambda data: data['power_cap'])
1293
1294    def get_power_readings(self):
1295        """Get current, min, max and average power readings"""
1296        return self._info_tag('SERVER_INFO', 'GET_POWER_READINGS')
1297
1298    def get_product_name(self):
1299        """Get the model name of the server, use get_fw_version to get the iLO model"""
1300        return self._info_tag('SERVER_INFO', 'GET_PRODUCT_NAME', process=lambda data: data['product_name'])
1301
1302    def get_pwreg(self):
1303        """Get the power and power alert threshold settings"""
1304        return self._info_tag('SERVER_INFO', 'GET_PWREG')
1305
1306    def get_rack_settings(self):
1307        """Get the rack settings for an iLO"""
1308        return self._info_tag('RACK_INFO', 'GET_RACK_SETTINGS')
1309
1310    def get_sdcard_status(self):
1311        """Get whether an SD card is connected to the server"""
1312        return self._info_tag('SERVER_INFO', 'GET_SDCARD_STATUS')
1313
1314    def get_security_msg(self):
1315        """Retrieve the security message that is displayed on the login screen"""
1316        return self._info_tag('RIB_INFO', 'GET_SECURITY_MSG')
1317
1318    def get_server_auto_pwr(self):
1319        """Get the automatic power on delay setting"""
1320        return self._info_tag('SERVER_INFO', 'GET_SERVER_AUTO_PWR', process=lambda data: data['server_auto_pwr'])
1321
1322    def get_server_event_log(self):
1323        """Get the IML log of the server"""
1324        def process(data):
1325            if isinstance(data, dict) and 'event' in data:
1326                return [data['event']]
1327            return data
1328        return self._info_tag('SERVER_INFO', 'GET_EVENT_LOG', 'EVENT_LOG', process=process)
1329
1330    def get_server_fqdn(self):
1331        """Get the fqdn of the server this iLO is managing"""
1332        return self._info_tag('SERVER_INFO', 'GET_SERVER_FQDN', 'SERVER_FQDN', process=lambda fqdn: fqdn['value'])
1333
1334    def get_server_name(self):
1335        """Get the name of the server this iLO is managing"""
1336        return self._info_tag('SERVER_INFO', 'GET_SERVER_NAME', 'SERVER_NAME', process=lambda name: name['value'])
1337
1338    def get_server_power_on_time(self):
1339        """How many minutes ago has the server been powered on"""
1340        return self._info_tag('SERVER_INFO', 'GET_SERVER_POWER_ON_TIME', 'SERVER_POWER_ON_MINUTES', process=lambda data: int(data['value']))
1341
1342    def get_smh_fqdn(self):
1343        """Get the fqdn of the HP System Management Homepage"""
1344        return self._info_tag('SERVER_INFO', 'GET_SMH_FQDN', 'SMH_FQDN', process=lambda fqdn: fqdn['value'])
1345
1346    def get_snmp_im_settings(self):
1347        """Where does the iLO send SNMP traps to and which traps does it send"""
1348        return self._info_tag('RIB_INFO', 'GET_SNMP_IM_SETTINGS')
1349
1350    def get_spatial(self):
1351        """Get location information"""
1352        return self._info_tag('SERVER_INFO', 'GET_SPATIAL', 'SPATIAL')
1353
1354    def get_sso_settings(self):
1355        """Get the HP SIM Single Sign-On settings"""
1356        return self._info_tag('SSO_INFO', 'GET_SSO_SETTINGS')
1357
1358    def get_supported_boot_mode(self):
1359        return self._info_tag('SERVER_INFO', 'GET_SUPPORTED_BOOT_MODE', process=lambda data: data['supported_boot_mode'])
1360
1361    def get_topology(self):
1362        """Get rack topology information"""
1363        return self._info_tag('RACK_INFO', 'GET_TOPOLOGY')
1364
1365    def get_tpm_status(self):
1366        """Get the status of the Trusted Platform Module"""
1367        return self._info_tag('SERVER_INFO', 'GET_TPM_STATUS')
1368
1369    def get_twofactor_settings(self):
1370        """Get two-factor authentication settings"""
1371        return self._info_tag('RIB_INFO', 'GET_TWOFACTOR_SETTINGS')
1372
1373    def get_uid_status(self):
1374        """Get the status of the UID light"""
1375        return self._info_tag('SERVER_INFO', 'GET_UID_STATUS', process=lambda data: data['uid'])
1376
1377    def get_user(self, user_login):
1378        """Get user info about a specific user"""
1379        return self._info_tag('USER_INFO', 'GET_USER', attrib={'USER_LOGIN': user_login})
1380
1381    def get_vm_status(self, device="CDROM"):
1382        """Get the status of virtual media devices. Valid devices are FLOPPY and CDROM"""
1383        return self._info_tag('RIB_INFO', 'GET_VM_STATUS', attrib={'DEVICE': device})
1384
1385    def hotkey_config(self, ctrl_t=None, ctrl_u=None, ctrl_v=None, ctrl_w=None,
1386                      ctrl_x=None, ctrl_y=None):
1387        """Change remote console hotkeys"""
1388        vars = locals()
1389        del vars['self']
1390        elements = [etree.Element(x.upper(), VALUE=vars[x]) for x in vars if vars[x] is not None]
1391        return self._control_tag('RIB_INFO', 'HOTKEY_CONFIG', elements=elements)
1392
1393    def import_certificate(self, certificate):
1394        """Import a signed SSL certificate"""
1395        return self._control_tag('RIB_INFO', 'IMPORT_CERTIFICATE', text=certificate)
1396
1397    # Broken in iLO3 < 1.55 for Administrator
1398    def import_ssh_key(self, user_login, ssh_key):
1399        """Imports an SSH key for the specified user. The value of ssh_key
1400           should be the content of an id_dsa.pub or id_rsa.pub file"""
1401        # Basic sanity checking
1402        if ' ' not in ssh_key:
1403            raise ValueError("Invalid SSH key")
1404        algo, key = ssh_key.split(' ',2)[:2]
1405        if algo not in ['ssh-dss', 'ssh-rsa']:
1406            raise ValueError("Invalid SSH key, only DSA and RSA keys are supported")
1407        try:
1408            key.decode('base64')
1409        except Exception:
1410            raise ValueError("Invalid SSH key")
1411        key_ = "-----BEGIN SSH KEY-----\r\n%s\r\n%s %s\r\n-----END SSH KEY-----\r\n" % (algo, key, user_login)
1412        return self._control_tag('RIB_INFO', 'IMPORT_SSH_KEY', text=key_)
1413
1414    def delete_ssh_key(self, user_login):
1415        """Delete a users SSH key"""
1416        return self._control_tag('USER_INFO', 'MOD_USER', attrib={'USER_LOGIN': user_login}, elements=[etree.Element('DEL_USERS_SSH_KEY')])
1417
1418    def insert_virtual_media(self, device, image_url):
1419        """Insert a virtual floppy or CDROM. Note that you will also need to
1420           use :func:`set_vm_status` to connect the media"""
1421        return self._control_tag('RIB_INFO', 'INSERT_VIRTUAL_MEDIA', attrib={'DEVICE': device.upper(), 'IMAGE_URL': image_url})
1422
1423    def mod_encrypt_settings(self, user_login, password, ilo_group_name, cert_name, enable_redundancy,
1424            primary_server_address, primary_server_port, secondary_server_address=None, secondary_server_port=None):
1425        """Configure encryption settings"""
1426        vars = locals()
1427        del vars['self']
1428        elements = []
1429        for var in ['ilo_group_name', 'enable_redundancy']:
1430            if vars[var] is not None:
1431                elements.append(etree.Element(var.upper(), VALUE=vars.pop(var)))
1432        for var in vars:
1433            if vars[var] is not None:
1434                elements.append(etree.Element('ESKM_' + var.upper(), VALUE=vars[var]))
1435        return self._control_tag('RIB_INFO', 'MOD_ENCRYPT_SETTINGS', elements=elements)
1436
1437    def mod_federation_group(self, group_name, new_group_name=None, group_key=None,
1438            admin_priv=None, remote_cons_priv=None, reset_server_priv=None,
1439            virtual_media_priv=None, config_ilo_priv=None, login_priv=None):
1440        """Set attributes for a federation group, only specified arguments will
1441           be changed.  All arguments except group_name, new_group_name and
1442           group_key should be boolean"""
1443        attrs = locals()
1444        elements = []
1445        if attrs['new_group_name'] is not None:
1446            elements.append(etree.Element('GROUP_NAME', VALUE=attrs['new_group_name']))
1447        if attrs['group_key'] is not None:
1448            elements.append(etree.Element('PASSWORD', VALUE=attrs['group_key']))
1449        for attribute in [x for x in attrs.keys() if x.endswith('_priv')]:
1450            if attrs[attribute] is not None:
1451                val = ['No', 'Yes'][bool(attrs[attribute])]
1452                elements.append(etree.Element(attribute.upper(), VALUE=val))
1453        return self._control_tag('RIB_INFO', 'MOD_FEDERATION_GROUP', attrib={'GROUP_NAME': group_name}, elements=elements)
1454
1455    def mod_global_settings(self, ilo_funct_enabled=None, rbsu_post_ip=None,
1456            # Access settings
1457            f8_prompt_enabled=None, f8_login_required=None, lock_configuration=None,
1458            serial_cli_status=None, serial_cli_speed=None,
1459            http_port=None, https_port=None, ssh_port=None, ssh_status=None,
1460            ipmi_dcmi_over_lan_enabled=None, ipmi_dcmi_over_lan_port=None,
1461            remote_console_port_status=None, remote_console_port=None, remote_console_encryption=None,
1462            rawvsp_port=None, vsp_software_flow_control=None,
1463            terminal_services_port=None,
1464            shared_console_enable=None, shared_console_port=None, remote_console_acquire=None,
1465            telnet_enable=None, ssl_empty_records_enable=None, remote_console_status=None,
1466            ribcl_status=None, virtual_media_status=None, webgui_status=None, webserver_status=None,
1467
1468            # Security settings
1469            min_password=None, enforce_aes=None, authentication_failure_logging=None,
1470            authentication_failure_delay_secs=None, authentication_failures_before_delay=None,
1471            ssl_v3_enable=None, session_timeout=None,
1472
1473            # Monitoring & alerting
1474            snmp_access_enabled=None, snmp_port=None, snmp_trap_port=None,
1475            remote_syslog_enable=None, remote_syslog_server_address=None, remote_syslog_port=None,
1476            alertmail_enable=None, alertmail_email_address=None,
1477            alertmail_sender_domain=None, alertmail_smtp_server=None, alertmail_smtp_port=None,
1478            alertmail_smtp_auth_enable=None, alertmail_smtp_auth_username=None,
1479            alertmail_smtp_secure_enable=None,
1480
1481            # Console capturing
1482            vsp_log_enable=None,
1483            interactive_console_replay_enable=None, console_capture_enable=None,
1484            console_capture_boot_buffer_enable=None, console_capture_fault_buffer_enable=None,
1485            console_capture_port=None,
1486            capture_auto_export_enable=None, capture_auto_export_location=None,
1487            capture_auto_export_username=None, capture_auto_export_password=None,
1488
1489            # And the rest
1490            remote_keyboard_model=None, virtual_kbmouse_connection=None, vmedia_disable=None,
1491            virtual_media_port=None, key_up_key_down_enable=None, high_performance_mouse=None,
1492            brownout_recovery=None, enhanced_cli_prompt_enable=None, tcp_keep_alive_enable=None,
1493            propagate_time_to_host=None, passthrough_config=None):
1494        """Modify iLO global settings, only values that are specified will be changed. Note that
1495           many settings only work on certain iLO models and firmware versions"""
1496        vars = dict(locals())
1497        del vars['self']
1498
1499        # even though a get_global_settings returns the actual speed we have to use
1500        # numerical values between 0 (unchanged) and 6 to represent speed
1501        serial_cli_speed_options = {
1502            '9600':   1,
1503            '19200':  2,
1504            '38400':  3,
1505            '57600':  4,
1506            '115200': 5,
1507        }
1508
1509        # same with serial_cli_status
1510        serial_cli_status_options = {
1511            'Disabled':                        1,
1512            'Enabled-No Authentication':       2,
1513            'Enabled-Authentication Required': 3,
1514        }
1515
1516        # and authentication_failure_logging
1517        authentication_failure_logging_options = {
1518            'Disabled':                  0,
1519            'Enabled-every failure':     1,
1520            'Enabled-every 2nd failure': 2,
1521            'Enabled-every 3rd failure': 3,
1522            'Enabled-every 5th failure': 5,
1523        }
1524
1525        vars_mappings = {
1526            "serial_cli_speed": serial_cli_speed_options,
1527            "serial_cli_status": serial_cli_status_options,
1528            "authentication_failure_logging": authentication_failure_logging_options
1529        }
1530
1531        for var_name, var_mappings in vars_mappings.items():
1532            if vars.get(var_name, None) is not None:
1533                var_value = str(vars.get(var_name))
1534                vars[var_name] = str(var_mappings.get(var_value,var_value))
1535
1536        dont_map = ['authentication_failure_logging', 'authentication_failures_before_delay', 'serial_cli_speed',
1537                    'min_password', 'session_timeout', "serial_cli_status"]
1538        elements = [etree.Element(x.upper(), VALUE=str({True: 'Yes', False: 'No'}.get(vars[x], vars[x])))
1539                    for x in vars if vars[x] is not None and x not in dont_map] + \
1540                   [etree.Element(x.upper(), VALUE=str(vars[x]))
1541                    for x in vars if vars[x] is not None and x in dont_map]
1542        return self._control_tag('RIB_INFO', 'MOD_GLOBAL_SETTINGS', elements=elements)
1543
1544    def mod_network_settings(self, enable_nic=None, reg_ddns_server=None,
1545            ping_gateway=None, dhcp_domain_name=None, speed_autoselect=None,
1546            nic_speed=None, full_duplex=None, dhcp_enable=None,
1547            ip_address=None, subnet_mask=None, gateway_ip_address=None,
1548            dns_name=None, domain_name=None, dhcp_gateway=None,
1549            dhcp_dns_server=None, dhcp_wins_server=None, dhcp_static_route=None,
1550            reg_wins_server=None, prim_dns_server=None, sec_dns_server=None,
1551            ter_dns_server=None, prim_wins_server=None, sec_wins_server=None,
1552            static_route_1=None, static_route_2=None, static_route_3=None,
1553            dhcp_sntp_settings=None, sntp_server1=None, sntp_server2=None,
1554            timezone=None, enclosure_ip_enable=None, web_agent_ip_address=None,
1555            shared_network_port=None, vlan_enabled=None, vlan_id=None,
1556            shared_network_port_vlan=None, shared_network_port_vlan_id=None, ipv6_address=None,
1557            ipv6_static_route_1=None, ipv6_static_route_2=None, ipv6_static_route_3=None,
1558            ipv6_prim_dns_server=None, ipv6_sec_dns_server=None, ipv6_ter_dns_server=None,
1559            ipv6_default_gateway=None, ipv6_preferred_protocol=None, ipv6_addr_autocfg=None,
1560            ipv6_reg_ddns_server=None, dhcpv6_dns_server=None, dhcpv6_rapid_commit=None,
1561            dhcpv6_stateful_enable=None, dhcpv6_stateless_enable=None, dhcpv6_sntp_settings=None,
1562            dhcpv6_domain_name=None, ilo_nic_auto_select=None, ilo_nic_auto_snp_scan=None,
1563            ilo_nic_auto_delay=None, ilo_nic_fail_over=None, gratuitous_arp=None,
1564            ilo_nic_fail_over_delay=None, snp_port=None):
1565        """Configure the network settings for the iLO card. The static route arguments require
1566           dicts as arguments. The necessary keys in these dicts are dest,
1567           gateway and mask all in dotted-quad form"""
1568        vars = dict(locals())
1569        del vars['self']
1570
1571        # For the ipv4 route elements, {'dest': XXX, 'gateway': XXX}
1572        # ipv6 routes are ipv6_dest, prefixlen, ipv6_gateway
1573        # IPv6 addresses may specify prefixlength as /64 (default 64)
1574        dont_map = ['prefixlen', 'ilo_nic_auto_snp_scan', 'ilo_nic_auto_delay', 'ilo_nic_fail_over_delay', 'snp_port', 'vlan_id']
1575        elements = [etree.Element(x.upper(), VALUE=str({True: 'Yes', False: 'No'}.get(vars[x], vars[x])))
1576                    for x in vars if vars[x] is not None and 'static_route_' not in x and x not in dont_map] + \
1577                   [etree.Element(x.upper(), VALUE=str(vars[x]))
1578                    for x in vars if vars[x] is not None and 'static_route_' not in x and x in dont_map]
1579
1580        for key in vars:
1581            if 'static_route_' not in key or not vars[key]:
1582                continue
1583            val = vars[key]
1584            # Uppercase all keys
1585            for key_ in val.keys():
1586                val[key_.upper()] = val.pop(key_)
1587            elements.append(etree.Element(key.upper(), **val))
1588
1589        for element in elements:
1590            if element.tag == 'IPV6_ADDRESS':
1591                addr = element.attrib['VALUE']
1592                if '/' in addr:
1593                    addr, plen = addr.rsplit('/', 1)
1594                    element.attrib.update({'VALUE': addr, 'PREFIXLEN': plen})
1595                if 'PREFIXLEN' not in element.attrib:
1596                    element.attrib['PREFIXLEN'] = '64'
1597            if "IPV6_STATIC_ROUTE_" in element.tag:
1598                plen = element.attrib['PREFIXLEN']
1599                if not isinstance(plen, basestring):
1600                    element.attrib['PREFIXLEN'] = str(plen)
1601        return self._control_tag('RIB_INFO', 'MOD_NETWORK_SETTINGS', elements=elements)
1602    mod_network_settings.requires_dict = ['static_route_1', 'static_route_2', 'static_route_3',
1603        'ipv6_static_route_1', 'ipv6_static_route_2', 'ipv6_static_route_3']
1604
1605    def mod_dir_config(self, dir_authentication_enabled=None,
1606            dir_local_user_acct=None,dir_server_address=None,
1607            dir_server_port=None,dir_object_dn=None,dir_object_password=None,
1608            dir_user_context_1=None,dir_user_context_2=None,
1609            dir_user_context_3=None,dir_user_context_4=None,
1610            dir_user_context_5=None,dir_user_context_6=None,
1611            dir_user_context_7=None,dir_user_context_8=None,
1612            dir_user_context_9=None,dir_user_context_10=None,
1613            dir_user_context_11=None,dir_user_context_12=None,
1614            dir_user_context_13=None,dir_user_context_14=None,
1615            dir_user_context_15=None,dir_enable_grp_acct=None,
1616            dir_kerberos_enabled=None,dir_kerberos_realm=None,
1617            dir_kerberos_kdc_address=None,dir_kerberos_kdc_port=None,
1618            dir_kerberos_keytab=None,
1619            dir_generic_ldap_enabled=None,
1620            dir_grpacct1_name=None,dir_grpacct1_sid=None,
1621            dir_grpacct1_priv=None,dir_grpacct2_name=None,
1622            dir_grpacct2_sid=None,dir_grpacct2_priv=None,
1623            dir_grpacct3_name=None,dir_grpacct3_sid=None,
1624            dir_grpacct3_priv=None,dir_grpacct4_name=None,
1625            dir_grpacct4_sid=None,dir_grpacct4_priv=None,
1626            dir_grpacct5_name=None,dir_grpacct5_sid=None,
1627            dir_grpacct5_priv=None,dir_grpacct6_name=None,
1628            dir_grpacct6_sid=None,dir_grpacct6_priv=None):
1629        """Modify iLO directory configuration, only values that are specified
1630             will be changed."""
1631        vars = dict(locals())
1632        del vars['self']
1633
1634        # The _priv thing is a comma-separated list of numbers, but other
1635        # functions use names, and the iLO ssh interface shows different names.
1636        # Support them all.
1637        privmap = {
1638            'login':         1,
1639            'rc':            2,
1640            'remote_cons':   2,
1641            'vm':            3,
1642            'virtual_media': 3,
1643            'power':         4,
1644            'reset_server':  4,
1645            'config':        5,
1646            'config_ilo':    5,
1647            'admin':         6,
1648        }
1649
1650        # create special case for element with text inside
1651        if dir_kerberos_keytab:
1652            keytab_el = etree.Element('DIR_KERBEROS_KEYTAB')
1653            keytab_el.text = dir_kerberos_keytab
1654            del vars['dir_kerberos_keytab']
1655
1656        elements = []
1657        for key, val in vars.items():
1658            if val is None:
1659                continue
1660            if key.endswith('_priv'):
1661                if isinstance(val, basestring):
1662                    val = val.replace('oemhp_', '').replace('_priv', '').split(',')
1663                val = ','.join([str(privmap.get(x,x)) for x in val])
1664            else:
1665                val = str({True: 'Yes', False: 'No'}.get(val, val))
1666            elements.append(etree.Element(key.upper(), VALUE=val))
1667
1668        if dir_kerberos_keytab:
1669            elements.append(keytab_el)
1670        return self._control_tag('DIR_INFO','MOD_DIR_CONFIG',elements=elements)
1671
1672
1673    def mod_snmp_im_settings(self, snmp_access=None, web_agent_ip_address=None,
1674            snmp_address_1=None, snmp_address_1_rocommunity=None, snmp_address_1_trapcommunity=None,
1675            snmp_address_2=None, snmp_address_2_rocommunity=None, snmp_address_2_trapcommunity=None,
1676            snmp_address_3=None, snmp_address_3_rocommunity=None, snmp_address_3_trapcommunity=None,
1677            snmp_port=None, snmp_trap_port=None, snmp_v3_engine_id=None, snmp_passthrough_status=None,
1678            trap_source_identifier=None, os_traps=None, rib_traps=None, cold_start_trap_broadcast=None,
1679            snmp_v1_traps=None, cim_security_mask=None, snmp_sys_location=None, snmp_sys_contact=None,
1680            agentless_management_enable=None, snmp_system_role=None, snmp_system_role_detail=None,
1681            snmp_user_profile_1=None, snmp_user_profile_2=None, snmp_user_profile_3=None):
1682        """Configure the SNMP and Insight Manager integration settings. The
1683           trapcommunity settings must be dicts with keys value (the name of
1684           the community) and version (1 or 2c)"""
1685        vars = dict(locals())
1686        del vars['self']
1687        elements = [etree.Element(x.upper(), VALUE=str({True: 'Yes', False: 'No'}.get(vars[x], vars[x])))
1688                    for x in vars if vars[x] is not None and 'trapcommunity' not in x and 'snmp_user_profile' not in x]
1689        for key in vars:
1690            if 'trapcommunity' in key and vars[key]:
1691                val = vars[key]
1692                for key_ in val.keys():
1693                    val[key_.upper()] = str(val.pop(key_))
1694                elements.append(etree.Element(key.upper(), **val))
1695            elif 'snmp_user_profile' in key and vars[key]:
1696                elt = etree.Element(key[:-2].upper(), {'INDEX': key[-1]})
1697                for key, val in vars[key].items():
1698                    etree.SubElement(elt, key.upper(), VALUE=str(val))
1699                elements.append(elt)
1700        return self._control_tag('RIB_INFO', 'MOD_SNMP_IM_SETTINGS', elements=elements)
1701    mod_snmp_im_settings.requires_dict = ['snmp_user_profile_1', 'snmp_user_profile_2', 'snmp_user_profile_3',
1702            'snmp_address_1_trapcommunity', 'snmp_address_2_trapcommunity', 'snmp_address_3_trapcommunity']
1703
1704    def mod_sso_settings(self, trust_mode=None, user_remote_cons_priv=None,
1705            user_reset_server_priv=None, user_virtual_media_priv=None,
1706            user_config_ilo_priv=None, user_admin_priv=None,
1707            operator_login_priv=None, operator_remote_cons_priv=None,
1708            operator_reset_server_priv=None, operator_virtual_media_priv=None,
1709            operator_config_ilo_priv=None, operator_admin_priv=None,
1710            administrator_login_priv=None, administrator_remote_cons_priv=None,
1711            administrator_reset_server_priv=None, administrator_virtual_media_priv=None,
1712            administrator_config_ilo_priv=None, administrator_admin_priv=None):
1713        vars = dict(locals())
1714        del vars['self']
1715        del vars['trust_mode']
1716        elements = []
1717        if trust_mode is not None:
1718            elements.append(etree.Element('TRUST_MODE', attrib={'VALUE': trust_mode}))
1719        vars = [(x.upper().split('_', 1), {True: 'Yes', False: 'No'}.get(vars[x], vars[x])) for x in vars if vars[x]]
1720        elements += [etree.Element(x[0][0] + '_ROLE', attrib={x[0][1]: x[1]}) for x in vars]
1721        return self._control_tag('SSO_INFO', 'MOD_SSO_SETTINGS', elements=elements)
1722
1723    def mod_twofactor_settings(self, auth_twofactor_enable=None, cert_revocation_check=None, cert_owner_san=None, cert_owner_subject=None):
1724        """Modify the twofactor authenticatino settings"""
1725        elements = []
1726        if auth_twofactor_enable is not None:
1727            elements.append(etree.Element('AUTH_TWOFACTOR_ENABLE', VALUE=['No', 'Yes'][bool(auth_twofactor_enable)]))
1728        if cert_revocation_check is not None:
1729            elements.append(etree.Element('CERT_REVOCATION_CHECK', VALUE=['No', 'Yes'][bool(cert_revocation_check)]))
1730        if cert_owner_san:
1731            elements.append(etree.Element('CERT_OWNER_SAN'))
1732        if cert_owner_subject:
1733            elements.append(etree.Element('CERT_OWNER_SUBJECT'))
1734        return self._control_tag('RIB_INFO', 'MOD_TWOFACTOR_SETTINGS', elements=elements)
1735
1736
1737    def mod_user(self, user_login, user_name=None, password=None,
1738            admin_priv=None, remote_cons_priv=None, reset_server_priv=None,
1739            virtual_media_priv=None, config_ilo_priv=None):
1740        """Set attributes for a user, only specified arguments will be changed.
1741           All arguments except user_name and password should be boolean"""
1742
1743        attrs = locals()
1744        elements = []
1745        if attrs['user_name'] is not None:
1746            elements.append(etree.Element('USER_NAME', VALUE=attrs['user_name']))
1747        if attrs['password'] is not None:
1748            elements.append(etree.Element('PASSWORD', VALUE=DoNotEscapeMe(attrs['password'])))
1749        for attribute in [x for x in attrs.keys() if x.endswith('_priv')]:
1750            if attrs[attribute] is not None:
1751                val = ['No', 'Yes'][bool(attrs[attribute])]
1752                elements.append(etree.Element(attribute.upper(), VALUE=val))
1753
1754        return self._control_tag('USER_INFO', 'MOD_USER', attrib={'USER_LOGIN': user_login}, elements=elements)
1755
1756    def press_pwr_btn(self):
1757        """Press the power button"""
1758        return self._control_tag('SERVER_INFO', 'PRESS_PWR_BTN')
1759
1760    def profile_apply(self, desc_name, action):
1761        """Apply a deployment profile"""
1762        elements = [
1763            etree.Element('PROFILE_DESC_NAME', attrib={'VALUE': desc_name}),
1764            etree.Element('PROFILE_OPTIONS', attrib={'VALUE': 'none'}), # Currently unused
1765            etree.Element('PROFILE_ACTION', attrib={'VALUE': action}),
1766        ]
1767        return self._control_tag('RIB_INFO', 'PROFILE_APPLY', elements=elements)
1768
1769    def profile_apply_get_results(self):
1770        """Retrieve the results of the last profile_apply"""
1771        return self._info_tag('RIB_INFO', 'PROFILE_APPLY_GET_RESULTS')
1772
1773    def profile_delete(self, desc_name):
1774        """Delet the specified deployment profile"""
1775        return self._control_tag('RIB_INFO', 'PROFILE_DELETE', elements=[etree.Element('PROFILE_DESC_NAME', attrib={'VALUE': desc_name})])
1776
1777    def profile_desc_download(self, desc_name, name, description, blob_namespace='perm', blob_name=None, url=None):
1778        """Make the iLO download a blob and create a deployment profile"""
1779        elements = [
1780            etree.Element('PROFILE_DESC_NAME', attrib={'VALUE': desc_name}),
1781            etree.Element('PROFILE_NAME', attrib={'VALUE': name}),
1782            etree.Element('PROFILE_DESCRIPTION', attrib={'VALUE': description}),
1783            etree.Element('PROFILE_SCHEMA', attrib={'VALUE': 'intelligentprovisioning.1.0.0'}),
1784        ]
1785        if blob_namespace:
1786            elements.append(etree.Element('BLOB_NAMESPACE', attrib={'VALUE': blob_namespace}))
1787        if blob_name:
1788            elements.append(etree.Element('BLOB_NAME', attrib={'VALUE': blob_name}))
1789        else:
1790            elements.append(etree.Element('BLOB_NAME', attrib={'VALUE': desc_name}))
1791        if url:
1792            elements.append(etree.Element('PROFILE_URL', attrib={'VALUE': url}))
1793        return self._control_tag('RIB_INFO', 'PROFILE_DESC_DOWNLOAD', elements=elements)
1794
1795    def profile_list(self):
1796        """List all profile descriptors"""
1797        def process(data):
1798            if isinstance(data, dict):
1799                return data.values()
1800            return data
1801        return self._info_tag('RIB_INFO', 'PROFILE_LIST', 'PROFILE_DESC_LIST', process=process)
1802
1803    def hold_pwr_btn(self, toggle=None):
1804        """Press and hold the power button"""
1805        attrib = {}
1806        if toggle is not None:
1807            attrib['TOGGLE'] = ['No', 'Yes'][bool(toggle)]
1808        return self._control_tag('SERVER_INFO', 'HOLD_PWR_BTN', attrib=attrib)
1809
1810    def cold_boot_server(self):
1811        """Force a cold boot of the server"""
1812        return self._control_tag('SERVER_INFO', 'COLD_BOOT_SERVER')
1813
1814    def warm_boot_server(self):
1815        """Force a warm boot of the server"""
1816        return self._control_tag('SERVER_INFO', 'WARM_BOOT_SERVER')
1817
1818    def reset_rib(self):
1819        """Reset the iLO/RILOE board"""
1820        return self._control_tag('RIB_INFO', 'RESET_RIB')
1821
1822    def reset_server(self):
1823        """Power cycle the server"""
1824        return self._control_tag('SERVER_INFO', 'RESET_SERVER')
1825
1826    def send_snmp_test_trap(self):
1827        """Send an SNMP test trap to the configured alert destinations"""
1828        return self._control_tag('RIB_INFO', 'SEND_SNMP_TEST_TRAP')
1829
1830    def set_ahs_status(self, status):
1831        """Enable or disable AHS logging"""
1832        status = {True: 'enable', False: 'disable'}[status]
1833        return self._control_tag('RIB_INFO', 'SET_AHS_STATUS', attrib={'VALUE': status})
1834
1835    def set_asset_tag(self, asset_tag):
1836        """Set the server asset tag"""
1837        return self._control_tag('SERVER_INFO', 'SET_ASSET_TAG', attrib={'VALUE': asset_tag})
1838
1839    def set_critical_temp_remain_off(self, value):
1840        """Set whether the server will remain off after a critical temperature shutdown"""
1841        status = {True: 'Yes', False: 'No'}[value]
1842        return self._control_tag('SERVER_INFO', 'SET_CRITICAL_TEMP_REMAIN_OFF', attrib={'VALUE': value})
1843
1844    def set_ers_direct_connect(self, user_id, password, proxy_url=None,
1845            proxy_port=None, proxy_username=None, proxy_password=None):
1846        """Register your iLO with HP Insigt Online using Direct Connect. Note
1847           that you must also call dc_registration_complete"""
1848        elements = [
1849            etree.Element('ERS_HPP_USER_ID', attrib={'VALUE': str(user_id)}),
1850            etree.Element('ERS_HPP_PASSWORD', attrib={'VALUE': str(password)}),
1851        ]
1852        for key, value in locals().items():
1853            if key.startswith('proxy_') and value is not None:
1854                elements.append(etree.Element('ERS_WEB_' + key, attrib={'VALUE': str(value)}))
1855        return self._control_tag('RIB_INFO', 'SET_ERS_DIRECT_CONNECT', elements=elements)
1856
1857    def set_ers_irs_connect(self, ers_destination_url, ers_destination_port):
1858        """Connect to an Insight Remote Support server"""
1859        elements = [
1860            etree.Element('ERS_DESTINATION_URL', attrib={'VALUE': str(ers_destination_url)}),
1861            etree.Element('ERS_DESTINATION_PORT', attrib={'VALUE': str(ers_destination_port)}),
1862        ]
1863        return self._control_tag('RIB_INFO', 'SET_ERS_IRS_CONNECT', elements=elements)
1864
1865    def set_ers_web_proxy(self, proxy_url, proxy_port, proxy_username=None,
1866            proxy_password=None):
1867        """Register your iLO with HP Insigt Online using Direct Connect. Note
1868           that you must also call dc_registration_complete"""
1869        elements = []
1870        for key, value in locals().items():
1871            if key.startswith('proxy_') and value is not None:
1872                elements.append(etree.Element('ERS_WEB_' + key, attrib={'VALUE': str(value)}))
1873        return self._control_tag('RIB_INFO', 'SET_ERS_WEB_PROXY', elements=elements)
1874
1875    def set_federation_multicast(self, multicast_federation_enabled=True, multicast_discovery_enabled=True,
1876                                 multicast_announcement_interval=600, ipv6_multicast_scope="Site", multicast_ttl=5):
1877        """Set the Federation multicast configuration"""
1878        multicast_federation_enabled = {True: 'Yes', False: 'No'}[multicast_federation_enabled]
1879        multicast_discovery_enabled = {True: 'Yes', False: 'No'}[multicast_discovery_enabled]
1880        elements = [
1881            etree.Element('MULTICAST_FEDERATION_ENABLED', attrib={'VALUE': multicast_federation_enabled}),
1882            etree.Element('MULTICAST_DISCOVERY_ENABLED', attrib={'VALUE': multicast_discovery_enabled}),
1883            etree.Element('MULTICAST_ANNOUNCEMENT_INTERVAL', attrib={'VALUE': str(multicast_announcement_interval)}),
1884            etree.Element('IPV6_MULTICAST_SCOPE', attrib={'VALUE': str(ipv6_multicast_scope)}),
1885            etree.Element('MULTICAST_TTL', attrib={'VALUE': str(multicast_ttl)}),
1886        ]
1887        return self._control_tag('RIB_INFO', 'SET_FEDERATION_MULTICAST', elements=elements)
1888
1889
1890    def set_language(self, lang_id):
1891        """Set the default language. Only EN, JA and ZH are supported"""
1892        return self._control_tag('RIB_INFO', 'SET_LANGUAGE', attrib={'LANG_ID': lang_id})
1893
1894    def set_host_power(self, host_power=True):
1895        """Turn host power on or off"""
1896        power = ['No', 'Yes'][bool(host_power)]
1897        return self._control_tag('SERVER_INFO', 'SET_HOST_POWER', attrib={'HOST_POWER': power})
1898
1899    def set_host_power_saver(self, host_power_saver):
1900        """Set the configuration of the ProLiant power regulator"""
1901        mapping = {'off': 1, 'min': 2, 'auto': 3, 'max': 4}
1902        host_power_saver = str(mapping.get(str(host_power_saver).lower(), host_power_saver))
1903        return self._control_tag('SERVER_INFO', 'SET_HOST_POWER_SAVER', attrib={'HOST_POWER_SAVER': host_power_saver})
1904
1905    def set_one_time_boot(self, device):
1906        """Set one time boot device, device should be one of normal, floppy,
1907           cdrom, hdd, usb, rbsu or network. Ilo 4 also supports EMB-MENU
1908           (Displays the default boot menu), EMB-ACU (Boots into ACU),
1909           EMB-HPSUM-AUTO (Boots HPSUM in automatic update mode), EMB-DIAGS
1910           (Launches Insight Diagnostics for Linux in interactive mode) and
1911           RBSU (Boots into the system RBSU)"""
1912        if not device.lower().startswith('boot'):
1913            device = device.upper()
1914        return self._control_tag('SERVER_INFO', 'SET_ONE_TIME_BOOT', attrib={'VALUE': device})
1915
1916    def set_pending_boot_mode(self, boot_mode):
1917        """Set the boot mode for the next boot to UEFI or legacy"""
1918        return self._control_tag('SERVER_INFO', 'SET_PENDING_BOOT_MODE', attrib={'VALUE': boot_mode.upper()})
1919
1920    def set_persistent_boot(self, devices):
1921        """Set persistent boot order, devices should be comma-separated"""
1922        elements = []
1923        if isinstance(devices, basestring):
1924            devices = devices.split(',')
1925        for device in devices:
1926            if not device.lower().startswith('boot'):
1927                device = device.upper()
1928            elements.append(etree.Element('DEVICE', VALUE=device))
1929        return self._control_tag('SERVER_INFO', 'SET_PERSISTENT_BOOT', elements=elements)
1930
1931    def set_pers_mouse_keyboard_enabled(self, enabled):
1932        """Enable/disable persistent mouse and keyboard"""
1933        enabled = {True: 'Yes', False: 'No'}.get(enabled,enabled)
1934        return self._control_tag('SERVER_INFO', 'SET_PERS_MOUSE_KEYBOARD_ENABLED', attrib={'VALUE': enabled})
1935
1936    def set_pwreg(self, type, threshold=None, duration=None):
1937        """Set the power alert threshold"""
1938        elements = [etree.Element('PWRALERT', TYPE=type)]
1939        if type.lower() != "disabled":
1940            elements.append(etree.Element('PWRALERT_SETTINGS', THRESHOLD=str(threshold), DURATION=str(duration)))
1941        return self._control_tag('SERVER_INFO', 'SET_PWREG', elements=elements)
1942
1943    def set_power_cap(self, power_cap):
1944        """Set the power cap feature to a specific value"""
1945        return self._control_tag('SERVER_INFO', 'SET_POWER_CAP', attrib={'POWER_CAP': str(power_cap)})
1946
1947    def set_security_msg(self, security_msg, security_msg_text=''):
1948        """Enables/disables the security message on the iLO login screen and sets its value"""
1949        enabled = str({True: 'Yes', False: 'No'}.get(security_msg, security_msg))
1950        text = etree.Element('SECURITY_MSG_TEXT')
1951        text.append(CDATA(security_msg_text))
1952        elements = (etree.Element('SECURITY_MSG', VALUE=enabled), text)
1953        return self._control_tag('RIB_INFO', 'SET_SECURITY_MSG', elements=elements)
1954
1955    def set_server_auto_pwr(self, setting):
1956        """Set the automatic power on delay setting. Valid settings are False,
1957           True (for minumum delay), 15, 30, 45 60 (for that amount of delay)
1958           or random (for a random delay of up to 60 seconds.)"""
1959        setting = str({True: 'Yes', False: 'No'}.get(setting, setting))
1960        return self._control_tag('SERVER_INFO', 'SERVER_AUTO_PWR', attrib={'VALUE': setting})
1961
1962    def set_server_fqdn(self, fqdn):
1963        """Set the fqdn of the server"""
1964        return self._control_tag('SERVER_INFO', 'SERVER_FQDN', attrib={"VALUE": fqdn})
1965
1966    def set_server_name(self, name):
1967        """Set the name of the server"""
1968        try:
1969            return self._control_tag('SERVER_INFO', 'SERVER_NAME', attrib={"VALUE": name})
1970        except IloError:
1971            # In their infinite wisdom, HP decided that only this tag should use value
1972            # instead of VALUE. And only for certain hardware/firmware combinations.
1973            # slowclap.mp3
1974            return self._control_tag('SERVER_INFO', 'SERVER_NAME', attrib={"value": name})
1975
1976    def set_vf_status(self, boot_option="boot_once", write_protect=True):
1977        """Set the parameters of the RILOE virtual floppy specified virtual
1978        media. Valid boot options are boot_once, boot_always, no_boot, connect
1979        and disconnect."""
1980        write_protect = ['NO', 'YES'][bool(write_protect)]
1981        elements = [
1982            etree.Element('VF_BOOT_OPTION', value=boot_option.upper()),
1983            etree.Element('VF_WRITE_PROTECT', value=write_protect),
1984        ]
1985        return self._control_tag('RIB_INFO', 'SET_VF_STATUS', elements=elements)
1986
1987    def set_vm_status(self, device="cdrom", boot_option="boot_once", write_protect=True):
1988        """Set the parameters of the specified virtual media. Valid boot
1989           options are boot_once, boot_always, no_boot, connect and disconnect.
1990           Valid devices are floppy and cdrom"""
1991
1992        write_protect = ['NO', 'YES'][bool(write_protect)]
1993        elements = [
1994            etree.Element('VM_BOOT_OPTION', value=boot_option.upper()),
1995            etree.Element('VM_WRITE_PROTECT', value=write_protect),
1996        ]
1997        return self._control_tag('RIB_INFO', 'SET_VM_STATUS', attrib={'DEVICE': device.upper()},
1998                                 elements=elements)
1999
2000    def start_dir_test(self, dir_admin_distinguished_name, dir_admin_password, test_user_name, test_user_password):
2001        """Test directory authentication with the specified credentials"""
2002        vars = locals()
2003        del vars['self']
2004        elements = [etree.Element(x, VALUE=vars[x]) for x in vars]
2005        return self._control_tag('DIR_INFO', 'START_DIR_TEST', elements=elements)
2006
2007    def trigger_bb_data(self, message_id, days):
2008        """Initiate AHS data submission to IRS. The submitted data will include
2009           the specified message ID and number of days of data"""
2010        return self._control_tag('RIB_INFO', 'TRIGGER_BB_DATA', attrib={'MESSAGE_ID': message_id, 'BB_DAYS': days})
2011
2012    def trigger_l2_collection(self, message_id):
2013        """Initiate an L2 data collection submission to the Insight Remote Support server."""
2014        element = etree.Element('MESSAGE_ID', attrib={'value': str(message_id)})
2015        return self._control_tag('RIB_INFO', 'TRIGGER_L2_COLLECTION', elements=[element])
2016
2017    def trigger_test_event(self, message_id):
2018        """Trigger a test service event submission to the Insight Remote Support server."""
2019        element = etree.Element('MESSAGE_ID', attrib={'value': str(message_id)})
2020        return self._control_tag('RIB_INFO', 'TRIGGER_TEST_EVENT', elements=[element])
2021
2022    def uid_control(self, uid=False):
2023        """Turn the UID light on ("Yes") or off ("No")"""
2024        if isinstance(uid, basestring):
2025            uid = {'on': True, 'yes': True, 'off': False, 'no': False}.get(uid.lower(), uid)
2026        uid = ['No', 'Yes'][bool(uid)]
2027        return self._control_tag('SERVER_INFO', 'UID_CONTROL', attrib={"UID": uid.title()})
2028
2029    def update_rib_firmware(self, filename=None, version=None, progress=None):
2030        """Upload new RIB firmware, either specified by filename (.bin or
2031           .scexe) or version number. Use "latest" as version number to
2032           download and use the latest available firmware.
2033
2034           API note:
2035
2036           As this function may take a while, you can choose to receive
2037           progress messages by passing a callable in the progress parameter.
2038           This callable will be called many times to inform you about upload
2039           and flash progress."""
2040
2041        if self.delayed:
2042            raise IloError("Cannot run firmware update in delayed mode")
2043
2044        if self.read_response:
2045            raise IloError("Cannot run firmware update in read_response mode")
2046
2047        if not self.protocol:
2048            self._detect_protocol()
2049
2050        # Backwards compatibility
2051        if filename == 'latest':
2052            version = 'latest'
2053            filename = None
2054
2055        if filename and version:
2056            raise ValueError("Supply a filename or a version number, not both")
2057
2058        if not (filename or version):
2059            raise ValueError("Supply a filename or a version number")
2060
2061        current_version = self.get_fw_version()
2062        ilo = current_version['management_processor'].lower()
2063
2064        if not filename:
2065            config = hpilo_fw.config(self.firmware_mirror)
2066            if version == 'latest':
2067                if ilo not in config:
2068                    raise IloError("Cannot update %s to the latest version automatically" % ilo)
2069                version = config[ilo]['version']
2070            iversion = '%s %s' % (ilo, version)
2071            if iversion not in config:
2072                raise ValueError("Unknown firmware version: %s" % version)
2073            if current_version['firmware_version'] >= version:
2074                return "Already up-to-date"
2075            hpilo_fw.download(iversion, progress=progress)
2076            filename = config[iversion]['file']
2077        else:
2078            filename = hpilo_fw.parse(filename, ilo)
2079
2080        fwlen = os.path.getsize(filename)
2081        root, inner = self._root_element('RIB_INFO', MODE='write')
2082        etree.SubElement(inner, 'TPM_ENABLED', VALUE='Yes')
2083        inner = etree.SubElement(inner, 'UPDATE_RIB_FIRMWARE', IMAGE_LOCATION=filename, IMAGE_LENGTH=str(fwlen))
2084        if self.protocol == ILO_LOCAL:
2085            return self._request(root, progress)[1]
2086        elif self.protocol == ILO_RAW:
2087            inner.tail = '$EMBED:%s$' % filename
2088            return self._request(root, progress)[1]
2089        else:
2090            self._upload_file(filename, progress)
2091            return self._request(root, progress)[1]
2092
2093    def xmldata(self, item='all'):
2094        """Get basic discovery data which all iLO versions expose over
2095           unauthenticated https. The default item to query is 'all'. Despite
2096           its name, it does not return all information. To get license
2097           information, use 'cpqkey' as argument."""
2098        if self.delayed:
2099            raise IloError("xmldata is not compatible with delayed mode")
2100        if item.lower() not in ('all', 'cpqkey'):
2101            raise IloError("unsupported xmldata argument '%s', must be 'all' or 'cpqkey'" % item)
2102
2103        if self.read_response:
2104            with open(self.read_response) as fd:
2105                data = fd.read()
2106        else:
2107            url = 'https://%s:%s/xmldata?item=%s' % (self.hostname, self.port, item)
2108            if hasattr(ssl, 'create_default_context'):
2109                ctx = ssl.create_default_context()
2110                ctx.check_hostname = False
2111                ctx.verify_mode = ssl.CERT_NONE
2112                opener = urllib2.build_opener(urllib2.ProxyHandler({}), urllib2.HTTPSHandler(context=ctx))
2113            else:
2114                opener = urllib2.build_opener(urllib2.ProxyHandler({}))
2115            req = opener.open(url, None, self.timeout)
2116            data = req.read()
2117            self._debug(1, str(req.headers).rstrip() + "\n\n" + data.decode('ascii', 'iloxml_replace'))
2118        if self.save_response:
2119            fd = open(self.save_response, 'a')
2120            fd.write(data)
2121            fd.close()
2122        return self._element_children_to_dict(etree.fromstring(data))
2123
2124    def _parse_infra2_XXXX(self, element, key, ctag):
2125        ret = {key: []}
2126        for elt in element:
2127            tag = elt.tag.lower()
2128            if tag == 'bays':
2129                ret['bays'] = self._element_to_list(elt)
2130            elif tag == ctag:
2131                ret[key].append(self._element_children_to_dict(elt))
2132            else:
2133                ret[tag] = elt.text
2134        return {key: ret}
2135
2136    _parse_infra2_blades = lambda self, element: self._parse_infra2_XXXX(element, 'blades', 'blade')
2137    _parse_infra2_switches = lambda self, element: self._parse_infra2_XXXX(element, 'switches', 'switch')
2138    _parse_infra2_managers = lambda self, element: self._parse_infra2_XXXX(element, 'managers', 'manager')
2139    _parse_infra2_lcds = lambda self, element: self._parse_infra2_XXXX(element, 'lcds', 'lcd')
2140    _parse_infra2_fans = lambda self, element: self._parse_infra2_XXXX(element, 'fans', 'fan')
2141
2142    def _parse_infra2_power(self, element):
2143        ret = self._parse_infra2_XXXX(element, 'power', 'powersupply')
2144        ret['power']['powersupply'] = ret['power'].pop('power')
2145        return ret
2146
2147    def _parse_blade_portmap(self, element):
2148        ret = {'mezz': []}
2149        for elt in element:
2150            if elt.tag.lower() == 'mezz':
2151                ret['mezz'].append(self._element_children_to_dict(elt))
2152            elif elt.tag.lower() == 'status':
2153                ret[elt.tag.lower()] = elt.text.strip()
2154        return {'portmap': ret}
2155
2156    def _parse_mezz_slot(self, element):
2157        ret = {'port': []}
2158        for elt in element:
2159            if elt.tag.lower() == 'port':
2160                ret['port'].append(self._element_children_to_dict(elt))
2161            elif elt.tag.lower() == 'type':
2162                ret[elt.tag.lower()] = elt.text.strip()
2163        return {'slot': ret}
2164
2165    _parse_portmap_slot = _parse_mezz_slot
2166
2167    def _parse_mezz_device(self, element):
2168        ret = {'port': []}
2169        for elt in element:
2170            if elt.tag.lower() == 'port':
2171                ret['port'].append(self._element_children_to_dict(elt))
2172            else:
2173                ret[elt.tag.lower()] = elt.text.strip()
2174        return {'device': ret}
2175
2176    def _parse_temps_temp(self, element):
2177        ret = {'thresholds': []}
2178        for elt in element:
2179            if elt.tag.lower() == 'threshold':
2180                ret['thresholds'].append(self._element_children_to_dict(elt))
2181            else:
2182                ret[elt.tag.lower()] = elt.text
2183        return ret
2184
2185    xmldata_ectd = {
2186        'hsi': ('virtual',),
2187        'bladesystem': ('manager',),
2188        'infra2': ('diag', 'dim', 'vcm', 'vm'),
2189        'blade': ('bay', 'diag', 'portmap', 'power', 'vmstat'),
2190        'switch': ('bay', 'diag', 'portmap', 'power'),
2191        'manager': ('bay', 'diag', 'power'),
2192        'lcd': ('bay', 'diag'),
2193        'fan': ('bay',),
2194        'powersupply': ('bay', 'diag'),
2195    }
2196