Esempio n. 1
0
    def post_job(self, payload=None):
        """
        Post an empty job to the drp api
        to get a new job.

        :type payload: dict
        :param payload: Python dict containing a models.job.Job
                        with a machine uuid set.
        :return:
        """
        resource = self.endpoint + "/jobs"
        if payload is None:
            raise ValueError("payload can not be None type.")
        payload = json.dumps(payload)
        jsonbytes = payload.encode('utf-8')
        headers = self.headers
        headers.update({"Content-Type": "application/json; charset=utf-8"})
        r = urllib.request.Request(resource, data=jsonbytes, headers=headers,
                                   method="POST")
        try:
            res = urllib.request.urlopen(r, context=self.context)
            logger.debug("POST_JOB Response: {} {}".format(
                res.status,
                res.msg
            ))
            if res.status == 204:
                return {}
            data = res.read().decode('utf-8')
            json_obj = json.loads(data)
            return json_obj
        except urllib.error.HTTPError as res:
            return {'Error': res.code}
        except RemoteDisconnected as res:
            return {'Error': res.code}
Esempio n. 2
0
    def put_job_log(self, job=None, log_msg=None):
        """
        PUT a log message to DRP about a given job.

        :type job: Job
        :param job:
        :type log_msg: str
        :param log_msg:
        :return:
        """
        if job is None:
            raise ValueError("job can not be None.")
        if log_msg is None:
            raise ValueError("log_message can not be None")
        resource = self.endpoint + "/jobs/{}/log".format(job)
        log_msg = log_msg.encode("utf-8")
        headers = self.headers
        headers.update({"Content-Type": "application/octet-stream"})
        r = urllib.request.Request(resource, data=log_msg, headers=headers,
                                   method="PUT")
        try:
            res = urllib.request.urlopen(r, context=self.context)
            logger.debug("PUT_JOB_LOG Response: {} {}".format(
                res.status,
                res.msg
            ))
            if res.status == 204:
                return True
        except urllib.error.HTTPError:
            return False
        return False
Esempio n. 3
0
 def _get_job_actions(self, agent_state=None):
     jr = "jobs/{}/actions?os=esxi".format(agent_state.job.Uuid)
     ja_list = agent_state.client.get(resource=jr)
     logger.debug("Successfully retrieved {} job actions for job {}".format(
         len(ja_list), agent_state.job.Uuid))
     job_actions = []
     for ja in ja_list:
         job_actions.append(JobAction(**ja))
     return job_actions
Esempio n. 4
0
    def _create_job(self, agent_state=None):
        """

        :rtype: Job
        :return: Job or Error
        """
        logger.debug("Creating job for {}".format(agent_state.machine.Uuid))
        job = Job()
        job.Machine = agent_state.machine.Uuid
        logger.debug("POSTing {}".format(job.__dict__))
        j_obj = agent_state.client.post_job(payload=job.__dict__)  # type: dict
        if "Error" in j_obj:
            logger.error("Error in j_obj for create_job. {}".format(j_obj))
            self._set_machine_current_job_state(state="failed",
                                                agent_state=agent_state)
            time.sleep(3)
        return Job(**j_obj)
Esempio n. 5
0
    def post(self, resource=None, payload=None, **kwargs):
        """

        :param resource:
        :param payload:
        :param kwargs:
        :return:
        """
        resource = self.endpoint + "/{}".format(resource)
        if payload is None:
            raise ValueError("payload can not be None type.")
        payload = json.dumps(payload)
        jsonbytes = payload.encode('utf-8')
        headers = self.headers
        headers.update({"Content-Type": "application/json; charset=utf-8"})
        r = urllib.request.Request(resource, data=jsonbytes, headers=headers,
                                   method="POST")
        try:
            res = urllib.request.urlopen(r, context=self.context)
            logger.debug("POST {} Response: {} {}".format(
                resource,
                res.status,
                res.msg
            ))
            if res.status < 299:
                data = res.read().decode('utf-8')
                json_obj = json.loads(data)
                return json_obj
        except urllib.error.HTTPError as res:
            j_err = res.read().decode('utf-8')
            jobj = json.loads(j_err)
            ermsg = "Error in POST: {}\nPayload: {}\nHeaders: {}\nResponse: {}"
            logger.debug(ermsg.format(
                resource,
                jsonbytes,
                headers,
                jobj
            ))
            logger.exception(res)
            return {'Error': jobj}
        except RemoteDisconnected as res:
            return {'Error': res.code}
