Automatic Deployment
[genesis.git] / fuel / deploy / hardware_adapters / hp / hp_adapter.py
diff --git a/fuel/deploy/hardware_adapters/hp/hp_adapter.py b/fuel/deploy/hardware_adapters/hp/hp_adapter.py
new file mode 100644 (file)
index 0000000..7ce0dc9
--- /dev/null
@@ -0,0 +1,411 @@
+import re
+import time
+from netaddr import EUI, mac_unix
+import logging
+
+from run_oa_command import RunOACommand
+
+
+LOG = logging.getLogger(__name__)
+out_hdlr = logging.FileHandler(__file__.split('.')[0] + '.log', mode='w')
+out_hdlr.setFormatter(logging.Formatter('%(levelname)s: %(message)s'))
+LOG.addHandler(out_hdlr)
+LOG.setLevel(logging.DEBUG)
+
+class HpAdapter(object):
+
+    # Exception thrown at any kind of failure to get the requested
+    # information.
+    class NoInfoFoundError(Exception):
+        pass
+
+    # Totally failed to connect so a re-try with other HW should
+    # be done. This exception should never escape this class.
+    class InternalConnectError(Exception):
+        pass
+
+    # Format MAC so leading zeroes are displayed
+    class mac_dhcp(mac_unix):
+        word_fmt = "%.2x"
+
+    def __init__(self, mgmt_ip, username, password):
+        self.mgmt_ip = mgmt_ip
+        self.username = username
+        self.password = password
+        self.oa_error_message = ''
+
+    def get_blade_mac_addresses(self, shelf, blade):
+
+        LOG.debug("Entering: get_mac_addr_hp(%d,%d)" % (shelf, blade))
+        self.oa_error_message = ''
+        oa = RunOACommand(self.mgmt_ip, self.username, self.password)
+
+        LOG.debug("Connect to active OA for shelf %d" % shelf)
+        try:
+            res = oa.connect_to_active()
+        except:
+            raise self.InternalConnectError(oa.error_message)
+        if res is None:
+            raise self.InternalConnectError(oa.error_message)
+        if not oa.connected():
+            raise self.NoInfoFoundError(oa.error_message)
+
+        cmd = ("show server info " + str(blade))
+
+        LOG.debug("Send command to OA: %s" % cmd)
+        try:
+            serverinfo = oa.send_command(cmd)
+        except:
+            raise self.NoInfoFoundError(oa.error_message)
+        finally:
+            oa.close()
+
+        (left, right) = self.find_mac(serverinfo, shelf, blade)
+
+        left = EUI(left, dialect=self.mac_dhcp)
+        right = EUI(right, dialect=self.mac_dhcp)
+        return [str(left), str(right)]
+
+    def get_blade_hardware_info(self, shelf, blade=None):
+
+        if blade:
+            LOG.debug("Entering: get_hp_info(%d,%d)" % (shelf, blade))
+        else:
+            LOG.debug("Entering: get_hp_info(%d)" % shelf)
+
+        self.oa_error_message = ''
+        oa = RunOACommand(self.mgmt_ip, self.username, self.password)
+
+        LOG.debug("Connect to active OA for shelf %d" % shelf)
+
+        try:
+            res = oa.connect_to_active()
+        except:
+            self.oa_error_message = oa.error_message
+            return None
+        if res is None:
+            self.oa_error_message = oa.error_message
+            return None
+        if not oa.connected():
+            self.oa_error_message = oa.error_message
+            return None
+
+        # If no blade specified we're done we know this is an HP at this point
+        if not blade:
+            oa.close()
+            return "HP"
+
+        check = "show server info %d" % blade
+        LOG.debug("Send command to OA: %s" % check)
+        output = oa.send_command("%s" % check)
+        oa.close()
+
+        match = r"Product Name:\s+(.+)\Z"
+        if re.search(match, str(output[:])) is None:
+            self.oa_error_message = ("Blade %d in shelf %d does not exist\n"
+                                     % (blade, shelf))
+            return None
+
+        for line in output:
+            seobj = re.search(match, line)
+            if seobj:
+                return "HP %s" % seobj.group(1)
+        return False
+
+    def power_off_blades(self, shelf, blade_list):
+        return self.set_state(shelf, 'locked', blade_list=blade_list)
+
+    def power_on_blades(self, shelf, blade_list):
+        return self.set_state(shelf, 'unlocked', blade_list=blade_list)
+
+    def power_off_blade(self, shelf, blade):
+        return self.set_state(shelf, 'locked', one_blade=blade)
+
+    def power_on_blade(self, shelf, blade):
+        return self.set_state(shelf, 'unlocked', one_blade=blade)
+
+    def set_boot_order_blade(self, shelf, blade):
+        return self.set_boot_order(shelf, one_blade=blade)
+
+    def set_boot_order_blades(self, shelf, blade_list):
+        return self.set_boot_order(shelf, blade_list=blade_list)
+
+
+
+    # Search HP's OA server info for MAC for left and right control
+    def find_mac(self, serverinfo, shelf, blade):
+        left = False
+        right = False
+        for line in serverinfo:
+            if ("No Server Blade Installed" in line or
+                    "Invalid Arguments" in line):
+                raise self.NoInfoFoundError("Blade %d in shelf %d "
+                                            "does not exist." % (blade, shelf))
+            seobj = re.search(r"LOM1:1-a\s+([0-9A-F:]+)", line, re.I)
+            if seobj:
+                left = seobj.group(1)
+            else:
+                seobj = re.search(r"LOM1:2-a\s+([0-9A-F:]+)", line, re.I)
+                if seobj:
+                    right = seobj.group(1)
+            if left and right:
+                return left, right
+        raise self.NoInfoFoundError("Could not find MAC for blade %d "
+                                    "in shelf %d." % (blade, shelf))
+
+    # Do power on or off on all configured blades in shelf
+    # Return None to indicate that no connection do OA succeeded,
+    # Return False to indicate some connection to OA succeeded,
+    # or config error
+    # Return True to indicate that power state succesfully updated
+    # state: locked, unlocked
+    def set_state(self, shelf, state, one_blade=None, blade_list=None):
+
+        if state not in ['locked', 'unlocked']:
+            return None
+
+        if one_blade:
+            LOG.debug("Entering: set_state_hp(%d,%s,%d)" %
+                      (shelf, state, one_blade))
+        else:
+            LOG.debug("Entering: set_state_hp(%d,%s)" % (shelf, state))
+
+        self.oa_error_message = ''
+
+        oa = RunOACommand(self.mgmt_ip, self.username, self.password)
+
+        LOG.debug("Connect to active OA for shelf %d" % shelf)
+
+        try:
+            res = oa.connect_to_active()
+        except:
+            self.oa_error_message = oa.error_message
+            return None
+        if res is None:
+            self.oa_error_message = oa.error_message
+            return None
+        if not oa.connected():
+            self.oa_error_message = oa.error_message
+            return False
+
+        if one_blade:
+            blades = [one_blade]
+        else:
+            blades = sorted(blade_list)
+
+        LOG.debug("Check if blades are present")
+
+        check = "show server list"
+
+        LOG.debug("Send command to OA: %s" % check)
+        output = oa.send_command(check)
+        first = True
+        bladelist = ''
+        for blade in blades:
+            prog = re.compile(r"\s+" + str(blade) + r"\s+\[Absent\]",
+                              re.MULTILINE)
+            if prog.search(str(output[:])) is not None:
+                oa.close()
+                self.oa_error_message = ("Blade %d in shelf %d "
+                                         % (blade, shelf))
+                if one_blade:
+                    self.oa_error_message += ("does not exist.\n"
+                                         "Set state %s not performed.\n"
+                                         % state)
+                else:
+                    self.oa_error_message += (
+                        "specified but does not exist.\nSet "
+                        "state %s not performed on shelf %d\n"
+                        % (state, shelf))
+                return False
+            if not first:
+                bladelist += ","
+            else:
+                first = False
+            bladelist += str(blade)
+
+        if blade_list:
+            LOG.debug("All blades present")
+
+        # Use leading upper case on On/Off so it can be reused in match
+        extra = ""
+        if state == "locked":
+            powerstate = "Off"
+            extra = "force"
+        else:
+            powerstate = "On"
+
+        cmd = "power%s server %s" % (powerstate, bladelist)
+
+        if extra != "":
+            cmd += " %s" % extra
+
+        LOG.debug("Send command to OA: %s" % cmd)
+
+        try:
+            oa.send_command(cmd)
+        except:
+            self.oa_error_message = oa.error_message
+            oa.close()
+            return False
+
+        # Check that all blades reach the state which can take some time,
+        # so re-try a couple of times
+        LOG.debug("Check if state %s successfully set" % state)
+        recheck = 2
+        while True:
+            LOG.debug("Send command to OA: %s" % check)
+            try:
+                output = oa.send_command(check)
+            except:
+                self.oa_error_message = oa.error_message
+                oa.close()
+                return False
+            for blade in blades:
+                match = (r"\s+" + str(blade) +
+                         r"\s+\w+\s+\w+.\w+.\w+.\w+\s+\w+\s+%s" %
+                         powerstate)
+                prog = re.compile(match, re.MULTILINE)
+                if prog.search(str(output[:])) is None:
+                    recheck -= 1
+                    if recheck >= 0:
+                        # Re-try
+                        time.sleep(3)
+                        break
+                    oa.close()
+                    self.oa_error_message = (
+                        "Could not set state %s on blade %d in shelf %d\n"
+                        % (state, one_blade, shelf))
+                    for line in output:
+                        self.oa_error_message += line
+                    return False
+            else:
+                # state reached for all blades, exit the infinite loop
+                break
+
+        if one_blade:
+            LOG.debug("State %s successfully set on blade %d in shelf %d"
+                      % (state, one_blade, shelf))
+        else:
+            LOG.debug("State %s successfully set on blades %s in shelf %d"
+                      % (state, blade_list, shelf))
+        oa.close()
+        return True
+
+    # Change boot order on all blades in shelf
+    # Return None to indicate that no connection do OA succeeded,
+    # Return False to indicate some connection to OA succeeded,
+    # or config error,
+    # Return True to indicate that boot order succesfully changed
+    def set_boot_order(self, shelf, one_blade=None, blade_list=None):
+
+        if one_blade:
+            LOG.debug("Entering: set_bootorder_hp(%d,%d)" % (shelf, one_blade))
+        else:
+            LOG.debug("Entering: set_bootorder_hp(%d)" % shelf)
+
+        self.oa_error_message = ''
+
+        oa = RunOACommand(self.mgmt_ip, self.username, self.password)
+
+        LOG.debug("Connect to active OA for shelf %d" % shelf)
+
+        try:
+            res = oa.connect_to_active()
+        except:
+            self.oa_error_message = oa.error_message
+            return None
+        if res is None:
+            self.oa_error_message = oa.error_message
+            return None
+        if not oa.connected():
+            self.oa_error_message = oa.error_message
+            return False
+
+        if one_blade:
+            blades = [one_blade]
+        else:
+            blades = sorted(blade_list)
+
+        LOG.debug("Check if blades are present")
+
+        check = "show server list"
+
+        LOG.debug("Send command to OA: %s" % check)
+
+        output = oa.send_command(check)
+        first = True
+        bladelist = ''
+        for blade in blades:
+            prog = re.compile(r"\s+" + str(blade) + r"\s+\[Absent\]",
+                              re.MULTILINE)
+            if prog.search(str(output[:])) is not None:
+                oa.close()
+                self.oa_error_message = ("Blade %d in shelf %d "
+                                         % (blade, shelf))
+                if one_blade:
+                    self.oa_error_message += (
+                        "does not exist.\nChange boot order not performed.\n")
+                else:
+                    self.oa_error_message += (
+                        "specified but does not exist.\n"
+                        "Change boot order not performed on shelf %d\n"
+                        % shelf)
+                return False
+            if not first:
+                bladelist += ','
+            else:
+                first = False
+            bladelist += str(blade)
+
+        if blade_list:
+            LOG.debug("All blades present")
+
+        # Boot origins are pushed so first set boot from hard disk, then PXE
+        # NB! If we want to support boot from SD we must add USB to the "stack"
+        cmd1 = "set server boot first hdd %s" % bladelist
+        cmd2 = "set server boot first pxe %s" % bladelist
+        for cmd in [cmd1, cmd2]:
+
+            LOG.debug("Send command to OA: %s" % cmd)
+            try:
+                output = oa.send_command(cmd)
+            except:
+                self.oa_error_message = oa.error_message
+                for line in output:
+                    self.oa_error_message += line
+                oa.close()
+                return False
+
+        # Check that all blades got the correct boot order
+        # Needs updating if USB is added
+        LOG.debug("Check if boot order successfully set")
+        match = (r"^.*Boot Order\):\',\s*\'(\\t)+PXE NIC 1\',\s*\'(\\t)"
+                 r"+Hard Drive")
+        prog = re.compile(match)
+        for blade in blades:
+
+            check = "show server boot %d" % blade
+
+            LOG.debug("Send command to OA: %s" % check)
+            try:
+                output = oa.send_command(check)
+            except:
+                self.oa_error_message = oa.error_message
+                oa.close()
+                return False
+            if prog.search(str(output[:])) is None:
+                oa.close()
+                self.oa_error_message = ("Failed to set boot order on blade "
+                                         "%d in shelf %d\n" % (blade, shelf))
+                for line in output:
+                    self.oa_error_message += line
+                return False
+            LOG.debug("Boot order successfully set on blade %d in shelf %d"
+                      % (blade, shelf))
+
+        if blade_list:
+            LOG.debug("Boot order successfully set on all configured blades "
+                      "in shelf %d" % (shelf))
+        oa.close()
+        return True