def copy_ssh_key(self, hostname, ip):
        """Copy SSH public key to remote system.

        This method will inject the public SSH key into remote system. It
        will create the public key content from the private key given.
        """

        ssh_key = self.provider_params.get('ssh_key')
        username = self.provider_params.get('username')
        password = self.provider_params.get('password')

        # setup absolute path for private key
        private_key = os.path.join(self.workspace, ssh_key)

        # set permission of the private key
        try:
            os.chmod(private_key, stat.S_IRUSR | stat.S_IWUSR)
        except OSError as ex:
            raise BeakerProvisionerError(
                'Error setting private key file permissions: %s' % ex)

        self.logger.info('Generating SSH public key from private..')

        # generate public key from private
        public_key = os.path.join(self.workspace, ssh_key + ".pub")
        rsa_key = paramiko.RSAKey(filename=private_key)
        with open(public_key, 'w') as f:
            f.write('%s %s\n' % (rsa_key.get_name(), rsa_key.get_base64()))

        self.logger.info('Successfully generated SSH public key from private!')

        self.logger.info('Send SSH key to remote system %s:%s' %
                         (hostname, ip))

        # send the key to the beaker machine
        ssh_con = paramiko.SSHClient()
        ssh_con.set_missing_host_key_policy(paramiko.AutoAddPolicy())

        try:
            ssh_con.connect(hostname=ip, username=username, password=password)
            sftp = ssh_con.open_sftp()
            sftp.put(public_key, '/root/.ssh/authorized_keys')
        except paramiko.SSHException as ex:
            raise BeakerProvisionerError(
                'Failed to connect to remote system: %s' % ex)
        except IOError as ex:
            raise BeakerProvisionerError('Failed sending public key: %s' % ex)
        finally:
            ssh_con.close()

        self.logger.debug("Successfully sent key: {0}, "
                          "{1}".format(ip, hostname))
    def get_machine_info(self, xmldata):
        """Get the remote system information from the beaker results XML.

        This method will parse the beaker results XML to get the hostname and
        IP address.

        :param xmldata: XML data of a beaker job status.
        :type xmldata: dict
        """
        try:
            dom = parseString(xmldata)
        except Exception as ex:
            raise BeakerProvisionerError('Failed reading XML data: %s.' % ex)

        tasklist = dom.getElementsByTagName('task')
        for task in tasklist:
            cname = task.getAttribute('name')

            if cname == '/distribution/install' or cname == '/distribution/check-install':
                hostname = task.getElementsByTagName('system')[0]. \
                    getAttribute("value")
                addr = socket.gethostbyname(hostname)
                try:
                    hostname = hostname.split('.')[0]
                except Exception:
                    pass

                return hostname, addr
    def submit_bkr_xml(self):
        """Submit a beaker job XML to Beaker.

        This method will upload (submit) a beaker job XML to Beaker. If the
        job was successfully uploaded, the beaker job id will be returned.
        """
        # setup beaker client job submit commnand
        _cmd = "bkr job-submit --xml %s" % os.path.join(
            self.data_folder, self.job_xml)

        self.logger.info('Submitting beaker job XML..')
        self.logger.debug('Command to be run: %s' % _cmd)

        # submit beaker XML
        results = exec_local_cmd(_cmd)
        if results[0] != 0:
            self.logger.error(results[2])
            raise BeakerProvisionerError('Failed to submit beaker job XML!')
        output = results[1]

        # post results tasks
        if output.find("Submitted:") != "-1":
            mod_output = output[output.find("Submitted:"):]

            # set the result as ascii instead of unicode
            job_id = mod_output[mod_output.find("[") + 2:mod_output.find("]") -
                                1]
            job_url = os.path.join(self.url, 'jobs', job_id[2:])

            self.logger.info('Beaker job ID: %s.' % job_id)
            self.logger.info('Beaker job URL: %s.' % job_url)

            self.logger.info('Successfully submitted beaker XML!')

            return job_id, job_url
 def _connect(self):
     """Connect to beaker."""
     data = exec_local_cmd('bkr whoami')
     if data[0] != 0:
         self.logger.error(data[2])
         raise BeakerProvisionerError('Connection to beaker failed!')
     self.logger.info('Connected to beaker!')
    def get_job_status(self, xmldata):
        """Parse the beaker results.

        :param xmldata: XML data from beaker job status fetched.
        :type xmldata: str
        :return: Install (task) results
        :rtype: dict
        """
        mydict = {}
        # parse xml data string
        try:
            dom = parseString(xmldata)
        except Exception as ex:
            raise BeakerProvisionerError('Failed reading XML data: %s.' % ex)

        # check job status
        joblist = dom.getElementsByTagName('job')

        # verify it is a length of 1 else exception
        if len(joblist) != 1:
            raise BeakerProvisionerError(
                'Unable to parse job results from %s.' %
                self.provider_params.get('job_id'))

        mydict["job_result"] = joblist[0].getAttribute("result")
        mydict["job_status"] = joblist[0].getAttribute("status")

        tasklist = dom.getElementsByTagName('task')

        for task in tasklist:
            cname = task.getAttribute('name')
            if cname == '/distribution/install' or cname == '/distribution/check-install':
                mydict["install_result"] = task.getAttribute('result')
                mydict["install_status"] = task.getAttribute('status')

        if "install_status" in mydict and mydict["install_status"]:
            return mydict
        else:
            raise BeakerProvisionerError('Could not find install task status!')
    def cancel_job(self, job_id):
        """Cancel a existing beaker job.

        This method will cancel a existing beaker job using the job id.
        """

        # setup beaker job cancel command
        _cmd = "bkr job-cancel {0}".format(job_id)

        self.logger.info('Canceling beaker job..')

        # cancel beaker job
        results = exec_local_cmd(_cmd)
        if results[0] != 0:
            self.logger.error(results[2])
            raise BeakerProvisionerError('Failed to cancel job.')
        output = results[1]

        if "Cancelled" in output:
            self.logger.info("Job %s cancelled." % job_id)
        else:
            raise BeakerProvisionerError('Failed to cancel beaker job!')

        self.logger.info('Successfully cancelled beaker job!')
    def gen_bkr_xml(self):
        """Create beaker job xml based on host requirements.

        This method builds xml content and writes xml to file.
        """
        # set beaker xml absolute file path
        bkr_xml_file = os.path.join(self.data_folder, self.job_xml)

        # set attributes for beaker xml object
        for key, value in self.provider_params.items():
            if key is not 'name':
                if value:
                    setattr(self.bkr_xml, key, value)

        # generate beaker job xml (workflow-simple command)
        self.bkr_xml.generate_beaker_xml(bkr_xml_file,
                                         kickstart_path=self.workspace,
                                         savefile=True)

        if 'force' in self.bkr_xml.hrname:
            self.logger.warning(
                'Force was specified as a host_require_option.'
                'Any other host_require_options will be ignored since '
                'force is a mutually exclusive option in beaker.')
        # format beaker client command to run
        # Latest version of beaker client fails to generate xml with this
        # replacement
        # _cmd = self.bkr_xml.cmd.replace('=', "\=")

        self.logger.info('Generating beaker job XML..')
        self.logger.debug('Command to be run: %s' % self.bkr_xml.cmd)

        # generate beaker job XML
        results = exec_local_cmd(self.bkr_xml.cmd)
        if results[0] != 0:
            self.logger.error(results[2])
            raise BeakerProvisionerError('Failed to generate beaker job XML!')
        output = results[1]

        # generate complete beaker job XML
        self.bkr_xml.generate_xml_dom(bkr_xml_file, output, savefile=True)
        self.logger.info('Successfully generated beaker job XML!')
    def analyze_results(self, resultsdict):
        """Analyze the beaker job install task status.
        return success, fail, or warn based on the job and install task statuses

        :param resultsdict: Beaker job of install task status.
        :rtype: dict
        :return: Action such as [wait, success or fail]
        :rtype: str
        """
        # when is the job complete
        # TODO: explain what each beaker results analysis means
        if resultsdict["job_result"].strip().lower() == "new" and \
                resultsdict["job_status"].strip().lower() in \
                ["new", "waiting", "queued", "scheduled", "processed",
                 "installing"]:
            return "wait"
        elif resultsdict["install_result"].strip().lower() == "new" and \
                resultsdict["install_status"].strip().lower() in \
                ["new", "waiting", "queued", "scheduled", "running",
                 "processed"]:
            return "wait"
        elif resultsdict["job_status"].strip().lower() == "waiting" or \
                resultsdict["install_status"].strip().lower() == "waiting":
            return "wait"
        elif resultsdict["job_result"].strip().lower() == "pass" and \
                resultsdict["job_status"].strip().lower() == "running" and \
                resultsdict["install_result"].strip().lower() == "pass" and \
                resultsdict["install_status"].strip().lower() == "completed":
            return "success"
        elif resultsdict["job_result"].strip().lower() == "new" and \
                resultsdict["job_status"].strip().lower() == "running" and \
                resultsdict["install_result"].strip().lower() == "new" and \
                resultsdict["install_status"].strip().lower() == "completed":
            return "success"
        elif resultsdict["job_result"].strip().lower() == "warn":
            return "fail"
        elif resultsdict["job_result"].strip().lower() == "fail":
            return "fail"
        else:
            raise BeakerProvisionerError('Unexpected job status: %s!' %
                                         resultsdict)
    def wait_for_bkr_job(self, job_id):
        """Wait for submitted beaker job to have complete status.

        This method will wait for the beaker job to be complete depending on
        the timeout set. Users can define their own custom timeout or it will
        wait indefinitely for the machine to be provisioned.
        """
        # set max wait time (default is 8 hours)
        wait = self.provider_params.get('bkr_timeout', None)
        if wait is None:
            wait = 28800

        self.logger.debug('Beaker timeout limit: %s.' % wait)

        # check Beaker status every 60 seconds
        total_attempts = wait / 60

        attempt = 0
        while wait > 0:
            attempt += 1
            self.logger.info('Waiting for machine to be ready, attempt %s of '
                             '%s.' % (attempt, total_attempts))

            # setup beaker job results command
            _cmd = "bkr job-results %s" % job_id

            self.logger.debug('Fetching beaker job status..')

            # fetch beaker job status
            results = exec_local_cmd(_cmd)
            if results[0] != 0:
                self.logger.error(results[2])
                raise BeakerProvisionerError('Failed to fetch job status!')
            xml_output = results[1]

            self.logger.debug('Successfully fetched beaker job status!')

            bkr_job_status_dict = self.get_job_status(xml_output)
            self.logger.debug("Beaker job status: %s" % bkr_job_status_dict)
            status = self.analyze_results(bkr_job_status_dict)

            self.logger.info('Beaker Job: id: %s status: %s.' %
                             (job_id, status))

            if status == "wait":
                wait -= 60
                time.sleep(60)
                continue
            elif status == "success":
                self.logger.info("Machine is successfully provisioned from "
                                 "Beaker!")
                # get machine info
                return self.get_machine_info(xml_output)
            elif status == "fail":
                raise BeakerProvisionerError(
                    'Beaker job %s provision failed!' % job_id)
            else:
                raise BeakerProvisionerError(
                    'Beaker job %s has unknown status!' % job_id)

        # timeout reached for Beaker job
        self.logger.error('Maximum number of attempts reached!')

        # cancel job
        self.cancel_job(job_id)

        raise BeakerProvisionerError(
            'Timeout reached waiting for beaker job to finish!')
Example #10
0
def test_beaker_provisioner_error():
    with pytest.raises(BeakerProvisionerError):
        raise BeakerProvisionerError('error message')