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}
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
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
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)
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}
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}
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
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
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
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)