def setUp(self):
        """ Create temporary user for doing ssh on localhost """
        self.hostname = "localhost"
        self.__user = "******"
        self.__passwd = "P@ssw0rd"
        e_pass = crypt.crypt(self.__passwd, "22")
        os.system(f"sudo useradd -M -p {e_pass} {self.__user}")

        # Let ssh connection be alive across all tests
        self.session = SSHChannel(self.hostname, self.__user, self.__passwd, sftp_enabled=True)
Example #2
0
 def __init__(self, hostname, username=None, port=22, password=None):
     '''init method'''
     super(SSHRemoteExecutor, self).__init__(hostname, port)
     self._username = username
     self._password = password
     try:
         self._ssh_client = SSHChannel(self._hostname, self._username, \
                                         self._password, self._port)
     except Exception as err:
         Log.error(f'SSHRemoteExecutor, some error occured while connecting \
                     to SSH channel {err}')
def test_bmc_config(args):
    """Check if BMC configuration are valid.

    Testing BMC config with ipmitool is possible only when ipmi over lan
    is configured(out-band setup). It is taken care by test_bmc_is_accessible.
    So, validation on bmc onfiguration with bmc ip, user and secret value
    through ssh is fine at this time.
    """
    bmc_ip = Conf.get(GLOBAL_CONF, BMC_IP_KEY)
    bmc_user = Conf.get(GLOBAL_CONF, BMC_USER_KEY)
    bmc_secret = Conf.get(GLOBAL_CONF, BMC_SECRET_KEY)
    bmc_key = Cipher.generate_key(MACHINE_ID, "server_node")
    bmc_passwd = Cipher.decrypt(bmc_key,
                                bmc_secret.encode("utf-8")).decode("utf-8")
    # check BMC ip, user, password are valid
    session = SSHChannel(bmc_ip, bmc_user, bmc_passwd)
    session.disconnect()
Example #4
0
    def validate(self, v_type, args):
        """
        Process BMC validations.

        Usage (arguments to be provided):
        1. bmc accessible <node> <bmc_ip> <bmc_user> <bmc_passwd>
        2. bmc stonith <node> <bmc_ip> <bmc_user> <bmc_passwd>
        """
        if not isinstance(args, list):
            raise VError(errno.EINVAL, f"Invalid parameters '{args}'")

        if len(args) < 1:
            raise VError(errno.EINVAL, "No parameters specified")

        if len(args) < 4:
            raise VError(
                errno.EINVAL,
                f"Insufficient parameters '{args}' for 'bmc validate'. Refer usage."
            )
        elif len(args) > 4:
            raise VError(
                errno.EINVAL,
                f"Too many parameters '{args}' for 'bmc validate'. Refer usage."
            )

        # Root user to execute ipmitool command
        user = "******"
        node = args[0]
        self.session = SSHChannel(node,
                                  user,
                                  pkey_filename='/root/.ssh/id_rsa_prvsnr')

        if v_type == "accessible":
            self.validate_bmc_accessibility(node, args[1], args[2], args[3])
        elif v_type == "stonith":
            self.validate_bmc_stonith_config(node, args[1], args[2], args[3])
        else:
            raise VError(errno.EINVAL,
                         f"Action parameter '{v_type}' not supported")

        self.session.disconnect()
Example #5
0
    def validate_controller_accessibility(self, ip, username, password):
        """Check contoller console is accessible to node."""
        # Check if ssh connection is successful
        try:
            session = SSHChannel(host=ip, username=username, password=password)
            session.disconnect()
        except:
            err = traceback.format_exc()
            raise VError(
                errno.EINVAL,
                "Failed to create ssh connection to %s, Error: %s" % (ip, err))

        # ping controller IP
        cmd = "ping -c 1 -W 1 %s" % ip
        cmd_proc = SimpleProcess(cmd)
        _, stderr, rc = cmd_proc.run()
        if rc != 0:
            msg = "Ping failed for IP '%s'. Command: '%s', Return Code: '%s'." % (
                ip, cmd, rc)
            msg += stderr.decode("utf-8").replace('\r', '').replace('\n', '')
            raise VError(errno.EINVAL, msg)
