summaryrefslogtreecommitdiffstats
path: root/src/rgw/rgw_rest_realm.cc
blob: 704988af8c2aba4cc733c27d998c45bb4682ae82 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
// vim: ts=8 sw=2 smarttab ft=cpp

#include "common/errno.h"
#include "rgw_rest_realm.h"
#include "rgw_rest_s3.h"
#include "rgw_rest_config.h"
#include "rgw_zone.h"
#include "rgw_sal_rados.h"

#include "services/svc_zone.h"
#include "services/svc_mdlog.h"

#include "include/ceph_assert.h"

#define dout_subsys ceph_subsys_rgw

// reject 'period push' if we would have to fetch too many intermediate periods
static const uint32_t PERIOD_HISTORY_FETCH_MAX = 64;

// base period op, shared between Get and Post
class RGWOp_Period_Base : public RGWRESTOp {
 protected:
  RGWPeriod period;
  std::ostringstream error_stream;
 public:
  int verify_permission(optional_yield) override { return 0; }
  void send_response() override;
};

// reply with the period object on success
void RGWOp_Period_Base::send_response()
{
  set_req_state_err(s, op_ret, error_stream.str());
  dump_errno(s);

  if (op_ret < 0) {
    if (!s->err.message.empty()) {
      ldpp_dout(this, 4) << "Request failed with " << op_ret
          << ": " << s->err.message << dendl;
    }
    end_header(s);
    return;
  }

  encode_json("period", period, s->formatter);
  end_header(s, NULL, "application/json", s->formatter->get_len());
  flusher.flush();
}

// GET /admin/realm/period
class RGWOp_Period_Get : public RGWOp_Period_Base {
 public:
  void execute(optional_yield y) override;
  int check_caps(const RGWUserCaps& caps) override {
    return caps.check_cap("zone", RGW_CAP_READ);
  }
  int verify_permission(optional_yield) override {
    return check_caps(s->user->get_caps());
  }
  const char* name() const override { return "get_period"; }
};

void RGWOp_Period_Get::execute(optional_yield y)
{
  string realm_id, realm_name, period_id;
  epoch_t epoch = 0;
  RESTArgs::get_string(s, "realm_id", realm_id, &realm_id);
  RESTArgs::get_string(s, "realm_name", realm_name, &realm_name);
  RESTArgs::get_string(s, "period_id", period_id, &period_id);
  RESTArgs::get_uint32(s, "epoch", 0, &epoch);

  period.set_id(period_id);
  period.set_epoch(epoch);

  op_ret = period.init(this, store->ctx(), store->svc()->sysobj, realm_id, y, realm_name);
  if (op_ret < 0)
    ldpp_dout(this, 5) << "failed to read period" << dendl;
}

// POST /admin/realm/period
class RGWOp_Period_Post : public RGWOp_Period_Base {
 public:
  void execute(optional_yield y) override;
  int check_caps(const RGWUserCaps& caps) override {
    return caps.check_cap("zone", RGW_CAP_WRITE);
  }
  int verify_permission(optional_yield) override {
    return check_caps(s->user->get_caps());
  }
  const char* name() const override { return "post_period"; }
};

