def store_result(self, exit_status=None, output=None, stdout=None, stderr=None, result=None, script_version_id=None, timedout=False): # Don't allow ScriptResults to be overwritten unless the node is a # controller. Controllers are allowed to overwrite their results to # prevent new ScriptSets being created everytime a controller starts. # This also allows us to avoid creating an RPC call for the rack # controller to create a new ScriptSet. if not self.script_set.node.is_controller: # Allow PENDING, INSTALLING, and RUNNING scripts incase the node # didn't inform MAAS the Script was being run, it just uploaded # results. assert self.status in (SCRIPT_STATUS.PENDING, SCRIPT_STATUS.INSTALLING, SCRIPT_STATUS.RUNNING) assert self.output == b'' assert self.stdout == b'' assert self.stderr == b'' assert self.result == b'' assert self.script_version is None if timedout: self.status = SCRIPT_STATUS.TIMEDOUT elif exit_status is not None: self.exit_status = exit_status if exit_status == 0: self.status = SCRIPT_STATUS.PASSED elif self.status == SCRIPT_STATUS.INSTALLING: self.status = SCRIPT_STATUS.FAILED_INSTALLING else: self.status = SCRIPT_STATUS.FAILED if output is not None: self.output = Bin(output) if stdout is not None: self.stdout = Bin(stdout) if stderr is not None: self.stderr = Bin(stderr) if result is not None: self.result = Bin(result) try: parsed_yaml = self.read_results() except ValidationError as err: err_msg = ( "%s(%s) sent a script result with invalid YAML: %s" % (self.script_set.node.fqdn, self.script_set.node.system_id, err.message)) logger.error(err_msg) Event.objects.create_node_event( system_id=self.script_set.node.system_id, event_type=EVENT_TYPES.SCRIPT_RESULT_ERROR, event_description=err_msg) else: status = parsed_yaml.get('status') if status == 'passed': self.status = SCRIPT_STATUS.PASSED elif status == 'failed': self.status = SCRIPT_STATUS.FAILED elif status == 'degraded': self.status = SCRIPT_STATUS.DEGRADED elif status == 'timedout': self.status = SCRIPT_STATUS.TIMEDOUT if self.script: if script_version_id is not None: for script in self.script.script.previous_versions(): if script.id == script_version_id: self.script_version = script break if self.script_version is None: err_msg = ( "%s(%s) sent a script result for %s(%d) with an " "unknown script version(%d)." % (self.script_set.node.fqdn, self.script_set.node.system_id, self.script.name, self.script.id, script_version_id)) logger.error(err_msg) Event.objects.create_node_event( system_id=self.script_set.node.system_id, event_type=EVENT_TYPES.SCRIPT_RESULT_ERROR, event_description=err_msg) else: # If no script version was given assume the latest version # was run. self.script_version = self.script.script # If commissioning result check if its a builtin script, if so run its # hook before committing to the database. if (self.script_set.result_type == RESULT_TYPE.COMMISSIONING and self.name in NODE_INFO_SCRIPTS): post_process_hook = NODE_INFO_SCRIPTS[self.name]['hook'] err = ("%s(%s): commissioning script '%s' failed during " "post-processing." % (self.script_set.node.fqdn, self.script_set.node.system_id, self.name)) # Circular imports. from metadataserver.api import try_or_log_event try_or_log_event(self.script_set.node, None, err, post_process_hook, node=self.script_set.node, output=self.stdout, exit_status=self.exit_status) self.save()
def store_result( self, exit_status=None, output=None, stdout=None, stderr=None, result=None, script_version_id=None, timedout=False, ): # Controllers and Pods are allowed to overwrite their results during any status # to prevent new ScriptSets being created everytime a controller # starts. This also allows us to avoid creating an RPC call for the # rack controller to create a new ScriptSet. if (not self.script_set.node.is_controller and not self.script_set.node.is_pod): # Allow PENDING, APPLYING_NETCONF, INSTALLING, and RUNNING scripts # incase the node didn't inform MAAS the Script was being run, it # just uploaded results. assert self.status in SCRIPT_STATUS_RUNNING_OR_PENDING if timedout: self.status = SCRIPT_STATUS.TIMEDOUT elif exit_status is not None: self.exit_status = exit_status if exit_status == 0: self.status = SCRIPT_STATUS.PASSED elif self.status == SCRIPT_STATUS.INSTALLING: self.status = SCRIPT_STATUS.FAILED_INSTALLING elif self.status == SCRIPT_STATUS.APPLYING_NETCONF: self.status = SCRIPT_STATUS.FAILED_APPLYING_NETCONF else: self.status = SCRIPT_STATUS.FAILED if output is not None: self.output = Bin(output) if stdout is not None: self.stdout = Bin(stdout) if stderr is not None: self.stderr = Bin(stderr) if result is not None: self.result = Bin(result) try: parsed_yaml = self.read_results() except ValidationError as err: err_msg = ( "%s(%s) sent a script result with invalid YAML: %s" % ( self.script_set.node.fqdn, self.script_set.node.system_id, err.message, )) logger.error(err_msg) Event.objects.create_node_event( system_id=self.script_set.node.system_id, event_type=EVENT_TYPES.SCRIPT_RESULT_ERROR, event_description=err_msg, ) else: status = parsed_yaml.get("status") if status == "passed": self.status = SCRIPT_STATUS.PASSED elif status == "failed": self.status = SCRIPT_STATUS.FAILED elif status == "degraded": self.status = SCRIPT_STATUS.DEGRADED elif status == "timedout": self.status = SCRIPT_STATUS.TIMEDOUT elif status == "skipped": self.status = SCRIPT_STATUS.SKIPPED link_connected = parsed_yaml.get("link_connected") if self.interface and isinstance(link_connected, bool): self.interface.link_connected = link_connected self.interface.save(update_fields=["link_connected"]) if self.script: if script_version_id is not None: for script in self.script.script.previous_versions(): if script.id == script_version_id: self.script_version = script break if self.script_version is None: err_msg = ( "%s(%s) sent a script result for %s(%d) with an " "unknown script version(%d)." % ( self.script_set.node.fqdn, self.script_set.node.system_id, self.script.name, self.script.id, script_version_id, )) logger.error(err_msg) Event.objects.create_node_event( system_id=self.script_set.node.system_id, event_type=EVENT_TYPES.SCRIPT_RESULT_ERROR, event_description=err_msg, ) else: # If no script version was given assume the latest version # was run. self.script_version = self.script.script # If commissioning result check if its a builtin script, if so run its # hook before committing to the database. if (self.script_set.result_type == RESULT_TYPE.COMMISSIONING and self.name in NODE_INFO_SCRIPTS and stdout is not None): post_process_hook = NODE_INFO_SCRIPTS[self.name]["hook"] err = ("%s(%s): commissioning script '%s' failed during " "post-processing." % ( self.script_set.node.fqdn, self.script_set.node.system_id, self.name, )) # Circular imports. from metadataserver.api import try_or_log_event signal_status = try_or_log_event( self.script_set.node, None, err, post_process_hook, node=self.script_set.node, output=self.stdout, exit_status=self.exit_status, ) # If the script failed to process mark the script as failed to # prevent testing from running and help users identify where # the error came from. This can happen when a commissioning # script generated invalid output. if signal_status is not None: self.status = SCRIPT_STATUS.FAILED if (self.status == SCRIPT_STATUS.PASSED and self.script and self.script.script_type == SCRIPT_TYPE.COMMISSIONING and self.script.recommission): self.script_set.scriptresult_set.filter( script_name__in=NODE_INFO_SCRIPTS).update( status=SCRIPT_STATUS.PENDING, started=None, ended=None, updated=now(), ) self.save()