Example #6
0
    def setUpClass(cls):
        """Create temporary user for doing ssh on localhost."""
        cls.hostname = "localhost"
        cls.__user = "******"
        cls.__passwd = "P@ssw0rd"
        e_pass = crypt.crypt(cls.__passwd, "22")
        os.system(f"useradd -M -p {e_pass} {cls.__user}")

        # Let ssh connection be alive across all tests
        cls.session = SSHChannel(cls.hostname,
                                 cls.__user,
                                 cls.__passwd,
                                 sftp_enabled=True)
Example #7
0
    def validate(self, v_type: str, args: list, host: str = None):
        """
        Process rpm validations.

        Usage (arguments to be provided):
        1. pkg validate_rpms host (optional) [packagenames]
        2. pkg validate_pip3s host (optional) [pip3 packagenames]
        host should be host url in case passwordless ssh is not configured.
        host url format - user:passwd@fqdn:port
        """
        host = socket.getfqdn(
        ) if host == None or host == "localhost" else host
        host_details = re.search(r"(\w+):(.+)@(.+):(\d+)", host)
        if host_details:
            user = host_details.groups()[0]
            passwd = host_details.groups()[1]
            host = host_details.groups()[2]
            port = host_details.groups()[3]
            self.ssh = SSHChannel(host=host,
                                  username=user,
                                  password=passwd,
                                  port=port)
        elif host != socket.getfqdn():
            # Ensure we can perform passwordless ssh and there are no prompts
            NetworkV().validate("passwordless",
                                [pwd.getpwuid(os.getuid()).pw_name, host])
            self.passwdless_ssh_enabled = True

        if v_type == "rpms":
            return self.validate_rpm_pkgs(host, args)
        elif v_type == "pip3s":
            return self.validate_pip3_pkgs(host, args)
        else:
            raise VError(errno.EINVAL,
                         "Action parameter %s not supported" % v_type)

        if self.ssh:
            self.ssh.disconnect()
Example #8
0
class SSHRemoteExecutor(RemoteExecutor):
    '''class which enables remote communication using SSH'''
    def __init__(self, hostname, username=None, port=22, password=None):
        '''init method'''
        super(SSHRemoteExecutor, self).__init__(hostname, port)
        self._username = username
        self._password = password
        try:
            self._ssh_client = SSHChannel(self._hostname, self._username, \
                                            self._password, self._port)
        except Exception as err:
            Log.error(
                f'SSHRemoteExecutor, some error occured while connecting \
                        to SSH channel {err}')

    def execute(self, command: str, secret: str = None) -> None:
        '''
        Communicates to remote node by code execution

        Parameters:
            command: command to be executed on a remote node

        Return:
            int

        Exceptions:
                RemoteExecutorError
        '''
        command_log = command.replace(
            secret, '****') if secret is not None else command
        ret_code = 0
        res = None
        Log.info(
            f'Executing command: {command_log} on a remote host: {self._hostname}'
        )
        try:
            ret_code, res = self._ssh_client.execute(command)
            if ret_code:
                raise RemoteExecutorError(
                    f'Error: Failed to \
                                            execute command {command_log} on a \
                                            remote node: {self._hostname} with \
                                            error: {res}', ret_code)
        except RemoteExecutorError as ree:
            raise ree
        except Exception as err:
            raise RemoteExecutorError(
                f"Error: {err}.Some problem occured while executing \
                                        command {command_log} on a remote node: \
                                        {self._hostname}", ret_code)
Example #9
0
    def validate_firmware(self, ip, username, password, mc_expected):
        """
        Check expected contoller bundle version found.

        mc_expected: string or list of expected version(s).
        """
        try:
            session = SSHChannel(host=ip, username=username, password=password)
        except:
            err = traceback.format_exc()
            raise VError(
                errno.EINVAL,
                "Failed to create ssh connection to %s, Error: %s'" %
                (ip, err))

        # check firmware version command execution on MC
        rc, output = session.execute(self.version_cmd)
        if (rc != 0) or (self.success_msg not in output):
            raise VError(
                errno.EINVAL,
                "Controller command failure. Command: %s Output: %s" %
                (self.version_cmd, output))

        # check expected bundle version is found on MC
        _supported = False
        if mc_expected:
            if isinstance(mc_expected, list):
                _supported = any(ver for ver in mc_expected if ver in output)
            else:
                _supported = True if mc_expected in output else False

        if not _supported:
            raise VError(
                errno.EINVAL, "Unsupported firmware version found on %s.\
                Expected controller bundle version(s): %s" % (ip, mc_expected))

        session.disconnect()