void RGWOp_Period_Post::execute(optional_yield y)
{
  auto cct = store->ctx();

  // initialize the period without reading from rados
  period.init(this, cct, store->svc()->sysobj, y, false);

  // decode the period from input
  const auto max_size = cct->_conf->rgw_max_put_param_size;
  bool empty;
  op_ret = rgw_rest_get_json_input(cct, s, period, max_size, &empty);
  if (op_ret < 0) {
    ldpp_dout(this, -1) << "failed to decode period" << dendl;
    return;
  }

  // require period.realm_id to match our realm
  if (period.get_realm() != store->svc()->zone->get_realm().get_id()) {
    error_stream << "period with realm id " << period.get_realm()
        << " doesn't match current realm " << store->svc()->zone->get_realm().get_id() << std::endl;
    op_ret = -EINVAL;
    return;
  }

  // load the realm and current period from rados; there may be a more recent
  // period that we haven't restarted with yet. we also don't want to modify
  // the objects in use by RGWRados
  RGWRealm realm(period.get_realm());
  op_ret = realm.init(this, cct, store->svc()->sysobj, y);
  if (op_ret < 0) {
    ldpp_dout(this, -1) << "failed to read current realm: "
        << cpp_strerror(-op_ret) << dendl;
    return;
  }

  RGWPeriod current_period;
  op_ret = current_period.init(this, cct, store->svc()->sysobj, realm.get_id(), y);
  if (op_ret < 0) {
    ldpp_dout(this, -1) << "failed to read current period: "
        << cpp_strerror(-op_ret) << dendl;
    return;
  }

  // if period id is empty, handle as 'period commit'
  if (period.get_id().empty()) {
    op_ret = period.commit(this, store, realm, current_period, error_stream, y);
    if (op_ret < 0) {
      ldpp_dout(this, -1) << "master zone failed to commit period" << dendl;
    }
    return;
  }

  // if it's not period commit, nobody is allowed to push to the master zone
  if (period.get_master_zone() == store->svc()->zone->get_zone_params().get_id()) {
    ldpp_dout(this, 10) << "master zone rejecting period id="
        << period.get_id() << " epoch=" << period.get_epoch() << dendl;
    op_ret = -EINVAL; // XXX: error code
    return;
  }

  // write the period to rados
  op_ret = period.store_info(this, false, y);
  if (op_ret < 0) {
    ldpp_dout(this, -1) << "failed to store period " << period.get_id() << dendl;
    return;
  }
  // set as latest epoch
  op_ret = period.update_latest_epoch(this, period.get_epoch(), y);
  if (op_ret == -EEXIST) {
    // already have this epoch (or a more recent one)
    ldpp_dout(this, 4) << "already have epoch >= " << period.get_epoch()
        << " for period " << period.get_id() << dendl;
    op_ret = 0;
    return;
  }
  if (op_ret < 0) {
    ldpp_dout(this, -1) << "failed to set latest epoch" << dendl;
    return;
  }

  auto period_history = store->svc()->mdlog->get_period_history();

  // decide whether we can set_current_period() or set_latest_epoch()
  if (period.get_id() != current_period.get_id()) {
    auto current_epoch = current_period.get_realm_epoch();
    // discard periods in the past
    if (period.get_realm_epoch() < current_epoch) {
      ldpp_dout(this, 10) << "discarding period " << period.get_id()
          << " with realm epoch " << period.get_realm_epoch()
          << " older than current epoch " << current_epoch << dendl;
      // return success to ack that we have this period
      return;
    }
    // discard periods too far in the future
    if (period.get_realm_epoch() > current_epoch + PERIOD_HISTORY_FETCH_MAX) {
      ldpp_dout(this, -1) << "discarding period " << period.get_id()
          << " with realm epoch " << period.get_realm_epoch() << " too far in "
          "the future from current epoch " << current_epoch << dendl;
      op_ret = -ENOENT; // XXX: error code
      return;
    }
    // attach a copy of the period into the period history
    auto cursor = period_history->attach(this, RGWPeriod{period}, y);
    if (!cursor) {
      // we're missing some history between the new period and current_period
      op_ret = cursor.get_error();
      ldpp_dout(this, -1) << "failed to collect the periods between current period "
          << current_period.get_id() << " (realm epoch " << current_epoch
          << ") and the new period " << period.get_id()
          << " (realm epoch " << period.get_realm_epoch()
          << "): " << cpp_strerror(-op_ret) << dendl;
      return;
    }
    if (cursor.has_next()) {
      // don't switch if we have a newer period in our history
      ldpp_dout(this, 4) << "attached period " << period.get_id()
          << " to history, but the history contains newer periods" << dendl;
      return;
    }
    // set as current period
    op_ret = realm.set_current_period(this, period, y);
    if (op_ret < 0) {
      ldpp_dout(this, -1) << "failed to update realm's current period" << dendl;
      return;
    }
    ldpp_dout(this, 4) << "period " << period.get_id()
        << " is newer than current period " << current_period.get_id()
        << ", updating realm's current period and notifying zone" << dendl;
    realm.notify_new_period(this, period, y);
    return;
  }
  // reflect the period into our local objects
  op_ret = period.reflect(this, y);
  if (op_ret < 0) {
    ldpp_dout(this, -1) << "failed to update local objects: "
        << cpp_strerror(-op_ret) << dendl;
    return;
  }
  ldpp_dout(this, 4) << "period epoch " << period.get_epoch()
      << " is newer than current epoch " << current_period.get_epoch()
      << ", updating period's latest epoch and notifying zone" << dendl;
  realm.notify_new_period(this, period, y);
  // update the period history
  period_history->insert(RGWPeriod{period});
}