Esempio n. 6
0
    def patch(self, resource=None, payload=None):
        """
        Given a resource and a payload of an RFC6902 patch
        make a PATCH request to update the given resource with
        the given payload.

        Example:
        obj = client.patch(
              resource="machines/6d109287-dffa-4344-a727-2b17970ca210",
              payload=payload)

        :type resource: str
        :param resource: machines/uuid, jobs/uuid, etc..
        :type payload: str
        :param payload: '[{"value": "b", "op": "replace", "path": "/1"},
                          {"value": "v", "op": "replace", "path": "/2"}]'
        :return: python object
        """
        url = self.endpoint + "/{}".format(resource)
        jsondata = json.loads(payload)
        jsondata = json.dumps(jsondata)
        jsonbytes = jsondata.encode('utf-8')
        headers = self.headers
        headers.update({"Content-Type": "application/json; charset=utf-8"})
        r = urllib.request.Request(url, data=jsonbytes, headers=headers,
                                   method="PATCH")
        try:
            res = urllib.request.urlopen(r, context=self.context)
            data = res.read().decode('utf-8')
            json_obj = json.loads(data)
            return json_obj
        except urllib.error.HTTPError as res:
            logger.debug("Failed to patch.")
            logger.debug("Resource: {}".format(url))
            logger.debug("Payload: {}".format(jsonbytes))
            logger.exception(res)
            return {'Error': res.code}
        except RemoteDisconnected as res:
            logger.debug("Failed to patch due to RemoteDisconnected.")
            logger.debug("Resource: {}".format(url))
            logger.debug("Payload: {}".format(jsonbytes))
            logger.exception(res)
            return {'Error': res.code}
Esempio n. 7
0
    def get(self, resource=None):
        """
        Primitive get request that will return a json_decoded
        object of the resource. The actual type will vary depending
        on what the API returns.

        :type resource: str
        :param resource: An API resource like: info, objects, machines, etc..
        :return:
        """
        h = {**self.headers}
        if "machines" in resource:
            h.update(self.longpoll_wait_header)
            h.update(self.machine_etag_header)
        url = self.endpoint + "/{}".format(resource)
        logger.debug("Request resource: {}".format(resource))
        logger.debug("Request headers: {}".format(h))
        r = urllib.request.Request(url, headers=h)
        retry_count = 0
        while retry_count < 31536000:
            logger.debug("Current retry_count {}".format(retry_count))
            if retry_count > 0:
                # ty to not flood the server to death
                logger.debug("Inside the count. {}".format(retry_count))
                time.sleep(2)
            try:
                logger.debug("Trying to open request to resource: {}".format(
                    resource))
                res = urllib.request.urlopen(r, context=self.context)
                logger.debug("Response from server {}".format(res))
                data = res.read().decode('utf-8')
                json_obj = json.loads(data)
                if "machines" in resource:
                    logger.debug(
                        "Made a machine call. Updating Etag ID from {}".format(
                            self.machine_etag_header.get("If-None-Match")
                        )
                    )
                    res_headers = dict(res.getheaders())
                    self.machine_etag_header.update(
                        {'If-None-Match': res_headers.get('Etag')}
                    )
                    logger.debug("Updated machine_etag to: {}".format(
                        res_headers.get('Etag'))
                    )
                return json_obj
            except json.decoder.JSONDecodeError:
                return data
            except urllib.error.HTTPError as e:
                if e.code == 304:
                    # expected status for the Prefer timeout
                    # do not bump the retry count here
                    pass
                if e.code == 404:
                    logger.info("Requested resource not found. {}".format(
                        resource)
                    )
                    error = e.read().decode('utf-8')
                    obj = json.loads(error)
                    logger.debug("Response payload: {}".format(obj))
                    if "machines" in resource:
                        msg = "The machine ID has changed from the value "
                        msg += "stored in the config. This was caused by "
                        msg += "human error. The most likely cause was "
                        msg += "removing the machine from drp and adding it "
                        msg += "back some how. Please see KB 00065 "
                        msg += "or contact [email protected] for more "
                        msg += "assistance."
                        logger.info(msg)
                        raise DRPExitException
                    retry_count += 1
                if e.code == 403:
                    logger.debug("Token expired.")
                    raise DRPExitException
                if e.code > 399:
                    logger.debug("Got error from remote endpoint: {}".format(
                        e.code
                    ))
                    logger.exception(e)
                    retry_count += 1
            except (urllib.error.URLError, RemoteDisconnected) as e:
                logger.debug("Failed to fetch resource: {}".format(
                    resource))
                logger.exception(e)
                time.sleep(5)
                retry_count += 1
                saved_error = e
        raise saved_error