Example #10
0
class PkgV:
    """Pkg related validations."""
    def __init__(self):
        self.passwdless_ssh_enabled = False
        self.ssh = None

    def __execute_cmd(self, cmd):
        """
        Execute command using SSHChannel or SimpleProcees and returns result.
        Uses SimpleProcess to execute the command on passwordless ssh configured
        host. Otherwise, uses SSHChannel to execute command.
        """
        if self.ssh:
            retcode, result = self.ssh.execute(cmd)
        else:
            handler = SimpleProcess(cmd)
            stdout, stderr, retcode = handler.run()
            result = stdout.decode("utf-8") if retcode == 0 else stderr.decode(
                "utf-8")
        if retcode != 0:
            raise VError(errno.EINVAL,
                         "Command failure. cmd: %s stderr: %s" % (cmd, result))
        return result

    def validate(self, v_type: str, args: list, host: str = None):
        """
        Process rpm validations.
        Usage (arguments to be provided):
        1. pkg validate_rpms host (optional) [packagenames]
        2. pkg validate_pip3s host (optional) [pip3 packagenames]
        host should be host url in case passwordless ssh is not configured.
        host url format - user:passwd@fqdn:port
        """
        host = socket.getfqdn(
        ) if host == None or host == "localhost" else host
        host_details = re.search(r"(\w+):(.+)@(.+):(\d+)", host)
        if host_details:
            user = host_details.groups()[0]
            passwd = host_details.groups()[1]
            host = host_details.groups()[2]
            port = host_details.groups()[3]
            self.ssh = SSHChannel(host=host,
                                  username=user,
                                  password=passwd,
                                  port=port)
        elif host != socket.getfqdn():
            # Ensure we can perform passwordless ssh and there are no prompts
            NetworkV().validate("passwordless",
                                [pwd.getpwuid(os.getuid()).pw_name, host])
            self.passwdless_ssh_enabled = True

        if v_type == "rpms":
            return self.validate_rpm_pkgs(host, args)
        elif v_type == "pip3s":
            return self.validate_pip3_pkgs(host, args)
        else:
            raise VError(errno.EINVAL,
                         "Action parameter %s not supported" % v_type)

        if self.ssh:
            self.ssh.disconnect()

    def validate_rpm_pkgs(self, host, pkgs, skip_version_check=True):
        """Check if rpm pkg is installed."""
        cmd = "ssh %s rpm -qa" % host if self.passwdless_ssh_enabled else "rpm -qa"
        result = self.__execute_cmd(cmd)

        if not isinstance(pkgs, dict):
            skip_version_check = True

        for pkg in pkgs:
            if result.find("%s" % pkg) == -1:
                raise VError(
                    errno.EINVAL,
                    "rpm pkg %s not installed on host %s." % (pkg, host))
            if not skip_version_check:
                matched_str = re.search(r"%s-([^-][0-9.]+)-" % pkg, result)
                installed_version = matched_str.groups()[0]
                expected_version = pkgs[pkg]
                if installed_version != expected_version:
                    raise VError(
                        errno.EINVAL,
                        "Mismatched version for rpm package %s on host %s. Installed %s. Expected %s."
                        % (pkg, host, installed_version, expected_version))

    def validate_pip3_pkgs(self, host, pkgs, skip_version_check=True):
        """Check if pip3 pkg is installed."""
        cmd = "ssh %s pip3 list" % host if self.passwdless_ssh_enabled else "pip3 list"
        result = self.__execute_cmd(cmd)

        if not isinstance(pkgs, dict):
            skip_version_check = True

        for pkg in pkgs:
            if result.find("%s" % pkg) == -1:
                raise VError(
                    errno.EINVAL,
                    "pip3 pkg %s not installed on host %s." % (pkg, host))
            if not skip_version_check:
                matched_str = re.search(r"%s \((.*)\)" % pkg, result)
                installed_version = matched_str.groups()[0]
                expected_version = pkgs[pkg]
                if installed_version != expected_version:
                    raise VError(
                        errno.EINVAL,
                        "Mismatched version for pip3 package %s on host %s. Installed %s. Expected %s."
                        % (pkg, host, installed_version, expected_version))
