]>
Commit | Line | Data |
---|---|---|
b4e8d170 | 1 | /* Copyright (c) 2009, 2010, 2011, 2012 Nicira, Inc. |
f85f8ebb BP |
2 | * |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | * you may not use this file except in compliance with the License. | |
5 | * You may obtain a copy of the License at: | |
6 | * | |
7 | * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | * | |
9 | * Unless required by applicable law or agreed to in writing, software | |
10 | * distributed under the License is distributed on an "AS IS" BASIS, | |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | * See the License for the specific language governing permissions and | |
13 | * limitations under the License. | |
14 | */ | |
15 | ||
16 | #include <config.h> | |
17 | ||
18 | #include "trigger.h" | |
19 | ||
f85f8ebb | 20 | #include <limits.h> |
1b1d2e6d | 21 | #include <string.h> |
f85f8ebb | 22 | |
53178986 | 23 | #include "file.h" |
ee89ea7b | 24 | #include "openvswitch/json.h" |
f85f8ebb BP |
25 | #include "jsonrpc.h" |
26 | #include "ovsdb.h" | |
53178986 | 27 | #include "ovsdb-error.h" |
fd016ae3 | 28 | #include "openvswitch/poll-loop.h" |
e317253b | 29 | #include "server.h" |
1b1d2e6d BP |
30 | #include "transaction.h" |
31 | #include "openvswitch/vlog.h" | |
fd016ae3 | 32 | #include "util.h" |
f85f8ebb | 33 | |
1b1d2e6d | 34 | VLOG_DEFINE_THIS_MODULE(trigger); |
53178986 | 35 | |
e317253b | 36 | static bool ovsdb_trigger_try(struct ovsdb_trigger *, long long int now); |
1b1d2e6d BP |
37 | static void ovsdb_trigger_complete(struct ovsdb_trigger *); |
38 | static void trigger_convert_error(struct ovsdb_trigger *, | |
39 | struct ovsdb_error *); | |
53178986 | 40 | static void trigger_success(struct ovsdb_trigger *, struct json *result); |
f85f8ebb | 41 | |
53178986 | 42 | bool |
b4e8d170 | 43 | ovsdb_trigger_init(struct ovsdb_session *session, struct ovsdb *db, |
e317253b | 44 | struct ovsdb_trigger *trigger, |
53178986 BP |
45 | struct jsonrpc_msg *request, long long int now, |
46 | bool read_only, const char *role, const char *id) | |
f85f8ebb | 47 | { |
53178986 BP |
48 | ovs_assert(!strcmp(request->method, "transact") || |
49 | !strcmp(request->method, "convert")); | |
e317253b | 50 | trigger->session = session; |
b4e8d170 | 51 | trigger->db = db; |
417e7e66 | 52 | ovs_list_push_back(&trigger->db->triggers, &trigger->node); |
f85f8ebb | 53 | trigger->request = request; |
53178986 | 54 | trigger->reply = NULL; |
1b1d2e6d | 55 | trigger->progress = NULL; |
f85f8ebb BP |
56 | trigger->created = now; |
57 | trigger->timeout_msec = LLONG_MAX; | |
e51879e9 | 58 | trigger->read_only = read_only; |
d6db7b3c LR |
59 | trigger->role = nullable_xstrdup(role); |
60 | trigger->id = nullable_xstrdup(id); | |
53178986 | 61 | return ovsdb_trigger_try(trigger, now); |
f85f8ebb BP |
62 | } |
63 | ||
64 | void | |
65 | ovsdb_trigger_destroy(struct ovsdb_trigger *trigger) | |
66 | { | |
1b1d2e6d | 67 | ovsdb_txn_progress_destroy(trigger->progress); |
417e7e66 | 68 | ovs_list_remove(&trigger->node); |
53178986 BP |
69 | jsonrpc_msg_destroy(trigger->request); |
70 | jsonrpc_msg_destroy(trigger->reply); | |
d6db7b3c LR |
71 | free(trigger->role); |
72 | free(trigger->id); | |
f85f8ebb BP |
73 | } |
74 | ||
75 | bool | |
76 | ovsdb_trigger_is_complete(const struct ovsdb_trigger *trigger) | |
77 | { | |
1b1d2e6d | 78 | return trigger->reply && !trigger->progress; |
f85f8ebb BP |
79 | } |
80 | ||
53178986 BP |
81 | struct jsonrpc_msg * |
82 | ovsdb_trigger_steal_reply(struct ovsdb_trigger *trigger) | |
f85f8ebb | 83 | { |
53178986 BP |
84 | struct jsonrpc_msg *reply = trigger->reply; |
85 | trigger->reply = NULL; | |
86 | return reply; | |
f85f8ebb BP |
87 | } |
88 | ||
1b1d2e6d BP |
89 | /* Cancels 'trigger'. 'reason' should be a human-readable reason for log |
90 | * messages etc. */ | |
f85f8ebb | 91 | void |
1b1d2e6d | 92 | ovsdb_trigger_cancel(struct ovsdb_trigger *trigger, const char *reason) |
53178986 | 93 | { |
1b1d2e6d BP |
94 | if (trigger->progress) { |
95 | /* The transaction still might complete asynchronously, but we can stop | |
96 | * tracking it. */ | |
97 | ovsdb_txn_progress_destroy(trigger->progress); | |
98 | trigger->progress = NULL; | |
99 | } | |
100 | ||
101 | jsonrpc_msg_destroy(trigger->reply); | |
102 | trigger->reply = NULL; | |
103 | ||
53178986 | 104 | if (!strcmp(trigger->request->method, "transact")) { |
1b1d2e6d BP |
105 | /* There's no place to stick 'reason' into the error reply because RFC |
106 | * 7047 prescribes a fix form for these messages, see section 4.1.4. */ | |
107 | trigger->reply = jsonrpc_create_error(json_string_create("canceled"), | |
108 | trigger->request->id); | |
109 | ovsdb_trigger_complete(trigger); | |
53178986 | 110 | } else if (!strcmp(trigger->request->method, "convert")) { |
1b1d2e6d BP |
111 | trigger_convert_error( |
112 | trigger, | |
113 | ovsdb_error("canceled", "database conversion canceled because %s", | |
114 | reason)); | |
115 | } | |
116 | } | |
117 | ||
118 | void | |
119 | ovsdb_trigger_prereplace_db(struct ovsdb_trigger *trigger) | |
120 | { | |
121 | if (!ovsdb_trigger_is_complete(trigger)) { | |
122 | if (!strcmp(trigger->request->method, "transact")) { | |
123 | ovsdb_trigger_cancel(trigger, "database schema is changing"); | |
124 | } else if (!strcmp(trigger->request->method, "convert")) { | |
125 | /* We don't cancel "convert" requests when a database is being | |
126 | * replaced for two reasons. First, we expect the administrator to | |
127 | * do some kind of sensible synchronization on conversion requests, | |
128 | * that is, it only really makes sense for the admin to do a single | |
129 | * conversion at a time at a scheduled point. Second, if we did | |
130 | * then every "convert" request would end up getting canceled since | |
131 | * "convert" itself causes the database to be replaced. */ | |
132 | } else { | |
133 | OVS_NOT_REACHED(); | |
134 | } | |
53178986 BP |
135 | } |
136 | } | |
137 | ||
138 | bool | |
f85f8ebb BP |
139 | ovsdb_trigger_run(struct ovsdb *db, long long int now) |
140 | { | |
141 | struct ovsdb_trigger *t, *next; | |
f85f8ebb | 142 | |
53178986 | 143 | bool run_triggers = db->run_triggers; |
f85f8ebb | 144 | db->run_triggers = false; |
53178986 BP |
145 | |
146 | bool disconnect_all = false; | |
147 | ||
4e8e4213 | 148 | LIST_FOR_EACH_SAFE (t, next, node, &db->triggers) { |
1b1d2e6d BP |
149 | if (run_triggers |
150 | || now - t->created >= t->timeout_msec | |
151 | || t->progress) { | |
53178986 BP |
152 | if (ovsdb_trigger_try(t, now)) { |
153 | disconnect_all = true; | |
154 | } | |
f85f8ebb BP |
155 | } |
156 | } | |
53178986 | 157 | return disconnect_all; |
f85f8ebb BP |
158 | } |
159 | ||
160 | void | |
161 | ovsdb_trigger_wait(struct ovsdb *db, long long int now) | |
162 | { | |
163 | if (db->run_triggers) { | |
164 | poll_immediate_wake(); | |
165 | } else { | |
166 | long long int deadline = LLONG_MAX; | |
167 | struct ovsdb_trigger *t; | |
168 | ||
4e8e4213 | 169 | LIST_FOR_EACH (t, node, &db->triggers) { |
f85f8ebb BP |
170 | if (t->created < LLONG_MAX - t->timeout_msec) { |
171 | long long int t_deadline = t->created + t->timeout_msec; | |
172 | if (deadline > t_deadline) { | |
173 | deadline = t_deadline; | |
174 | if (now >= deadline) { | |
175 | break; | |
176 | } | |
177 | } | |
178 | } | |
179 | } | |
180 | ||
181 | if (deadline < LLONG_MAX) { | |
7cf8b266 | 182 | poll_timer_wait_until(deadline); |
f85f8ebb BP |
183 | } |
184 | } | |
185 | } | |
186 | ||
187 | static bool | |
e317253b | 188 | ovsdb_trigger_try(struct ovsdb_trigger *t, long long int now) |
f85f8ebb | 189 | { |
1b1d2e6d BP |
190 | /* Handle "initialized" state. */ |
191 | if (!t->reply) { | |
192 | ovs_assert(!t->progress); | |
193 | ||
194 | struct ovsdb_txn *txn = NULL; | |
195 | struct ovsdb *newdb = NULL; | |
196 | if (!strcmp(t->request->method, "transact")) { | |
197 | bool durable; | |
198 | ||
199 | struct json *result; | |
200 | txn = ovsdb_execute_compose( | |
201 | t->db, t->session, t->request->params, t->read_only, | |
202 | t->role, t->id, now - t->created, &t->timeout_msec, | |
203 | &durable, &result); | |
204 | if (!txn) { | |
205 | if (result) { | |
206 | /* Complete. There was an error but we still represent it | |
207 | * in JSON-RPC as a successful result. */ | |
208 | trigger_success(t, result); | |
209 | } else { | |
210 | /* Unsatisfied "wait" condition. Take no action now, retry | |
211 | * later. */ | |
212 | } | |
213 | return false; | |
214 | } | |
215 | ||
216 | /* Transition to "committing" state. */ | |
217 | t->reply = jsonrpc_create_reply(result, t->request->id); | |
218 | t->progress = ovsdb_txn_propose_commit(txn, durable); | |
219 | } else if (!strcmp(t->request->method, "convert")) { | |
220 | /* Permission check. */ | |
221 | if (t->role && *t->role) { | |
222 | trigger_convert_error( | |
223 | t, ovsdb_perm_error( | |
224 | "RBAC rules for client \"%s\" role \"%s\" prohibit " | |
225 | "\"convert\" of database %s " | |
226 | "(only the root role may convert databases)", | |
227 | t->id, t->role, t->db->schema->name)); | |
228 | return false; | |
229 | } | |
230 | ||
231 | /* Validate parameters. */ | |
232 | const struct json *params = t->request->params; | |
233 | if (params->type != JSON_ARRAY || params->u.array.n != 2) { | |
234 | trigger_convert_error(t, ovsdb_syntax_error(params, NULL, | |
235 | "array expected")); | |
236 | return false; | |
237 | } | |
238 | ||
239 | /* Parse new schema and make a converted copy. */ | |
240 | const struct json *new_schema_json = params->u.array.elems[1]; | |
241 | struct ovsdb_schema *new_schema; | |
242 | struct ovsdb_error *error | |
243 | = ovsdb_schema_from_json(new_schema_json, &new_schema); | |
244 | if (!error && strcmp(new_schema->name, t->db->schema->name)) { | |
245 | error = ovsdb_error("invalid parameters", | |
246 | "new schema name (%s) does not match " | |
247 | "database name (%s)", | |
248 | new_schema->name, t->db->schema->name); | |
249 | } | |
250 | if (!error) { | |
251 | error = ovsdb_convert(t->db, new_schema, &newdb); | |
252 | } | |
253 | if (error) { | |
254 | ovsdb_schema_destroy(new_schema); | |
255 | trigger_convert_error(t, error); | |
256 | return false; | |
257 | } | |
258 | ||
259 | /* Make the new copy into a transaction log record. */ | |
260 | struct json *txn_json = ovsdb_to_txn_json( | |
261 | newdb, "converted by ovsdb-server"); | |
262 | ||
263 | /* Propose the change. */ | |
264 | t->progress = ovsdb_txn_propose_schema_change( | |
265 | t->db, new_schema_json, txn_json); | |
266 | json_destroy(txn_json); | |
267 | t->reply = jsonrpc_create_reply(json_object_create(), | |
268 | t->request->id); | |
269 | } else { | |
270 | OVS_NOT_REACHED(); | |
53178986 BP |
271 | } |
272 | ||
1b1d2e6d BP |
273 | /* If the transaction committed synchronously, complete it and |
274 | * transition to "complete". This is more than an optimization because | |
275 | * the file-based storage isn't implemented to read back the | |
276 | * transactions that we write (which is an ugly broken abstraction but | |
277 | * it's what we have). */ | |
278 | if (ovsdb_txn_progress_is_complete(t->progress) | |
279 | && !ovsdb_txn_progress_get_error(t->progress)) { | |
280 | if (txn) { | |
281 | ovsdb_txn_complete(txn); | |
282 | } | |
283 | ovsdb_txn_progress_destroy(t->progress); | |
284 | t->progress = NULL; | |
285 | ovsdb_trigger_complete(t); | |
286 | if (newdb) { | |
287 | ovsdb_replace(t->db, newdb); | |
288 | return true; | |
289 | } | |
53178986 BP |
290 | return false; |
291 | } | |
1b1d2e6d | 292 | ovsdb_destroy(newdb); |
53178986 | 293 | |
1b1d2e6d BP |
294 | /* Fall through to the general handling for the "committing" state. We |
295 | * abort the transaction--if and when it eventually commits, we'll read | |
296 | * it back from storage and replay it locally. */ | |
297 | if (txn) { | |
298 | ovsdb_txn_abort(txn); | |
53178986 | 299 | } |
1b1d2e6d BP |
300 | } |
301 | ||
302 | /* Handle "committing" state. */ | |
303 | if (t->progress) { | |
304 | if (!ovsdb_txn_progress_is_complete(t->progress)) { | |
305 | return false; | |
53178986 | 306 | } |
1b1d2e6d BP |
307 | |
308 | /* Transition to "complete". */ | |
309 | struct ovsdb_error *error | |
310 | = ovsdb_error_clone(ovsdb_txn_progress_get_error(t->progress)); | |
311 | ovsdb_txn_progress_destroy(t->progress); | |
312 | t->progress = NULL; | |
313 | ||
53178986 | 314 | if (error) { |
1b1d2e6d BP |
315 | if (!strcmp(ovsdb_error_get_tag(error), "cluster error")) { |
316 | /* Temporary error. Transition back to "initialized" state to | |
317 | * try again. */ | |
318 | jsonrpc_msg_destroy(t->reply); | |
319 | t->reply = NULL; | |
320 | t->db->run_triggers = true; | |
321 | ovsdb_error_destroy(error); | |
322 | } else { | |
323 | /* Permanent error. Transition to "completed" state to report | |
324 | * it. */ | |
325 | if (!strcmp(t->request->method, "transact")) { | |
326 | json_array_add(t->reply->result, | |
327 | ovsdb_error_to_json_free(error)); | |
328 | ovsdb_trigger_complete(t); | |
329 | } else if (!strcmp(t->request->method, "convert")) { | |
330 | jsonrpc_msg_destroy(t->reply); | |
331 | t->reply = NULL; | |
332 | trigger_convert_error(t, error); | |
333 | } | |
334 | } | |
335 | } else { | |
336 | /* Success. */ | |
337 | ovsdb_trigger_complete(t); | |
53178986 BP |
338 | } |
339 | ||
1b1d2e6d | 340 | return false; |
f85f8ebb | 341 | } |
1b1d2e6d BP |
342 | |
343 | OVS_NOT_REACHED(); | |
f85f8ebb BP |
344 | } |
345 | ||
346 | static void | |
1b1d2e6d | 347 | ovsdb_trigger_complete(struct ovsdb_trigger *t) |
f85f8ebb | 348 | { |
1b1d2e6d | 349 | ovs_assert(t->reply); |
417e7e66 BW |
350 | ovs_list_remove(&t->node); |
351 | ovs_list_push_back(&t->session->completions, &t->node); | |
f85f8ebb | 352 | } |
53178986 | 353 | |
1b1d2e6d BP |
354 | /* Makes a "convert" request into an error. |
355 | * | |
356 | * This is not suitable for "transact" requests because their replies should | |
357 | * never be bare ovsdb_errors: RFC 7047 says that their replies must either be | |
358 | * a JSON-RPC reply that contains an array of operation replies (which can be | |
359 | * errors), or a JSON-RPC error whose "error" member is simply "canceled". */ | |
53178986 | 360 | static void |
1b1d2e6d | 361 | trigger_convert_error(struct ovsdb_trigger *t, struct ovsdb_error *error) |
53178986 | 362 | { |
1b1d2e6d BP |
363 | ovs_assert(!strcmp(t->request->method, "convert")); |
364 | ovs_assert(error && !t->reply); | |
365 | t->reply = jsonrpc_create_error( | |
53178986 | 366 | ovsdb_error_to_json_free(error), t->request->id); |
1b1d2e6d | 367 | ovsdb_trigger_complete(t); |
53178986 BP |
368 | } |
369 | ||
370 | static void | |
371 | trigger_success(struct ovsdb_trigger *t, struct json *result) | |
372 | { | |
1b1d2e6d BP |
373 | ovs_assert(result && !t->reply); |
374 | t->reply = jsonrpc_create_reply(result, t->request->id); | |
375 | ovsdb_trigger_complete(t); | |
53178986 | 376 | } |