/src/pacemaker/lib/common/agents.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2004-2025 the Pacemaker project contributors |
3 | | * |
4 | | * The version control history for this file may have further details. |
5 | | * |
6 | | * This source code is licensed under the GNU Lesser General Public License |
7 | | * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. |
8 | | */ |
9 | | |
10 | | #include <crm_internal.h> |
11 | | |
12 | | #include <stdbool.h> |
13 | | #include <stdio.h> |
14 | | #include <string.h> |
15 | | #include <strings.h> |
16 | | |
17 | | #include <glib.h> // g_str_has_prefix() |
18 | | |
19 | | #include <crm/crm.h> |
20 | | #include <crm/common/util.h> |
21 | | |
22 | | /*! |
23 | | * \brief Get capabilities of a resource agent standard |
24 | | * |
25 | | * \param[in] standard Standard name |
26 | | * |
27 | | * \return Bitmask of enum pcmk_ra_caps values |
28 | | */ |
29 | | uint32_t |
30 | | pcmk_get_ra_caps(const char *standard) |
31 | 0 | { |
32 | | /* @COMPAT This should probably be case-sensitive, but isn't, |
33 | | * for backward compatibility. |
34 | | */ |
35 | 0 | if (standard == NULL) { |
36 | 0 | return pcmk_ra_cap_none; |
37 | |
|
38 | 0 | } else if (!strcasecmp(standard, PCMK_RESOURCE_CLASS_OCF)) { |
39 | 0 | return pcmk_ra_cap_provider | pcmk_ra_cap_params |
40 | 0 | | pcmk_ra_cap_unique | pcmk_ra_cap_promotable |
41 | 0 | | pcmk_ra_cap_cli_exec; |
42 | |
|
43 | 0 | } else if (!strcasecmp(standard, PCMK_RESOURCE_CLASS_STONITH)) { |
44 | | /* @COMPAT Stonith resources can't really be unique clones, but we've |
45 | | * allowed it in the past and have it in some scheduler regression tests |
46 | | * (which were likely never used as real configurations). |
47 | | * |
48 | | * @TODO Remove pcmk_ra_cap_unique at the next major schema version |
49 | | * bump, with a transform to remove PCMK_META_GLOBALLY_UNIQUE from the |
50 | | * config. |
51 | | */ |
52 | 0 | return pcmk_ra_cap_params | pcmk_ra_cap_unique | pcmk_ra_cap_stdin |
53 | 0 | | pcmk_ra_cap_fence_params; |
54 | |
|
55 | 0 | } else if (!strcasecmp(standard, PCMK_RESOURCE_CLASS_LSB)) { |
56 | 0 | return pcmk_ra_cap_status | pcmk_ra_cap_cli_exec; |
57 | |
|
58 | 0 | } else if (!strcasecmp(standard, PCMK_RESOURCE_CLASS_SYSTEMD) |
59 | 0 | || !strcasecmp(standard, PCMK_RESOURCE_CLASS_SERVICE)) { |
60 | 0 | return pcmk_ra_cap_status; |
61 | 0 | } |
62 | 0 | return pcmk_ra_cap_none; |
63 | 0 | } |
64 | | |
65 | | int |
66 | | pcmk__effective_rc(int rc) |
67 | 0 | { |
68 | 0 | int remapped_rc = rc; |
69 | |
|
70 | 0 | switch (rc) { |
71 | 0 | case PCMK_OCF_DEGRADED: |
72 | 0 | remapped_rc = PCMK_OCF_OK; |
73 | 0 | break; |
74 | | |
75 | 0 | case PCMK_OCF_DEGRADED_PROMOTED: |
76 | 0 | remapped_rc = PCMK_OCF_RUNNING_PROMOTED; |
77 | 0 | break; |
78 | | |
79 | 0 | default: |
80 | 0 | break; |
81 | 0 | } |
82 | | |
83 | 0 | return remapped_rc; |
84 | 0 | } |
85 | | |
86 | | char * |
87 | | crm_generate_ra_key(const char *standard, const char *provider, |
88 | | const char *type) |
89 | 0 | { |
90 | 0 | bool std_empty = pcmk__str_empty(standard); |
91 | 0 | bool prov_empty = pcmk__str_empty(provider); |
92 | 0 | bool ty_empty = pcmk__str_empty(type); |
93 | |
|
94 | 0 | if (std_empty || ty_empty) { |
95 | 0 | return NULL; |
96 | 0 | } |
97 | | |
98 | 0 | return pcmk__assert_asprintf("%s%s%s:%s", |
99 | 0 | standard, |
100 | 0 | (prov_empty ? "" : ":"), |
101 | 0 | (prov_empty ? "" : provider), |
102 | 0 | type); |
103 | 0 | } |
104 | | |
105 | | /*! |
106 | | * \brief Parse a "standard[:provider]:type" agent specification |
107 | | * |
108 | | * \param[in] spec Agent specification |
109 | | * \param[out] standard Where to store agent standard (may not be \c NULL) |
110 | | * \param[out] provider Where to store agent provider if the standard supports |
111 | | * one (may not be \c NULL) |
112 | | * \param[put] type Where to store agent type (may not be \c NULL) |
113 | | * |
114 | | * \return \c pcmk_ok if the string could be parsed, \c -EINVAL otherwise |
115 | | * |
116 | | * \note It is acceptable for the type to contain a ':' if the standard supports |
117 | | * that. For example, systemd supports the form "systemd:UNIT@A:B". |
118 | | * \note On success, the caller is responsible for freeing \p *standard, |
119 | | * \p *provider, and \p *type using \c free(). On failure, all of these |
120 | | * are left unchanged. |
121 | | */ |
122 | | int |
123 | | crm_parse_agent_spec(const char *spec, char **standard, char **provider, |
124 | | char **type) |
125 | 0 | { |
126 | 0 | gchar **parts = NULL; |
127 | 0 | int rc = pcmk_ok; |
128 | |
|
129 | 0 | CRM_CHECK((spec != NULL) && (standard != NULL) && (provider != NULL) |
130 | 0 | && (type != NULL), return -EINVAL); |
131 | | |
132 | 0 | parts = g_strsplit(spec, ":", 3); |
133 | |
|
134 | 0 | if (pcmk__str_empty(parts[0])) { |
135 | | // Empty standard |
136 | 0 | rc = -EINVAL; |
137 | 0 | goto done; |
138 | 0 | } |
139 | | |
140 | 0 | if (pcmk__is_set(pcmk_get_ra_caps(parts[0]), pcmk_ra_cap_provider)) { |
141 | 0 | if (pcmk__str_empty(parts[1]) || pcmk__str_empty(parts[2])) { |
142 | | // Empty provider or type |
143 | 0 | rc = -EINVAL; |
144 | 0 | goto done; |
145 | 0 | } |
146 | | |
147 | 0 | *standard = pcmk__str_copy(parts[0]); |
148 | 0 | *provider = pcmk__str_copy(parts[1]); |
149 | 0 | *type = pcmk__str_copy(parts[2]); |
150 | |
|
151 | 0 | } else { |
152 | 0 | if (pcmk__str_empty(parts[1])) { |
153 | | // Empty type |
154 | 0 | rc = -EINVAL; |
155 | 0 | goto done; |
156 | 0 | } |
157 | | |
158 | 0 | *standard = pcmk__str_copy(parts[0]); |
159 | |
|
160 | 0 | if (parts[2] == NULL) { |
161 | | // Common case: type does not contain a colon |
162 | 0 | *type = pcmk__str_copy(parts[1]); |
163 | |
|
164 | 0 | } else { |
165 | | // Accommodate "systemd:UNIT@A:B", for example |
166 | 0 | gchar *joined = g_strjoinv(":", parts + 1); |
167 | |
|
168 | 0 | *type = pcmk__str_copy(joined); |
169 | 0 | g_free(joined); |
170 | 0 | } |
171 | 0 | } |
172 | | |
173 | 0 | done: |
174 | 0 | g_strfreev(parts); |
175 | 0 | return rc; |
176 | 0 | } |
177 | | |
178 | | /*! |
179 | | * \brief Check whether a given stonith parameter is handled by Pacemaker |
180 | | * |
181 | | * Return true if a given string is the name of one of the special resource |
182 | | * instance attributes interpreted directly by Pacemaker for stonith-class |
183 | | * resources. |
184 | | * |
185 | | * \param[in] param Parameter name to check |
186 | | * |
187 | | * \return true if \p param is a special fencing parameter |
188 | | */ |
189 | | bool |
190 | | pcmk_stonith_param(const char *param) |
191 | 0 | { |
192 | 0 | if (param == NULL) { |
193 | 0 | return false; |
194 | 0 | } |
195 | | |
196 | | /* @COMPAT Pacemaker does not handle PCMK__FENCING_STONITH_TIMEOUT specially |
197 | | * as a resource parameter, so pcmk_stonith_param() should not return true |
198 | | * for it. It is unclear from the commit history why we returned true for it |
199 | | * in the first place. |
200 | | * |
201 | | * However, when the feature set is less than 3.16.0, |
202 | | * calculate_secure_digest() filters out these special fencing parameters |
203 | | * when calculating the digest. There's no good reason why a user should |
204 | | * have configured this as a fence resource parameter in the first place. |
205 | | * |
206 | | * But out of an abundance of caution, we should wait to drop |
207 | | * PCMK__FENCING_STONITH_TIMEOUT from this function until we no longer |
208 | | * support rolling upgrades from below Pacemaker 2.1.5. |
209 | | */ |
210 | 0 | if (pcmk__str_any_of(param, PCMK_FENCING_PROVIDES, |
211 | 0 | PCMK__FENCING_STONITH_TIMEOUT, NULL)) { |
212 | 0 | return true; |
213 | 0 | } |
214 | | |
215 | 0 | if (!g_str_has_prefix(param, "pcmk_")) { // Short-circuit common case |
216 | 0 | return false; |
217 | 0 | } |
218 | 0 | if (pcmk__str_any_of(param, |
219 | 0 | PCMK_FENCING_ACTION_LIMIT, |
220 | 0 | PCMK_FENCING_DELAY_BASE, |
221 | 0 | PCMK_FENCING_DELAY_MAX, |
222 | 0 | PCMK_FENCING_HOST_ARGUMENT, |
223 | 0 | PCMK_FENCING_HOST_CHECK, |
224 | 0 | PCMK_FENCING_HOST_LIST, |
225 | 0 | PCMK_FENCING_HOST_MAP, |
226 | 0 | NULL)) { |
227 | 0 | return true; |
228 | 0 | } |
229 | 0 | param = strchr(param + 5, '_'); // Skip past "pcmk_ACTION" |
230 | | return pcmk__str_any_of(param, "_action", "_timeout", "_retries", NULL); |
231 | 0 | } |