Example #11
0
class BmcV:
    """BMC related validations."""
    def __init__(self):
        self.channel_cmd = "channel info"
        self.session = None

    def __get_bmc_ip(self, node):
        """ Get BMC IP along with status of command
        """
        cmd = "ipmitool lan print 1 | grep 'IP Address'"
        rc, output = self.session.execute(cmd)
        if rc != 0:
            msg = f"Failed to get BMC IP of {node}. Command: '{cmd}',\
                    Return Code: '{rc}'."

            msg += output
            raise VError(errno.EINVAL, msg)
        bmc_ip = output.split()[-1]
        return bmc_ip

    def __get_bmc_power_status(self, node):
        """ Get BMC power status
        """
        cmd = "ipmitool chassis status | grep 'System Power'"
        rc, output = self.session.execute(cmd)
        if rc != 0:
            msg = f"Failed to get BMC power status of {node}. Command: '{cmd}',\
                    Return Code: '{rc}'."

            msg += output
            raise VError(errno.EINVAL, msg)
        pw_status = output.split()[-1]
        return pw_status

    def __ping_bmc(self, node):
        """ Ping BMC IP
        """
        ip = self.__get_bmc_ip(node)
        cmd = f"ping -c 1 -W 1 {ip}"
        rc, output = self.session.execute(cmd)
        if rc != 0:
            msg = f"Ping failed for IP '{ip}'. Command: '{cmd}',\
                    Return Code: '{rc}'."

            msg += output
            raise VError(errno.ECONNREFUSED, msg)

    def validate(self, v_type, args):
        """
        Process BMC validations.
        Usage (arguments to be provided):
        1. bmc accessible <node> <bmc_ip> <bmc_user> <bmc_passwd>
        2. bmc stonith <node> <bmc_ip> <bmc_user> <bmc_passwd>
        """
        if not isinstance(args, list):
            raise VError(errno.EINVAL, f"Invalid parameters '{args}'")

        if len(args) < 1:
            raise VError(errno.EINVAL, "No parameters specified")

        if len(args) < 4:
            raise VError(
                errno.EINVAL,
                f"Insufficient parameters '{args}' for 'bmc validate'. Refer usage."
            )
        elif len(args) > 4:
            raise VError(
                errno.EINVAL,
                f"Too many parameters '{args}' for 'bmc validate'. Refer usage."
            )

        # Root user to execute ipmitool command
        user = "******"
        node = args[0]
        self.session = SSHChannel(node,
                                  user,
                                  pkey_filename='/root/.ssh/id_rsa_prvsnr')

        if v_type == "accessible":
            self.validate_bmc_accessibility(node, args[1], args[2], args[3])
        elif v_type == "stonith":
            self.validate_bmc_stonith_config(node, args[1], args[2], args[3])
        else:
            raise VError(errno.EINVAL,
                         f"Action parameter '{v_type}' not supported")

        self.session.disconnect()

    def validate_bmc_accessibility(self, node, bmc_ip, bmc_user, bmc_passwd):
        """ Validate BMC accessibility
        """
        # Validate bmc accessibility on inband setup
        self.validate_inband_bmc_channel(node)
        # BMC IP based validations
        # Validate bmc accessibility on outband setup
        self.validate_bmc_channel_over_lan(bmc_ip, bmc_user, bmc_passwd)
        # Check if we can ping BMC
        self.__ping_bmc(node)

    def validate_bmc_stonith_config(self, node, bmc_ip, bmc_user, bmc_passwd):
        """ Validations for BMC STONITH Configuration
        """
        cmd = f"fence_ipmilan -P -a {bmc_ip} -o status -l {bmc_user} -p {bmc_passwd}"
        rc, output = self.session.execute(cmd)
        if rc != 0:
            msg = f"Failed to check BMC STONITH Config. Command: '{cmd}',\
                    Return Code: '{rc}'."

            msg += output
            raise VError(errno.EINVAL, msg)

    def validate_inband_bmc_channel(self, node):
        """ Get BMC channel information (inband)
        """
        cmd = f"ipmitool {self.channel_cmd}"
        rc, output = self.session.execute(cmd)
        if rc != 0:
            msg = f"Failed to get BMC channel info of '{node}''. Command: '{cmd}',\
                    Return Code: '{rc}'."

            msg += output
            raise VError(errno.EINVAL, msg)
        return True

    def validate_bmc_channel_over_lan(self, bmc_ip, bmc_user, bmc_passwd):
        """ Get BMC channel information over lan (out-of-band)
        """
        # check BMC ip is accessible over lan (out of band)
        cmd = f"ipmitool -H {bmc_ip} -U {bmc_user} -P {bmc_passwd} -I lan {self.channel_cmd}"
        rc, output = self.session.execute(cmd)
        if rc != 0:
            msg = f"Failed to get BMC channel info of '{bmc_ip}' over lan. Command: '{cmd}',\
                    Return Code: '{rc}'."

            msg += output
            raise VError(errno.EINVAL, msg)
        return True
