]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- |
2 | // vim: ts=8 sw=2 smarttab | |
3 | ||
4 | #include "common/errno.h" | |
5 | #include "rgw_rest_realm.h" | |
6 | #include "rgw_rest_s3.h" | |
7 | #include "rgw_rest_config.h" | |
8 | ||
9 | #include "include/assert.h" | |
10 | ||
11 | #define dout_subsys ceph_subsys_rgw | |
12 | ||
13 | // reject 'period push' if we would have to fetch too many intermediate periods | |
14 | static const uint32_t PERIOD_HISTORY_FETCH_MAX = 64; | |
15 | ||
16 | // base period op, shared between Get and Post | |
17 | class RGWOp_Period_Base : public RGWRESTOp { | |
18 | protected: | |
19 | RGWPeriod period; | |
20 | std::ostringstream error_stream; | |
21 | public: | |
22 | int verify_permission() override { return 0; } | |
23 | void send_response() override; | |
24 | }; | |
25 | ||
26 | // reply with the period object on success | |
27 | void RGWOp_Period_Base::send_response() | |
28 | { | |
29 | s->err.message = error_stream.str(); | |
30 | ||
31 | set_req_state_err(s, http_ret); | |
32 | dump_errno(s); | |
33 | ||
34 | if (http_ret < 0) { | |
35 | if (!s->err.message.empty()) { | |
36 | ldout(s->cct, 4) << "Request failed with " << http_ret | |
37 | << ": " << s->err.message << dendl; | |
38 | } | |
39 | end_header(s); | |
40 | return; | |
41 | } | |
42 | ||
43 | encode_json("period", period, s->formatter); | |
44 | end_header(s, NULL, "application/json", s->formatter->get_len()); | |
45 | flusher.flush(); | |
46 | } | |
47 | ||
48 | // GET /admin/realm/period | |
49 | class RGWOp_Period_Get : public RGWOp_Period_Base { | |
50 | public: | |
51 | void execute() override; | |
52 | const string name() override { return "get_period"; } | |
53 | }; | |
54 | ||
55 | void RGWOp_Period_Get::execute() | |
56 | { | |
57 | string realm_id, realm_name, period_id; | |
58 | epoch_t epoch = 0; | |
59 | RESTArgs::get_string(s, "realm_id", realm_id, &realm_id); | |
60 | RESTArgs::get_string(s, "realm_name", realm_name, &realm_name); | |
61 | RESTArgs::get_string(s, "period_id", period_id, &period_id); | |
62 | RESTArgs::get_uint32(s, "epoch", 0, &epoch); | |
63 | ||
64 | period.set_id(period_id); | |
65 | period.set_epoch(epoch); | |
66 | ||
67 | http_ret = period.init(store->ctx(), store, realm_id, realm_name); | |
68 | if (http_ret < 0) | |
69 | ldout(store->ctx(), 5) << "failed to read period" << dendl; | |
70 | } | |
71 | ||
72 | // POST /admin/realm/period | |
73 | class RGWOp_Period_Post : public RGWOp_Period_Base { | |
74 | public: | |
75 | void execute() override; | |
76 | const string name() override { return "post_period"; } | |
77 | }; | |
78 | ||
79 | void RGWOp_Period_Post::execute() | |
80 | { | |
81 | auto cct = store->ctx(); | |
82 | ||
83 | // initialize the period without reading from rados | |
84 | period.init(cct, store, false); | |
85 | ||
86 | // decode the period from input | |
87 | const auto max_size = cct->_conf->rgw_max_put_param_size; | |
88 | bool empty; | |
89 | http_ret = rgw_rest_get_json_input(cct, s, period, max_size, &empty); | |
90 | if (http_ret < 0) { | |
91 | lderr(cct) << "failed to decode period" << dendl; | |
92 | return; | |
93 | } | |
94 | ||
95 | // require period.realm_id to match our realm | |
96 | if (period.get_realm() != store->realm.get_id()) { | |
97 | error_stream << "period with realm id " << period.get_realm() | |
98 | << " doesn't match current realm " << store->realm.get_id() << std::endl; | |
99 | http_ret = -EINVAL; | |
100 | return; | |
101 | } | |
102 | ||
103 | // load the realm and current period from rados; there may be a more recent | |
104 | // period that we haven't restarted with yet. we also don't want to modify | |
105 | // the objects in use by RGWRados | |
106 | RGWRealm realm(period.get_realm()); | |
107 | http_ret = realm.init(cct, store); | |
108 | if (http_ret < 0) { | |
109 | lderr(cct) << "failed to read current realm: " | |
110 | << cpp_strerror(-http_ret) << dendl; | |
111 | return; | |
112 | } | |
113 | ||
114 | RGWPeriod current_period; | |
115 | http_ret = current_period.init(cct, store, realm.get_id()); | |
116 | if (http_ret < 0) { | |
117 | lderr(cct) << "failed to read current period: " | |
118 | << cpp_strerror(-http_ret) << dendl; | |
119 | return; | |
120 | } | |
121 | ||
122 | // if period id is empty, handle as 'period commit' | |
123 | if (period.get_id().empty()) { | |
124 | http_ret = period.commit(realm, current_period, error_stream); | |
125 | if (http_ret < 0) { | |
126 | lderr(cct) << "master zone failed to commit period" << dendl; | |
127 | } | |
128 | return; | |
129 | } | |
130 | ||
131 | // if it's not period commit, nobody is allowed to push to the master zone | |
132 | if (period.get_master_zone() == store->get_zone_params().get_id()) { | |
133 | ldout(cct, 10) << "master zone rejecting period id=" | |
134 | << period.get_id() << " epoch=" << period.get_epoch() << dendl; | |
135 | http_ret = -EINVAL; // XXX: error code | |
136 | return; | |
137 | } | |
138 | ||
139 | // write the period to rados | |
140 | http_ret = period.store_info(false); | |
141 | if (http_ret < 0) { | |
142 | lderr(cct) << "failed to store period " << period.get_id() << dendl; | |
143 | return; | |
144 | } | |
145 | ||
146 | // decide whether we can set_current_period() or set_latest_epoch() | |
147 | if (period.get_id() != current_period.get_id()) { | |
148 | auto current_epoch = current_period.get_realm_epoch(); | |
149 | // discard periods in the past | |
150 | if (period.get_realm_epoch() < current_epoch) { | |
151 | ldout(cct, 10) << "discarding period " << period.get_id() | |
152 | << " with realm epoch " << period.get_realm_epoch() | |
153 | << " older than current epoch " << current_epoch << dendl; | |
154 | // return success to ack that we have this period | |
155 | return; | |
156 | } | |
157 | // discard periods too far in the future | |
158 | if (period.get_realm_epoch() > current_epoch + PERIOD_HISTORY_FETCH_MAX) { | |
159 | lderr(cct) << "discarding period " << period.get_id() | |
160 | << " with realm epoch " << period.get_realm_epoch() << " too far in " | |
161 | "the future from current epoch " << current_epoch << dendl; | |
162 | http_ret = -ENOENT; // XXX: error code | |
163 | return; | |
164 | } | |
165 | // attach a copy of the period into the period history | |
166 | auto cursor = store->period_history->attach(RGWPeriod{period}); | |
167 | if (!cursor) { | |
168 | // we're missing some history between the new period and current_period | |
169 | http_ret = cursor.get_error(); | |
170 | lderr(cct) << "failed to collect the periods between current period " | |
171 | << current_period.get_id() << " (realm epoch " << current_epoch | |
172 | << ") and the new period " << period.get_id() | |
173 | << " (realm epoch " << period.get_realm_epoch() | |
174 | << "): " << cpp_strerror(-http_ret) << dendl; | |
175 | return; | |
176 | } | |
177 | if (cursor.has_next()) { | |
178 | // don't switch if we have a newer period in our history | |
179 | ldout(cct, 4) << "attached period " << period.get_id() | |
180 | << " to history, but the history contains newer periods" << dendl; | |
181 | return; | |
182 | } | |
183 | // set as current period | |
184 | http_ret = realm.set_current_period(period); | |
185 | if (http_ret < 0) { | |
186 | lderr(cct) << "failed to update realm's current period" << dendl; | |
187 | return; | |
188 | } | |
189 | ldout(cct, 4) << "period " << period.get_id() | |
190 | << " is newer than current period " << current_period.get_id() | |
191 | << ", updating realm's current period and notifying zone" << dendl; | |
192 | realm.notify_new_period(period); | |
193 | return; | |
194 | } | |
195 | ||
196 | if (period.get_epoch() <= current_period.get_epoch()) { | |
197 | lderr(cct) << "period epoch " << period.get_epoch() << " is not newer " | |
198 | "than current epoch " << current_period.get_epoch() | |
199 | << ", discarding update" << dendl; | |
200 | return; | |
201 | } | |
202 | // set as latest epoch | |
203 | http_ret = period.set_latest_epoch(period.get_epoch()); | |
204 | if (http_ret < 0) { | |
205 | lderr(cct) << "failed to set latest epoch" << dendl; | |
206 | return; | |
207 | } | |
208 | // reflect the period into our local objects | |
209 | http_ret = period.reflect(); | |
210 | if (http_ret < 0) { | |
211 | lderr(cct) << "failed to update local objects: " | |
212 | << cpp_strerror(-http_ret) << dendl; | |
213 | return; | |
214 | } | |
215 | ldout(cct, 4) << "period epoch " << period.get_epoch() | |
216 | << " is newer than current epoch " << current_period.get_epoch() | |
217 | << ", updating period's latest epoch and notifying zone" << dendl; | |
218 | realm.notify_new_period(period); | |
219 | // update the period history | |
220 | store->period_history->insert(RGWPeriod{period}); | |
221 | } | |
222 | ||
223 | class RGWHandler_Period : public RGWHandler_Auth_S3 { | |
224 | protected: | |
225 | using RGWHandler_Auth_S3::RGWHandler_Auth_S3; | |
226 | ||
227 | RGWOp *op_get() override { return new RGWOp_Period_Get; } | |
228 | RGWOp *op_post() override { return new RGWOp_Period_Post; } | |
229 | }; | |
230 | ||
231 | class RGWRESTMgr_Period : public RGWRESTMgr { | |
232 | public: | |
233 | RGWHandler_REST* get_handler(struct req_state*, | |
234 | const rgw::auth::StrategyRegistry& auth_registry, | |
235 | const std::string&) override { | |
236 | return new RGWHandler_Period(auth_registry); | |
237 | } | |
238 | }; | |
239 | ||
240 | ||
241 | // GET /admin/realm | |
242 | class RGWOp_Realm_Get : public RGWRESTOp { | |
243 | std::unique_ptr<RGWRealm> realm; | |
244 | public: | |
245 | int verify_permission() override { return 0; } | |
246 | void execute() override; | |
247 | void send_response() override; | |
248 | const string name() override { return "get_realm"; } | |
249 | }; | |
250 | ||
251 | void RGWOp_Realm_Get::execute() | |
252 | { | |
253 | string id; | |
254 | RESTArgs::get_string(s, "id", id, &id); | |
255 | string name; | |
256 | RESTArgs::get_string(s, "name", name, &name); | |
257 | ||
258 | // read realm | |
259 | realm.reset(new RGWRealm(id, name)); | |
260 | http_ret = realm->init(g_ceph_context, store); | |
261 | if (http_ret < 0) | |
262 | lderr(store->ctx()) << "failed to read realm id=" << id | |
263 | << " name=" << name << dendl; | |
264 | } | |
265 | ||
266 | void RGWOp_Realm_Get::send_response() | |
267 | { | |
268 | set_req_state_err(s, http_ret); | |
269 | dump_errno(s); | |
270 | ||
271 | if (http_ret < 0) { | |
272 | end_header(s); | |
273 | return; | |
274 | } | |
275 | ||
276 | encode_json("realm", *realm, s->formatter); | |
277 | end_header(s, NULL, "application/json", s->formatter->get_len()); | |
278 | flusher.flush(); | |
279 | } | |
280 | ||
281 | class RGWHandler_Realm : public RGWHandler_Auth_S3 { | |
282 | protected: | |
283 | using RGWHandler_Auth_S3::RGWHandler_Auth_S3; | |
284 | RGWOp *op_get() override { return new RGWOp_Realm_Get; } | |
285 | }; | |
286 | ||
287 | RGWRESTMgr_Realm::RGWRESTMgr_Realm() | |
288 | { | |
289 | // add the /admin/realm/period resource | |
290 | register_resource("period", new RGWRESTMgr_Period); | |
291 | } | |
292 | ||
293 | RGWHandler_REST* | |
294 | RGWRESTMgr_Realm::get_handler(struct req_state*, | |
295 | const rgw::auth::StrategyRegistry& auth_registry, | |
296 | const std::string&) | |
297 | { | |
298 | return new RGWHandler_Realm(auth_registry); | |
299 | } |