Esempio n. 8
0
    def on_event(self, *args, **kwargs):
        agent_state = kwargs.get("agent_state")
        machine_uuid = kwargs.get("machine_uuid")
        logger.debug("Fetching machine: {}".format(machine_uuid))
        try:
            agent_state.machine = self._get_machine(agent_state=agent_state,
                                                    machine_uuid=machine_uuid)
        except DRPExitException:
            return Exit(), agent_state
        logger.debug("Retrieved machine: {}-{}".format(
            agent_state.machine.Name, agent_state.machine.Uuid))
        if not agent_state.machine.Runnable:
            logger.debug("Machine not runnable. Patching.")
            m_copy = copy.deepcopy(agent_state.machine)
            m_copy.Runnable = True
            agent_state.machine = self._patch_machine(agent_state, m_copy)
            logger.debug("Successfully Patched machine to runnable")

        if agent_state.machine.CurrentJob != "":
            logger.debug("Existing Job {} for {}-{}".format(
                agent_state.machine.CurrentJob, agent_state.machine.Name,
                agent_state.machine.Uuid))
            logger.debug("Loading copy of Job into agent_state")
            try:
                agent_state.job = self._get_job(agent_state=agent_state)
                logger.debug("Current job state: {}".format(
                    agent_state.job.State))
                if (agent_state.job.State == "running"
                        or agent_state.job.State == "created"):
                    agent_state = self._set_job_state(state="failed",
                                                      agent_state=agent_state)
                    logger.debug("Current Job closed on startup: {}".format(
                        agent_state.__dict__))
            except Exception as e:
                logger.debug(e.message)
                logger.debug("Current Job is not present: {}".format(
                    agent_state.machine.CurrentJob))
        logger.debug("Setting Etag back to 0 from Initialize -> WaitRunnable")
        agent_state.client.machine_etag_header = {'If-None-Match': 0}
        return WaitRunnable(), agent_state
