1"""
2Functions for interacting with the job cache
3"""
4
5
6import logging
7
8import salt.minion
9import salt.utils.event
10import salt.utils.jid
11import salt.utils.verify
12
13log = logging.getLogger(__name__)
14
15
16def store_job(opts, load, event=None, mminion=None):
17    """
18    Store job information using the configured master_job_cache
19    """
20    # Generate EndTime
21    endtime = salt.utils.jid.jid_to_time(salt.utils.jid.gen_jid(opts))
22    # If the return data is invalid, just ignore it
23    if any(key not in load for key in ("return", "jid", "id")):
24        return False
25    if not salt.utils.verify.valid_id(opts, load["id"]):
26        return False
27    if mminion is None:
28        mminion = salt.minion.MasterMinion(opts, states=False, rend=False)
29
30    job_cache = opts["master_job_cache"]
31    if load["jid"] == "req":
32        # The minion is returning a standalone job, request a jobid
33        load["arg"] = load.get("arg", load.get("fun_args", []))
34        load["tgt_type"] = "glob"
35        load["tgt"] = load["id"]
36
37        prep_fstr = "{}.prep_jid".format(opts["master_job_cache"])
38        try:
39            load["jid"] = mminion.returners[prep_fstr](
40                nocache=load.get("nocache", False)
41            )
42        except KeyError:
43            emsg = "Returner '{}' does not support function prep_jid".format(job_cache)
44            log.error(emsg)
45            raise KeyError(emsg)
46        except Exception:  # pylint: disable=broad-except
47            log.critical(
48                "The specified '%s' returner threw a stack trace:\n",
49                job_cache,
50                exc_info=True,
51            )
52
53        # save the load, since we don't have it
54        saveload_fstr = "{}.save_load".format(job_cache)
55        try:
56            mminion.returners[saveload_fstr](load["jid"], load)
57        except KeyError:
58            emsg = "Returner '{}' does not support function save_load".format(job_cache)
59            log.error(emsg)
60            raise KeyError(emsg)
61        except Exception:  # pylint: disable=broad-except
62            log.critical(
63                "The specified '%s' returner threw a stack trace",
64                job_cache,
65                exc_info=True,
66            )
67    elif salt.utils.jid.is_jid(load["jid"]):
68        # Store the jid
69        jidstore_fstr = "{}.prep_jid".format(job_cache)
70        try:
71            mminion.returners[jidstore_fstr](False, passed_jid=load["jid"])
72        except KeyError:
73            emsg = "Returner '{}' does not support function prep_jid".format(job_cache)
74            log.error(emsg)
75            raise KeyError(emsg)
76        except Exception:  # pylint: disable=broad-except
77            log.critical(
78                "The specified '%s' returner threw a stack trace",
79                job_cache,
80                exc_info=True,
81            )
82
83    if event:
84        # If the return data is invalid, just ignore it
85        log.info("Got return from %s for job %s", load["id"], load["jid"])
86        event.fire_event(
87            load, salt.utils.event.tagify([load["jid"], "ret", load["id"]], "job")
88        )
89        event.fire_ret_load(load)
90
91    # if you have a job_cache, or an ext_job_cache, don't write to
92    # the regular master cache
93    if not opts["job_cache"] or opts.get("ext_job_cache"):
94        return
95
96    # do not cache job results if explicitly requested
97    if load.get("jid") == "nocache":
98        log.debug(
99            "Ignoring job return with jid for caching %s from %s",
100            load["jid"],
101            load["id"],
102        )
103        return
104
105    # otherwise, write to the master cache
106    savefstr = "{}.save_load".format(job_cache)
107    getfstr = "{}.get_load".format(job_cache)
108    fstr = "{}.returner".format(job_cache)
109    updateetfstr = "{}.update_endtime".format(job_cache)
110    if "fun" not in load and load.get("return", {}):
111        ret_ = load.get("return", {})
112        if "fun" in ret_:
113            load.update({"fun": ret_["fun"]})
114        if "user" in ret_:
115            load.update({"user": ret_["user"]})
116
117    # Try to reach returner methods
118    try:
119        savefstr_func = mminion.returners[savefstr]
120        getfstr_func = mminion.returners[getfstr]
121        fstr_func = mminion.returners[fstr]
122    except KeyError as error:
123        emsg = "Returner '{}' does not support function {}".format(job_cache, error)
124        log.error(emsg)
125        raise KeyError(emsg)
126
127    if job_cache != "local_cache":
128        try:
129            mminion.returners[savefstr](load["jid"], load)
130        except KeyError as e:
131            log.error("Load does not contain 'jid': %s", e)
132        except Exception:  # pylint: disable=broad-except
133            log.critical(
134                "The specified '%s' returner threw a stack trace",
135                job_cache,
136                exc_info=True,
137            )
138
139    try:
140        mminion.returners[fstr](load)
141    except Exception:  # pylint: disable=broad-except
142        log.critical(
143            "The specified '%s' returner threw a stack trace", job_cache, exc_info=True
144        )
145
146    if opts.get("job_cache_store_endtime") and updateetfstr in mminion.returners:
147        mminion.returners[updateetfstr](load["jid"], endtime)
148
149
150def store_minions(opts, jid, minions, mminion=None, syndic_id=None):
151    """
152    Store additional minions matched on lower-level masters using the configured
153    master_job_cache
154    """
155    if mminion is None:
156        mminion = salt.minion.MasterMinion(opts, states=False, rend=False)
157    job_cache = opts["master_job_cache"]
158    minions_fstr = "{}.save_minions".format(job_cache)
159
160    try:
161        mminion.returners[minions_fstr](jid, minions, syndic_id=syndic_id)
162    except KeyError:
163        raise KeyError(
164            "Returner '{}' does not support function save_minions".format(job_cache)
165        )
166
167
168def get_retcode(ret):
169    """
170    Determine a retcode for a given return
171    """
172    retcode = 0
173    # if there is a dict with retcode, use that
174    if isinstance(ret, dict) and ret.get("retcode", 0) != 0:
175        return ret["retcode"]
176    # if its a boolean, False means 1
177    elif isinstance(ret, bool) and not ret:
178        return 1
179    return retcode
180
181
182# vim:set et sts=4 ts=4 tw=80:
183