class TestSSHChannel(unittest.TestCase):
    """ Check ssh connection related validations """

    def setUp(self):
        """ Create temporary user for doing ssh on localhost """
        self.hostname = "localhost"
        self.__user = "******"
        self.__passwd = "P@ssw0rd"
        e_pass = crypt.crypt(self.__passwd, "22")
        os.system(f"sudo useradd -M -p {e_pass} {self.__user}")

        # Let ssh connection be alive across all tests
        self.session = SSHChannel(self.hostname, self.__user, self.__passwd, sftp_enabled=True)

    def test_connection_ok(self):
        """ Check if ssh connection is alive """
        if not self.session.is_connection_alive():
            raise VError(errno.EINVAL,
                f"Connection is lost with client '{self.session.host}'")

    def test_command_execution_ok(self):
        """ Check if command is executed successfully """
        cmd = "whoami"
        rc, output = self.session.execute(cmd)
        if rc !=0  or self.__user not in output:
            raise VError(
                errno.EINVAL, f"Command execution failed on {self.hostname}.\
                                Command: '{cmd}'\
                                Return Code: {rc}")

    def test_reconnect_ok(self):
        """ Check ssh re-connect after disconnection caller """
        # Close ssh connection
        self.session.disconnect()
        if self.session.is_connection_alive():
            raise VError(errno.EINVAL,
                f"SSH connection is not closed with client '{self.session.host}'")
        # Establish ssh to same client without invoking instance
        # and validate ssh connection is active
        self.session.connect()
        if not self.session.is_connection_alive():
            raise VError(errno.EINVAL,
                f"Connection is lost with client '{self.session.host}'")

    def test_send_file(self):
        # Create local file
        local_file = '/tmp/test_file.txt'
        fObj = open(local_file, 'w')
        fObj.close()
        # Send file to remote location
        remote_dir = '/tmp/tmp_remote/'
        os.makedirs(remote_dir, exist_ok=True)
        os.chmod(remote_dir, 0o657)
        remote_file = os.path.join(remote_dir, 'test_file.txt')
        self.session.send_file(local_file, remote_file)
        os.remove(local_file)
        os.system(f"rm -rf {remote_dir}")

    def test_recv_file(self):
        # Create remote file
        remote_dir = '/tmp/tmp_remote/'
        os.makedirs(remote_dir, exist_ok=True)
        remote_file = os.path.join(remote_dir, 'test_file.txt')
        fObj = open(remote_file, 'w')
        fObj.close()
        # Receive file from remote location
        local_file = '/tmp/test_file.txt'
        self.session.recv_file(remote_file, local_file)
        os.remove(remote_file)

    def tearDown(self):
        """ Cleanup the setup """
        self.session.disconnect()
        os.system(f"sudo userdel {self.__user}")