Esempio n. 9
0
    def _run_job_actions(self, agent_state=None, actions=None):
        for index, action in enumerate(actions):
            final = len(actions) == index - 1
            agent_state.stop = False
            agent_state.poweroff = False
            agent_state.reboot = False
            agent_state.incomplete = False
            agent_state.failed = False
            if 'OS' in action.Meta:
                if 'esxi' not in action.Meta['OS']:
                    logger.debug("Found non ESXi OS. Skipping JobAction.")
                    continue

            if action.Path != "":
                logger.debug("Attempting to add a file to the file system.")
                try:
                    action_runner.add_file(job_action=action)
                    log_msg = "Added {} to the file system".format(action.Path)
                    logger.debug(log_msg)
                    c = agent_state.client  # type: Client
                    c.put_job_log(job=agent_state.job.Uuid, log_msg=log_msg)
                except Exception as e:
                    agent_state.failed = True
                    log_msg = "Failed to add file {}".format(action.Path)
                    logger.error(log_msg)
                    m_copy = copy.deepcopy(agent_state.machine)
                    m_copy.Runnable = False
                    self._patch_machine(agent_state, m_copy)
                    logger.exception(e)
                    c = agent_state.client  # type: Client
                    log_msg += "\n"
                    log_msg += repr(e)
                    c.put_job_log(job=agent_state.job.Uuid, log_msg=log_msg)
                    return agent_state, -1
            else:
                logger.debug("Running command on system. {}".format(
                    action.Name))
                result = action_runner.run_command(
                    job_action=action,
                    timeout=agent_state.config.command_timeout,
                    expath=agent_state.config.command_path)
                log_msg = ""
                code = int(result.get("Exit_Code"))
                if code != 0:
                    agent_state.failed = True
                    log_msg += "Failed to run command on system.\n"
                    logger.error(log_msg)
                    m_copy = copy.deepcopy(agent_state.machine)
                    m_copy.Runnable = False
                    self._patch_machine(agent_state, m_copy)
                    if code == 16:
                        agent_state.stop = True
                        agent_state.failed = False
                    elif code == 32:
                        agent_state.poweroff = True
                        agent_state.failed = False
                    elif code == 64:
                        agent_state.reboot = True
                        agent_state.failed = False
                    elif code == 128:
                        agent_state.incomplete = True
                        agent_state.failed = False
                    elif code == 144:
                        agent_state.stop = True
                        agent_state.incomplete = True
                        agent_state.failed = False
                    elif code == 160:
                        agent_state.incomplete = True
                        agent_state.poweroff = True
                        agent_state.failed = False
                    elif code == 192:
                        agent_state.incomplete = True
                        agent_state.reboot = True
                        agent_state.failed = False
                c = agent_state.client  # type: Client
                log_msg += "Command: {}".format(action.Name)
                log_msg += "\nErrors: "
                log_msg += result.get("Errors").decode('utf-8')
                log_msg += "\nOutput: "
                log_msg += result.get("Out").decode('utf-8')
                log_msg += "\nExit Code: {}".format(code)
                c.put_job_log(job=agent_state.job.Uuid, log_msg=log_msg)

                # If a non-final action sets the incomplete flag, it actually
                # means early success and stop processing actions for this
                # task.  This allows actions to be structured in an
                # "early exit" fashion.
                #
                # Only the final action can actually set things as incomplete.
                if final and agent_state.incomplete:
                    agent_state.incomplete = False
                    return agent_state, code
                if agent_state.failed:
                    return agent_state, code
                if (agent_state.reboot or agent_state.poweroff
                        or agent_state.stop):
                    agent_state.incomplete = not final
                    return agent_state, code
        return agent_state, None
Esempio n. 10
0
    def on_event(self, *args, **kwargs):
        agent_state = kwargs.get("agent_state")

        logger.debug("Creating a new job for {}-{}".format(
            agent_state.machine.Name, agent_state.machine.Uuid))
        agent_state.job = self._create_job(agent_state=agent_state)
        if agent_state.job.Uuid is None:
            return WaitRunnable(), agent_state
        logger.debug("Resetting Machine Etag to 0.")
        agent_state.client.machine_etag_header = {'If-None-Match': 0}
        logger.debug("Successfully created job {}".format(
            agent_state.job.Uuid))

        # if task contains a colon - skip it
        if ":" in agent_state.job.Task:
            agent_state.wait = True
            return self._handle_state(agent_state=agent_state)
        if agent_state.job.State != "running":
            logger.debug("Setting job {} to state running from {}".format(
                agent_state.job.Uuid, agent_state.job.State))
            agent_state = self._set_job_state(agent_state=agent_state,
                                              state="running")
        logger.debug("Fetching job actions for job {}".format(
            agent_state.job.Uuid))
        job_actions = self._get_job_actions(agent_state=agent_state)
        agent_state, action_results = self._run_job_actions(
            agent_state=agent_state, actions=job_actions)
        logger.debug("Finished running Job Actions.")
        logger.debug("Setting Job State.")
        agent_state = self._set_job_state(state="finished",
                                          agent_state=agent_state)
        return self._handle_state(agent_state=agent_state,
                                  action_results=action_results)