class RGWHandler_Period : public RGWHandler_Auth_S3 {
 protected:
  using RGWHandler_Auth_S3::RGWHandler_Auth_S3;

  RGWOp *op_get() override { return new RGWOp_Period_Get; }
  RGWOp *op_post() override { return new RGWOp_Period_Post; }
};

class RGWRESTMgr_Period : public RGWRESTMgr {
 public:
  RGWHandler_REST* get_handler(rgw::sal::RGWRadosStore *store,
			       struct req_state*,
                               const rgw::auth::StrategyRegistry& auth_registry,
                               const std::string&) override {
    return new RGWHandler_Period(auth_registry);
  }
};


// GET /admin/realm
class RGWOp_Realm_Get : public RGWRESTOp {
  std::unique_ptr<RGWRealm> realm;
public:
  int check_caps(const RGWUserCaps& caps) override {
    return caps.check_cap("zone", RGW_CAP_READ);
  }
  int verify_permission(optional_yield) override {
    return check_caps(s->user->get_caps());
  }
  void execute(optional_yield y) override;
  void send_response() override;
  const char* name() const override { return "get_realm"; }
};

void RGWOp_Realm_Get::execute(optional_yield y)
{
  string id;
  RESTArgs::get_string(s, "id", id, &id);
  string name;
  RESTArgs::get_string(s, "name", name, &name);

  // read realm
  realm.reset(new RGWRealm(id, name));
  op_ret = realm->init(this, g_ceph_context, store->svc()->sysobj, y);
  if (op_ret < 0)
    ldpp_dout(this, -1) << "failed to read realm id=" << id
        << " name=" << name << dendl;
}

void RGWOp_Realm_Get::send_response()
{
  set_req_state_err(s, op_ret);
  dump_errno(s);

  if (op_ret < 0) {
    end_header(s);
    return;
  }

  encode_json("realm", *realm, s->formatter);
  end_header(s, NULL, "application/json", s->formatter->get_len());
  flusher.flush();
}

// GET /admin/realm?list
class RGWOp_Realm_List : public RGWRESTOp {
  std::string default_id;
  std::list<std::string> realms;
public:
  int check_caps(const RGWUserCaps& caps) override {
    return caps.check_cap("zone", RGW_CAP_READ);
  }
  int verify_permission(optional_yield) override {
    return check_caps(s->user->get_caps());
  }
  void execute(optional_yield y) override;
  void send_response() override;
  const char* name() const override { return "list_realms"; }
};

void RGWOp_Realm_List::execute(optional_yield y)
{
  {
    // read default realm
    RGWRealm realm(store->ctx(), store->svc()->sysobj);
    [[maybe_unused]] int ret = realm.read_default_id(this, default_id, y);
  }
  op_ret = store->svc()->zone->list_realms(this, realms);
  if (op_ret < 0)
    ldpp_dout(this, -1) << "failed to list realms" << dendl;
}

void RGWOp_Realm_List::send_response()
{
  set_req_state_err(s, op_ret);
  dump_errno(s);

  if (op_ret < 0) {
    end_header(s);
    return;
  }

  s->formatter->open_object_section("realms_list");
  encode_json("default_info", default_id, s->formatter);
  encode_json("realms", realms, s->formatter);
  s->formatter->close_section();
  end_header(s, NULL, "application/json", s->formatter->get_len());
  flusher.flush();
}

class RGWHandler_Realm : public RGWHandler_Auth_S3 {
protected:
  using RGWHandler_Auth_S3::RGWHandler_Auth_S3;
  RGWOp *op_get() override {
    if (s->info.args.sub_resource_exists("list"))
      return new RGWOp_Realm_List;
    return new RGWOp_Realm_Get;
  }
};

RGWRESTMgr_Realm::RGWRESTMgr_Realm()
{
  // add the /admin/realm/period resource
  register_resource("period", new RGWRESTMgr_Period);
}

RGWHandler_REST*
RGWRESTMgr_Realm::get_handler(rgw::sal::RGWRadosStore *store,
			      struct req_state*,
                              const rgw::auth::StrategyRegistry& auth_registry,
                              const std::string&)
{
  return new RGWHandler_Realm(auth_registry);
}