]>
Commit | Line | Data |
---|---|---|
11fdf7f2 | 1 | // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- |
9f95a23c | 2 | // vim: ts=8 sw=2 smarttab ft=cpp |
11fdf7f2 | 3 | |
31f18b77 FG |
4 | #include "rgw_sync_module_es.h" |
5 | #include "rgw_sync_module_es_rest.h" | |
6 | #include "rgw_es_query.h" | |
7 | #include "rgw_op.h" | |
8 | #include "rgw_rest.h" | |
9 | #include "rgw_rest_s3.h" | |
f67539c2 | 10 | #include "rgw_sal_rados.h" |
31f18b77 FG |
11 | |
12 | #define dout_context g_ceph_context | |
13 | #define dout_subsys ceph_subsys_rgw | |
14 | ||
15 | struct es_index_obj_response { | |
16 | string bucket; | |
17 | rgw_obj_key key; | |
18 | uint64_t versioned_epoch{0}; | |
19 | ACLOwner owner; | |
20 | set<string> read_permissions; | |
21 | ||
22 | struct { | |
23 | uint64_t size{0}; | |
24 | ceph::real_time mtime; | |
25 | string etag; | |
26 | string content_type; | |
a8e16298 | 27 | string storage_class; |
31f18b77 FG |
28 | map<string, string> custom_str; |
29 | map<string, int64_t> custom_int; | |
30 | map<string, string> custom_date; | |
31 | ||
32 | template <class T> | |
33 | struct _custom_entry { | |
34 | string name; | |
35 | T value; | |
36 | void decode_json(JSONObj *obj) { | |
37 | JSONDecoder::decode_json("name", name, obj); | |
38 | JSONDecoder::decode_json("value", value, obj); | |
39 | } | |
40 | }; | |
41 | ||
42 | void decode_json(JSONObj *obj) { | |
43 | JSONDecoder::decode_json("size", size, obj); | |
44 | string mtime_str; | |
45 | JSONDecoder::decode_json("mtime", mtime_str, obj); | |
46 | parse_time(mtime_str.c_str(), &mtime); | |
47 | JSONDecoder::decode_json("etag", etag, obj); | |
48 | JSONDecoder::decode_json("content_type", content_type, obj); | |
a8e16298 | 49 | JSONDecoder::decode_json("storage_class", storage_class, obj); |
31f18b77 FG |
50 | list<_custom_entry<string> > str_entries; |
51 | JSONDecoder::decode_json("custom-string", str_entries, obj); | |
52 | for (auto& e : str_entries) { | |
53 | custom_str[e.name] = e.value; | |
54 | } | |
55 | list<_custom_entry<int64_t> > int_entries; | |
56 | JSONDecoder::decode_json("custom-int", int_entries, obj); | |
57 | for (auto& e : int_entries) { | |
58 | custom_int[e.name] = e.value; | |
59 | } | |
60 | list<_custom_entry<string> > date_entries; | |
61 | JSONDecoder::decode_json("custom-date", date_entries, obj); | |
62 | for (auto& e : date_entries) { | |
63 | custom_date[e.name] = e.value; | |
64 | } | |
65 | } | |
66 | } meta; | |
67 | ||
68 | void decode_json(JSONObj *obj) { | |
69 | JSONDecoder::decode_json("bucket", bucket, obj); | |
70 | JSONDecoder::decode_json("name", key.name, obj); | |
71 | JSONDecoder::decode_json("instance", key.instance, obj); | |
72 | JSONDecoder::decode_json("versioned_epoch", versioned_epoch, obj); | |
73 | JSONDecoder::decode_json("permissions", read_permissions, obj); | |
74 | JSONDecoder::decode_json("owner", owner, obj); | |
75 | JSONDecoder::decode_json("meta", meta, obj); | |
76 | } | |
77 | }; | |
78 | ||
79 | struct es_search_response { | |
80 | uint32_t took; | |
81 | bool timed_out; | |
82 | struct { | |
83 | uint32_t total; | |
84 | uint32_t successful; | |
85 | uint32_t failed; | |
86 | void decode_json(JSONObj *obj) { | |
87 | JSONDecoder::decode_json("total", total, obj); | |
88 | JSONDecoder::decode_json("successful", successful, obj); | |
89 | JSONDecoder::decode_json("failed", failed, obj); | |
90 | } | |
91 | } shards; | |
92 | struct obj_hit { | |
93 | string index; | |
94 | string type; | |
95 | string id; | |
96 | // double score | |
97 | es_index_obj_response source; | |
98 | void decode_json(JSONObj *obj) { | |
99 | JSONDecoder::decode_json("_index", index, obj); | |
100 | JSONDecoder::decode_json("_type", type, obj); | |
101 | JSONDecoder::decode_json("_id", id, obj); | |
102 | JSONDecoder::decode_json("_source", source, obj); | |
103 | } | |
104 | }; | |
105 | struct { | |
106 | uint32_t total; | |
107 | // double max_score; | |
108 | list<obj_hit> hits; | |
109 | void decode_json(JSONObj *obj) { | |
110 | JSONDecoder::decode_json("total", total, obj); | |
111 | // JSONDecoder::decode_json("max_score", max_score, obj); | |
112 | JSONDecoder::decode_json("hits", hits, obj); | |
113 | } | |
114 | } hits; | |
115 | void decode_json(JSONObj *obj) { | |
116 | JSONDecoder::decode_json("took", took, obj); | |
117 | JSONDecoder::decode_json("timed_out", timed_out, obj); | |
118 | JSONDecoder::decode_json("_shards", shards, obj); | |
119 | JSONDecoder::decode_json("hits", hits, obj); | |
120 | } | |
121 | }; | |
122 | ||
123 | class RGWMetadataSearchOp : public RGWOp { | |
124 | RGWSyncModuleInstanceRef sync_module_ref; | |
125 | RGWElasticSyncModuleInstance *es_module; | |
126 | protected: | |
127 | string expression; | |
128 | string custom_prefix; | |
129 | #define MAX_KEYS_DEFAULT 100 | |
130 | uint64_t max_keys{MAX_KEYS_DEFAULT}; | |
131 | string marker_str; | |
132 | uint64_t marker{0}; | |
133 | string next_marker; | |
134 | bool is_truncated{false}; | |
135 | string err; | |
136 | ||
137 | es_search_response response; | |
138 | ||
139 | public: | |
140 | RGWMetadataSearchOp(const RGWSyncModuleInstanceRef& sync_module) : sync_module_ref(sync_module) { | |
141 | es_module = static_cast<RGWElasticSyncModuleInstance *>(sync_module_ref.get()); | |
142 | } | |
143 | ||
f67539c2 | 144 | int verify_permission(optional_yield) override { |
31f18b77 FG |
145 | return 0; |
146 | } | |
147 | virtual int get_params() = 0; | |
11fdf7f2 | 148 | void pre_exec() override; |
f67539c2 | 149 | void execute(optional_yield y) override; |
31f18b77 | 150 | |
11fdf7f2 TL |
151 | const char* name() const override { return "metadata_search"; } |
152 | virtual RGWOpType get_type() override { return RGW_OP_METADATA_SEARCH; } | |
153 | virtual uint32_t op_mask() override { return RGW_OP_TYPE_READ; } | |
31f18b77 FG |
154 | }; |
155 | ||
156 | void RGWMetadataSearchOp::pre_exec() | |
157 | { | |
158 | rgw_bucket_object_pre_exec(s); | |
159 | } | |
160 | ||
f67539c2 | 161 | void RGWMetadataSearchOp::execute(optional_yield y) |
31f18b77 FG |
162 | { |
163 | op_ret = get_params(); | |
164 | if (op_ret < 0) | |
165 | return; | |
166 | ||
167 | list<pair<string, string> > conds; | |
168 | ||
9f95a23c TL |
169 | if (!s->user->get_info().system) { |
170 | conds.push_back(make_pair("permissions", s->user->get_id().to_str())); | |
31f18b77 FG |
171 | } |
172 | ||
173 | if (!s->bucket_name.empty()) { | |
174 | conds.push_back(make_pair("bucket", s->bucket_name)); | |
175 | } | |
176 | ||
177 | ESQueryCompiler es_query(expression, &conds, custom_prefix); | |
178 | ||
179 | static map<string, string, ltstr_nocase> aliases = { | |
180 | { "bucket", "bucket" }, /* forces lowercase */ | |
181 | { "name", "name" }, | |
182 | { "key", "name" }, | |
183 | { "instance", "instance" }, | |
184 | { "etag", "meta.etag" }, | |
185 | { "size", "meta.size" }, | |
186 | { "mtime", "meta.mtime" }, | |
187 | { "lastmodified", "meta.mtime" }, | |
a8e16298 TL |
188 | { "last_modified", "meta.mtime" }, |
189 | { "contenttype", "meta.content_type" }, | |
190 | { "content_type", "meta.content_type" }, | |
191 | { "storageclass", "meta.storage_class" }, | |
192 | { "storage_class", "meta.storage_class" }, | |
31f18b77 FG |
193 | }; |
194 | es_query.set_field_aliases(&aliases); | |
195 | ||
196 | static map<string, ESEntityTypeMap::EntityType> generic_map = { {"bucket", ESEntityTypeMap::ES_ENTITY_STR}, | |
197 | {"name", ESEntityTypeMap::ES_ENTITY_STR}, | |
198 | {"instance", ESEntityTypeMap::ES_ENTITY_STR}, | |
199 | {"permissions", ESEntityTypeMap::ES_ENTITY_STR}, | |
200 | {"meta.etag", ESEntityTypeMap::ES_ENTITY_STR}, | |
a8e16298 | 201 | {"meta.content_type", ESEntityTypeMap::ES_ENTITY_STR}, |
31f18b77 | 202 | {"meta.mtime", ESEntityTypeMap::ES_ENTITY_DATE}, |
a8e16298 TL |
203 | {"meta.size", ESEntityTypeMap::ES_ENTITY_INT}, |
204 | {"meta.storage_class", ESEntityTypeMap::ES_ENTITY_STR} }; | |
31f18b77 FG |
205 | ESEntityTypeMap gm(generic_map); |
206 | es_query.set_generic_type_map(&gm); | |
207 | ||
208 | static set<string> restricted_fields = { {"permissions"} }; | |
209 | es_query.set_restricted_fields(&restricted_fields); | |
210 | ||
211 | map<string, ESEntityTypeMap::EntityType> custom_map; | |
f67539c2 | 212 | for (auto& i : s->bucket->get_info().mdsearch_config) { |
31f18b77 FG |
213 | custom_map[i.first] = (ESEntityTypeMap::EntityType)i.second; |
214 | } | |
215 | ||
216 | ESEntityTypeMap em(custom_map); | |
217 | es_query.set_custom_type_map(&em); | |
218 | ||
219 | bool valid = es_query.compile(&err); | |
220 | if (!valid) { | |
b3b6e05e | 221 | ldpp_dout(this, 10) << "invalid query, failed generating request json" << dendl; |
31f18b77 FG |
222 | op_ret = -EINVAL; |
223 | return; | |
224 | } | |
225 | ||
226 | JSONFormatter f; | |
227 | encode_json("root", es_query, &f); | |
228 | ||
229 | RGWRESTConn *conn = es_module->get_rest_conn(); | |
230 | ||
231 | bufferlist in; | |
232 | bufferlist out; | |
233 | ||
234 | stringstream ss; | |
235 | ||
236 | f.flush(ss); | |
237 | in.append(ss.str()); | |
238 | ||
239 | string resource = es_module->get_index_path() + "/_search"; | |
240 | param_vec_t params; | |
241 | static constexpr int BUFSIZE = 32; | |
242 | char buf[BUFSIZE]; | |
243 | snprintf(buf, sizeof(buf), "%lld", (long long)max_keys); | |
244 | params.push_back(param_pair_t("size", buf)); | |
245 | if (marker > 0) { | |
246 | params.push_back(param_pair_t("from", marker_str.c_str())); | |
247 | } | |
b3b6e05e | 248 | ldpp_dout(this, 20) << "sending request to elasticsearch, payload=" << string(in.c_str(), in.length()) << dendl; |
a8e16298 | 249 | auto& extra_headers = es_module->get_request_headers(); |
b3b6e05e | 250 | op_ret = conn->get_resource(s, resource, ¶ms, &extra_headers, |
f67539c2 | 251 | out, &in, nullptr, y); |
31f18b77 | 252 | if (op_ret < 0) { |
b3b6e05e | 253 | ldpp_dout(this, 0) << "ERROR: failed to fetch resource (r=" << resource << ", ret=" << op_ret << ")" << dendl; |
31f18b77 FG |
254 | return; |
255 | } | |
256 | ||
b3b6e05e | 257 | ldpp_dout(this, 20) << "response: " << string(out.c_str(), out.length()) << dendl; |
31f18b77 FG |
258 | |
259 | JSONParser jparser; | |
260 | if (!jparser.parse(out.c_str(), out.length())) { | |
b3b6e05e | 261 | ldpp_dout(this, 0) << "ERROR: failed to parse elasticsearch response" << dendl; |
31f18b77 FG |
262 | op_ret = -EINVAL; |
263 | return; | |
264 | } | |
265 | ||
266 | try { | |
267 | decode_json_obj(response, &jparser); | |
9f95a23c | 268 | } catch (const JSONDecoder::err& e) { |
b3b6e05e | 269 | ldpp_dout(this, 0) << "ERROR: failed to decode JSON input: " << e.what() << dendl; |
31f18b77 FG |
270 | op_ret = -EINVAL; |
271 | return; | |
272 | } | |
273 | ||
274 | } | |
275 | ||
276 | class RGWMetadataSearch_ObjStore_S3 : public RGWMetadataSearchOp { | |
277 | public: | |
11fdf7f2 | 278 | explicit RGWMetadataSearch_ObjStore_S3(const RGWSyncModuleInstanceRef& _sync_module) : RGWMetadataSearchOp(_sync_module) { |
31f18b77 FG |
279 | custom_prefix = "x-amz-meta-"; |
280 | } | |
281 | ||
282 | int get_params() override { | |
283 | expression = s->info.args.get("query"); | |
284 | bool exists; | |
285 | string max_keys_str = s->info.args.get("max-keys", &exists); | |
286 | #define MAX_KEYS_MAX 10000 | |
287 | if (exists) { | |
288 | string err; | |
289 | max_keys = strict_strtoll(max_keys_str.c_str(), 10, &err); | |
290 | if (!err.empty()) { | |
291 | return -EINVAL; | |
292 | } | |
293 | if (max_keys > MAX_KEYS_MAX) { | |
294 | max_keys = MAX_KEYS_MAX; | |
295 | } | |
296 | } | |
297 | marker_str = s->info.args.get("marker", &exists); | |
298 | if (exists) { | |
299 | string err; | |
300 | marker = strict_strtoll(marker_str.c_str(), 10, &err); | |
301 | if (!err.empty()) { | |
302 | return -EINVAL; | |
303 | } | |
304 | } | |
305 | uint64_t nm = marker + max_keys; | |
306 | static constexpr int BUFSIZE = 32; | |
307 | char buf[BUFSIZE]; | |
308 | snprintf(buf, sizeof(buf), "%lld", (long long)nm); | |
309 | next_marker = buf; | |
310 | return 0; | |
311 | } | |
312 | void send_response() override { | |
313 | if (op_ret) { | |
314 | s->err.message = err; | |
315 | set_req_state_err(s, op_ret); | |
316 | } | |
317 | dump_errno(s); | |
318 | end_header(s, this, "application/xml"); | |
319 | ||
320 | if (op_ret < 0) { | |
321 | return; | |
322 | } | |
323 | ||
324 | is_truncated = (response.hits.hits.size() >= max_keys); | |
325 | ||
326 | s->formatter->open_object_section("SearchMetadataResponse"); | |
327 | s->formatter->dump_string("Marker", marker_str); | |
328 | s->formatter->dump_string("IsTruncated", (is_truncated ? "true" : "false")); | |
329 | if (is_truncated) { | |
330 | s->formatter->dump_string("NextMarker", next_marker); | |
331 | } | |
332 | if (s->format == RGW_FORMAT_JSON) { | |
333 | s->formatter->open_array_section("Objects"); | |
334 | } | |
335 | for (auto& i : response.hits.hits) { | |
336 | s->formatter->open_object_section("Contents"); | |
337 | es_index_obj_response& e = i.source; | |
338 | s->formatter->dump_string("Bucket", e.bucket); | |
339 | s->formatter->dump_string("Key", e.key.name); | |
340 | string instance = (!e.key.instance.empty() ? e.key.instance : "null"); | |
341 | s->formatter->dump_string("Instance", instance.c_str()); | |
342 | s->formatter->dump_int("VersionedEpoch", e.versioned_epoch); | |
343 | dump_time(s, "LastModified", &e.meta.mtime); | |
344 | s->formatter->dump_int("Size", e.meta.size); | |
345 | s->formatter->dump_format("ETag", "\"%s\"", e.meta.etag.c_str()); | |
346 | s->formatter->dump_string("ContentType", e.meta.content_type.c_str()); | |
a8e16298 | 347 | s->formatter->dump_string("StorageClass", e.meta.storage_class.c_str()); |
31f18b77 FG |
348 | dump_owner(s, e.owner.get_id(), e.owner.get_display_name()); |
349 | s->formatter->open_array_section("CustomMetadata"); | |
350 | for (auto& m : e.meta.custom_str) { | |
351 | s->formatter->open_object_section("Entry"); | |
352 | s->formatter->dump_string("Name", m.first.c_str()); | |
353 | s->formatter->dump_string("Value", m.second); | |
354 | s->formatter->close_section(); | |
355 | } | |
356 | for (auto& m : e.meta.custom_int) { | |
357 | s->formatter->open_object_section("Entry"); | |
358 | s->formatter->dump_string("Name", m.first.c_str()); | |
359 | s->formatter->dump_int("Value", m.second); | |
360 | s->formatter->close_section(); | |
361 | } | |
362 | for (auto& m : e.meta.custom_date) { | |
363 | s->formatter->open_object_section("Entry"); | |
364 | s->formatter->dump_string("Name", m.first.c_str()); | |
365 | s->formatter->dump_string("Value", m.second); | |
366 | s->formatter->close_section(); | |
367 | } | |
368 | s->formatter->close_section(); | |
369 | rgw_flush_formatter(s, s->formatter); | |
370 | s->formatter->close_section(); | |
371 | }; | |
372 | if (s->format == RGW_FORMAT_JSON) { | |
373 | s->formatter->close_section(); | |
374 | } | |
375 | s->formatter->close_section(); | |
376 | rgw_flush_formatter_and_reset(s, s->formatter); | |
377 | } | |
378 | }; | |
379 | ||
380 | class RGWHandler_REST_MDSearch_S3 : public RGWHandler_REST_S3 { | |
381 | protected: | |
11fdf7f2 | 382 | RGWOp *op_get() override { |
31f18b77 | 383 | if (s->info.args.exists("query")) { |
9f95a23c | 384 | return new RGWMetadataSearch_ObjStore_S3(store->getRados()->get_sync_module()); |
31f18b77 FG |
385 | } |
386 | if (!s->init_state.url_bucket.empty() && | |
387 | s->info.args.exists("mdsearch")) { | |
388 | return new RGWGetBucketMetaSearch_ObjStore_S3; | |
389 | } | |
390 | return nullptr; | |
391 | } | |
11fdf7f2 | 392 | RGWOp *op_head() override { |
31f18b77 FG |
393 | return nullptr; |
394 | } | |
11fdf7f2 | 395 | RGWOp *op_post() override { |
31f18b77 FG |
396 | return nullptr; |
397 | } | |
398 | public: | |
11fdf7f2 | 399 | explicit RGWHandler_REST_MDSearch_S3(const rgw::auth::StrategyRegistry& auth_registry) : RGWHandler_REST_S3(auth_registry) {} |
31f18b77 FG |
400 | virtual ~RGWHandler_REST_MDSearch_S3() {} |
401 | }; | |
402 | ||
403 | ||
f67539c2 TL |
404 | RGWHandler_REST* RGWRESTMgr_MDSearch_S3::get_handler(rgw::sal::RGWRadosStore *store, |
405 | struct req_state* const s, | |
31f18b77 FG |
406 | const rgw::auth::StrategyRegistry& auth_registry, |
407 | const std::string& frontend_prefix) | |
408 | { | |
409 | int ret = | |
f67539c2 | 410 | RGWHandler_REST_S3::init_from_header(store, s, |
31f18b77 FG |
411 | RGW_FORMAT_XML, true); |
412 | if (ret < 0) { | |
413 | return nullptr; | |
414 | } | |
415 | ||
f67539c2 | 416 | if (!s->object->empty()) { |
31f18b77 FG |
417 | return nullptr; |
418 | } | |
419 | ||
420 | RGWHandler_REST *handler = new RGWHandler_REST_MDSearch_S3(auth_registry); | |
421 | ||
b3b6e05e | 422 | ldpp_dout(s, 20) << __func__ << " handler=" << typeid(*handler).name() |
31f18b77 FG |
423 | << dendl; |
424 | return handler; | |
425 | } | |
426 |