]> git.proxmox.com Git - ceph.git/blame - ceph/src/key_value_store/cls_kvs.cc
update sources to ceph Nautilus 14.2.1
[ceph.git] / ceph / src / key_value_store / cls_kvs.cc
CommitLineData
7c673cae
FG
1/*
2 * OSD classes for the key value store
3 *
4 * Created on: Aug 10, 2012
5 * Author: Eleanor Cawthon
6 */
7
11fdf7f2 8#include "include/compat.h"
7c673cae
FG
9#include "objclass/objclass.h"
10#include <errno.h>
11#include "key_value_store/kvs_arg_types.h"
12#include "include/types.h"
13#include <iostream>
14#include <climits>
15
16
17/**
18 * finds the index_data where a key belongs.
19 *
20 * @param key: the key to search for
21 * @param idata: the index_data for the first index value such that idata.key
22 * is greater than key.
23 * @param next_idata: the index_data for the next index entry after idata
24 * @pre: key is not encoded
25 * @post: idata contains complete information
26 * stored
27 */
28static int get_idata_from_key(cls_method_context_t hctx, const string &key,
29 index_data &idata, index_data &next_idata) {
30 bufferlist raw_val;
31 int r = 0;
32 std::map<std::string, bufferlist> kvmap;
33
c07f9fc5
FG
34 bool more;
35
36 r = cls_cxx_map_get_vals(hctx, key_data(key).encoded(), "", 2, &kvmap, &more);
7c673cae
FG
37 if (r < 0) {
38 CLS_LOG(20, "error reading index for range %s: %d", key.c_str(), r);
39 return r;
40 }
41
42 r = cls_cxx_map_get_val(hctx, key_data(key).encoded(), &raw_val);
43 if (r == 0){
44 CLS_LOG(20, "%s is already in the index: %d", key.c_str(), r);
11fdf7f2 45 auto b = raw_val.cbegin();
7c673cae
FG
46 idata.decode(b);
47 if (!kvmap.empty()) {
11fdf7f2 48 auto b = kvmap.begin()->second.cbegin();
7c673cae
FG
49 next_idata.decode(b);
50 }
51 return r;
52 } else if (r == -ENOENT || r == -ENODATA) {
11fdf7f2 53 auto b = kvmap.begin()->second.cbegin();
7c673cae
FG
54 idata.decode(b);
55 if (idata.kdata.prefix != "1") {
11fdf7f2 56 auto nb = (++kvmap.begin())->second.cbegin();
7c673cae
FG
57 next_idata.decode(nb);
58 }
59 r = 0;
60 } else if (r < 0) {
61 CLS_LOG(20, "error reading index for duplicates %s: %d", key.c_str(), r);
62 return r;
63 }
64
65 CLS_LOG(20, "idata is %s", idata.str().c_str());
66 return r;
67}
68
69
70static int get_idata_from_key_op(cls_method_context_t hctx,
71 bufferlist *in, bufferlist *out) {
72 CLS_LOG(20, "get_idata_from_key_op");
73 idata_from_key_args op;
11fdf7f2 74 auto it = in->cbegin();
7c673cae 75 try {
11fdf7f2 76 decode(op, it);
7c673cae
FG
77 } catch (buffer::error& err) {
78 CLS_LOG(20, "error decoding idata_from_key_args.");
79 return -EINVAL;
80 }
81 int r = get_idata_from_key(hctx, op.key, op.idata, op.next_idata);
82 if (r < 0) {
83 return r;
84 } else {
11fdf7f2 85 encode(op, *out);
7c673cae
FG
86 return 0;
87 }
88}
89
90/**
91 * finds the object in the index with the lowest key value that is greater
92 * than idata.key. If idata.key is the max key, returns -EOVERFLOW. If
93 * idata has a prefix and has timed out, cleans up.
94 *
95 * @param idata: idata for the object to search for.
96 * @param out_data: the idata for the next object.
97 *
98 * @pre: idata must contain a key.
99 * @post: out_data contains complete information
100 */
101static int get_next_idata(cls_method_context_t hctx, const index_data &idata,
102 index_data &out_data) {
103 int r = 0;
104 std::map<std::string, bufferlist> kvs;
c07f9fc5
FG
105 bool more;
106 r = cls_cxx_map_get_vals(hctx, idata.kdata.encoded(), "", 1, &kvs, &more);
7c673cae
FG
107 if (r < 0){
108 CLS_LOG(20, "getting kvs failed with error %d", r);
109 return r;
110 }
111
112 if (!kvs.empty()) {
113 out_data.kdata.parse(kvs.begin()->first);
11fdf7f2 114 auto b = kvs.begin()->second.cbegin();
7c673cae
FG
115 out_data.decode(b);
116 } else {
117 r = -EOVERFLOW;
118 }
119
120 return r;
121}
122
123static int get_next_idata_op(cls_method_context_t hctx,
124 bufferlist *in, bufferlist *out) {
125 CLS_LOG(20, "get_next_idata_op");
126 idata_from_idata_args op;
11fdf7f2 127 auto it = in->cbegin();
7c673cae 128 try {
11fdf7f2 129 decode(op, it);
7c673cae
FG
130 } catch (buffer::error& err) {
131 return -EINVAL;
132 }
133 int r = get_next_idata(hctx, op.idata, op.next_idata);
134 if (r < 0) {
135 return r;
136 } else {
137 op.encode(*out);
138 return 0;
139 }
140}
141
142/**
143 * finds the object in the index with the highest key value that is less
144 * than idata.key. If idata.key is the lowest key, returns -ERANGE If
145 * idata has a prefix and has timed out, cleans up.
146 *
147 * @param idata: idata for the object to search for.
148 * @param out_data: the idata for the next object.
149 *
150 * @pre: idata must contain a key.
151 * @ost: out_data contains complete information
152 */
153static int get_prev_idata(cls_method_context_t hctx, const index_data &idata,
154 index_data &out_data) {
155 int r = 0;
156 std::map<std::string, bufferlist> kvs;
c07f9fc5
FG
157 bool more;
158 r = cls_cxx_map_get_vals(hctx, "", "", LONG_MAX, &kvs, &more);
7c673cae
FG
159 if (r < 0){
160 CLS_LOG(20, "getting kvs failed with error %d", r);
161 return r;
162 }
163
164 std::map<std::string, bufferlist>::iterator it =
165 kvs.lower_bound(idata.kdata.encoded());
166 if (it->first != idata.kdata.encoded()) {
167 CLS_LOG(20, "object %s not found in the index (expected %s, found %s)",
168 idata.str().c_str(), idata.kdata.encoded().c_str(),
169 it->first.c_str());
170 return -ENODATA;
171 }
172 if (it == kvs.begin()) {
173 //it is the first object, there is no previous.
174 return -ERANGE;
175 } else {
176 --it;
177 }
178 out_data.kdata.parse(it->first);
11fdf7f2 179 auto b = it->second.cbegin();
7c673cae
FG
180 out_data.decode(b);
181
182 return 0;
183}
184
185static int get_prev_idata_op(cls_method_context_t hctx,
186 bufferlist *in, bufferlist *out) {
187 CLS_LOG(20, "get_next_idata_op");
188 idata_from_idata_args op;
11fdf7f2 189 auto it = in->cbegin();
7c673cae 190 try {
11fdf7f2 191 decode(op, it);
7c673cae
FG
192 } catch (buffer::error& err) {
193 return -EINVAL;
194 }
195 int r = get_prev_idata(hctx, op.idata, op.next_idata);
196 if (r < 0) {
197 return r;
198 } else {
199 op.encode(*out);
200 return 0;
201 }
202}
203
204/**
205 * Read all of the index entries where any keys in the map go
206 */
207static int read_many(cls_method_context_t hctx, const set<string> &keys,
208 map<string, bufferlist> * out) {
209 int r = 0;
c07f9fc5 210 bool more;
7c673cae
FG
211 CLS_ERR("reading from a map of size %d, first key encoded is %s",
212 (int)keys.size(), key_data(*keys.begin()).encoded().c_str());
213 r = cls_cxx_map_get_vals(hctx, key_data(*keys.begin()).encoded().c_str(),
c07f9fc5 214 "", LONG_MAX, out, &more);
7c673cae
FG
215 if (r < 0) {
216 CLS_ERR("getting omap vals failed with error %d", r);
217 }
218
219 CLS_ERR("got map of size %d ", (int)out->size());
220 if (out->size() > 1) {
221 out->erase(out->upper_bound(key_data(*keys.rbegin()).encoded().c_str()),
222 out->end());
223 }
224 CLS_ERR("returning map of size %d", (int)out->size());
225 return r;
226}
227
228static int read_many_op(cls_method_context_t hctx, bufferlist *in,
229 bufferlist *out) {
230 CLS_LOG(20, "read_many_op");
231 set<string> op;
232 map<string, bufferlist> outmap;
11fdf7f2 233 auto it = in->cbegin();
7c673cae 234 try {
11fdf7f2 235 decode(op, it);
7c673cae
FG
236 } catch (buffer::error & err) {
237 return -EINVAL;
238 }
239 int r = read_many(hctx, op, &outmap);
240 if (r < 0) {
241 return r;
242 } else {
243 encode(outmap, *out);
244 return 0;
245 }
246}
247
248/**
249 * Checks the unwritable xattr. If it is "1" (i.e., it is unwritable), returns
250 * -EACCES. otherwise, returns 0.
251 */
252static int check_writable(cls_method_context_t hctx) {
253 bufferlist bl;
254 int r = cls_cxx_getxattr(hctx, "unwritable", &bl);
255 if (r < 0) {
256 CLS_LOG(20, "error reading xattr %s: %d", "unwritable", r);
257 return r;
258 }
259 if (string(bl.c_str(), bl.length()) == "1") {
260 return -EACCES;
261 } else{
262 return 0;
263 }
264}
265
266static int check_writable_op(cls_method_context_t hctx,
267 bufferlist *in, bufferlist *out) {
268 CLS_LOG(20, "check_writable_op");
269 return check_writable(hctx);
270}
271
272/**
273 * returns -EKEYREJECTED if size is outside of bound, according to comparator.
274 *
275 * @bound: the limit to test
276 * @comparator: should be CEPH_OSD_CMPXATTR_OP_[EQ|GT|LT]
277 */
278static int assert_size_in_bound(cls_method_context_t hctx, int bound,
279 int comparator) {
280 //determine size
281 bufferlist size_bl;
282 int r = cls_cxx_getxattr(hctx, "size", &size_bl);
283 if (r < 0) {
284 CLS_LOG(20, "error reading xattr %s: %d", "size", r);
285 return r;
286 }
287
288 int size = atoi(string(size_bl.c_str(), size_bl.length()).c_str());
289 CLS_LOG(20, "size is %d, bound is %d", size, bound);
290
291 //compare size to comparator
292 switch (comparator) {
293 case CEPH_OSD_CMPXATTR_OP_EQ:
294 if (size != bound) {
295 return -EKEYREJECTED;
296 }
297 break;
298 case CEPH_OSD_CMPXATTR_OP_LT:
299 if (size >= bound) {
300 return -EKEYREJECTED;
301 }
302 break;
303 case CEPH_OSD_CMPXATTR_OP_GT:
304 if (size <= bound) {
305 return -EKEYREJECTED;
306 }
307 break;
308 default:
309 CLS_LOG(20, "invalid argument passed to assert_size_in_bound: %d",
310 comparator);
311 return -EINVAL;
312 }
313 return 0;
314}
315
316static int assert_size_in_bound_op(cls_method_context_t hctx,
317 bufferlist *in, bufferlist *out) {
318 CLS_LOG(20, "assert_size_in_bound_op");
319 assert_size_args op;
11fdf7f2 320 auto it = in->cbegin();
7c673cae 321 try {
11fdf7f2 322 decode(op, it);
7c673cae
FG
323 } catch (buffer::error& err) {
324 return -EINVAL;
325 }
326 return assert_size_in_bound(hctx, op.bound, op.comparator);
327}
328
329/**
330 * Attempts to insert omap into this object's omap.
331 *
332 * @return:
333 * if unwritable, returns -EACCES.
334 * if size > bound and key doesn't already exist in the omap, returns -EBALANCE.
335 * if exclusive is true, returns -EEXIST if any keys already exist.
336 *
337 * @post: object has omap entries inserted, and size xattr is updated
338 */
339static int omap_insert(cls_method_context_t hctx,
340 const map<string, bufferlist> &omap, int bound, bool exclusive) {
341
342 uint64_t size;
343 time_t time;
344 int r = cls_cxx_stat(hctx, &size, &time);
345 if (r < 0) {
346 return r;
347 }
348 CLS_LOG(20, "inserting %s", omap.begin()->first.c_str());
349 r = check_writable(hctx);
350 if (r < 0) {
351 CLS_LOG(20, "omap_insert: this object is unwritable: %d", r);
352 return r;
353 }
354
355 int assert_bound = bound;
356
357 //if this is an exclusive insert, make sure the key doesn't already exist.
358 for (map<string, bufferlist>::const_iterator it = omap.begin();
359 it != omap.end(); ++it) {
360 bufferlist bl;
361 r = cls_cxx_map_get_val(hctx, it->first, &bl);
362 if (r == 0 && string(bl.c_str(), bl.length()) != ""){
363 if (exclusive) {
364 CLS_LOG(20, "error: this is an exclusive insert and %s exists.",
365 it->first.c_str());
366 return -EEXIST;
367 }
368 assert_bound++;
369 CLS_LOG(20, "increased assert_bound to %d", assert_bound);
370 } else if (r != -ENODATA && r != -ENOENT) {
371 CLS_LOG(20, "error reading omap val for %s: %d", it->first.c_str(), r);
372 return r;
373 }
374 }
375
376 bufferlist old_size;
377 r = cls_cxx_getxattr(hctx, "size", &old_size);
378 if (r < 0) {
379 CLS_LOG(20, "error reading xattr %s: %d", "size", r);
380 return r;
381 }
382
383 int old_size_int = atoi(string(old_size.c_str(), old_size.length()).c_str());
384
385 CLS_LOG(20, "asserting size is less than %d (bound is %d)", assert_bound, bound);
386 if (old_size_int >= assert_bound) {
387 return -EKEYREJECTED;
388 }
389
390 int new_size_int = old_size_int + omap.size() - (assert_bound - bound);
391 CLS_LOG(20, "old size is %d, new size is %d", old_size_int, new_size_int);
392 bufferlist new_size;
393 stringstream s;
394 s << new_size_int;
395 new_size.append(s.str());
396
397 r = cls_cxx_map_set_vals(hctx, &omap);
398 if (r < 0) {
399 CLS_LOG(20, "error setting omap: %d", r);
400 return r;
401 }
402
403 r = cls_cxx_setxattr(hctx, "size", &new_size);
404 if (r < 0) {
405 CLS_LOG(20, "error setting xattr %s: %d", "size", r);
406 return r;
407 }
408 CLS_LOG(20, "successfully inserted %s", omap.begin()->first.c_str());
409 return 0;
410}
411
412static int omap_insert_op(cls_method_context_t hctx,
413 bufferlist *in, bufferlist *out) {
414 CLS_LOG(20, "omap_insert");
415 omap_set_args op;
11fdf7f2 416 auto it = in->cbegin();
7c673cae 417 try {
11fdf7f2 418 decode(op, it);
7c673cae
FG
419 } catch (buffer::error& err) {
420 return -EINVAL;
421 }
422 return omap_insert(hctx, op.omap, op.bound, op.exclusive);
423}
424
425static int create_with_omap(cls_method_context_t hctx,
426 const map<string, bufferlist> &omap) {
427 CLS_LOG(20, "creating with omap: %s", omap.begin()->first.c_str());
428 //first make sure the object is writable
429 int r = cls_cxx_create(hctx, true);
430 if (r < 0) {
431 CLS_LOG(20, "omap create: creating failed: %d", r);
432 return r;
433 }
434
435 int new_size_int = omap.size();
436 CLS_LOG(20, "omap insert: new size is %d", new_size_int);
437 bufferlist new_size;
438 stringstream s;
439 s << new_size_int;
440 new_size.append(s.str());
441
442 r = cls_cxx_map_set_vals(hctx, &omap);
443 if (r < 0) {
444 CLS_LOG(20, "omap create: error setting omap: %d", r);
445 return r;
446 }
447
448 r = cls_cxx_setxattr(hctx, "size", &new_size);
449 if (r < 0) {
450 CLS_LOG(20, "omap create: error setting xattr %s: %d", "size", r);
451 return r;
452 }
453
454 bufferlist u;
455 u.append("0");
456 r = cls_cxx_setxattr(hctx, "unwritable", &u);
457 if (r < 0) {
458 CLS_LOG(20, "omap create: error setting xattr %s: %d", "unwritable", r);
459 return r;
460 }
461
462 CLS_LOG(20, "successfully created %s", omap.begin()->first.c_str());
463 return 0;
464}
465
466static int create_with_omap_op(cls_method_context_t hctx,
467 bufferlist *in, bufferlist *out) {
468 CLS_LOG(20, "omap_insert");
469 map<string, bufferlist> omap;
11fdf7f2 470 auto it = in->cbegin();
7c673cae 471 try {
11fdf7f2 472 decode(omap, it);
7c673cae
FG
473 } catch (buffer::error& err) {
474 return -EINVAL;
475 }
476 return create_with_omap(hctx, omap);
477}
478
479/**
480 * Attempts to remove omap from this object's omap.
481 *
482 * @return:
483 * if unwritable, returns -EACCES.
484 * if size < bound and key doesn't already exist in the omap, returns -EBALANCE.
485 * if any of the keys are not in this object, returns -ENODATA.
486 *
487 * @post: object has omap entries removed, and size xattr is updated
488 */
489static int omap_remove(cls_method_context_t hctx,
490 const std::set<string> &omap, int bound) {
491 int r;
492 uint64_t size;
493 time_t time;
494 r = cls_cxx_stat(hctx, &size, &time);
495 if (r < 0) {
496 return r;
497 }
498
499 //first make sure the object is writable
500 r = check_writable(hctx);
501 if (r < 0) {
502 return r;
503 }
504
505 //check for existance of the key first
506 for (set<string>::const_iterator it = omap.begin();
507 it != omap.end(); ++it) {
508 bufferlist bl;
509 r = cls_cxx_map_get_val(hctx, *it, &bl);
510 if (r == -ENOENT || r == -ENODATA
511 || string(bl.c_str(), bl.length()) == ""){
512 return -ENODATA;
513 } else if (r < 0) {
514 CLS_LOG(20, "error reading omap val for %s: %d", it->c_str(), r);
515 return r;
516 }
517 }
518
519 //fail if removing from an object with only bound entries.
520 bufferlist old_size;
521 r = cls_cxx_getxattr(hctx, "size", &old_size);
522 if (r < 0) {
523 CLS_LOG(20, "error reading xattr %s: %d", "size", r);
524 return r;
525 }
526 int old_size_int = atoi(string(old_size.c_str(), old_size.length()).c_str());
527
528 CLS_LOG(20, "asserting size is greater than %d", bound);
529 if (old_size_int <= bound) {
530 return -EKEYREJECTED;
531 }
532
533 int new_size_int = old_size_int - omap.size();
534 CLS_LOG(20, "old size is %d, new size is %d", old_size_int, new_size_int);
535 bufferlist new_size;
536 stringstream s;
537 s << new_size_int;
538 new_size.append(s.str());
539
540 r = cls_cxx_setxattr(hctx, "size", &new_size);
541 if (r < 0) {
542 CLS_LOG(20, "error setting xattr %s: %d", "unwritable", r);
543 return r;
544 }
545
546 for (std::set<string>::const_iterator it = omap.begin();
547 it != omap.end(); ++it) {
548 r = cls_cxx_map_remove_key(hctx, *it);
549 if (r < 0) {
550 CLS_LOG(20, "error removing omap: %d", r);
551 return r;
552 }
553 }
554 return 0;
555}
556
557static int omap_remove_op(cls_method_context_t hctx,
558 bufferlist *in, bufferlist *out) {
559 CLS_LOG(20, "omap_remove");
560 omap_rm_args op;
11fdf7f2 561 auto it = in->cbegin();
7c673cae 562 try {
11fdf7f2 563 decode(op, it);
7c673cae
FG
564 } catch (buffer::error& err) {
565 return -EINVAL;
566 }
567 return omap_remove(hctx, op.omap, op.bound);
568}
569
570/**
571 * checks to see if this object needs to be split or rebalanced. if so, reads
572 * information about it.
573 *
574 * @post: if assert_size_in_bound(hctx, bound, comparator) succeeds,
575 * odata contains the size, omap, and unwritable attributes for this object.
576 * Otherwise, odata contains the size and unwritable attribute.
577 */
578static int maybe_read_for_balance(cls_method_context_t hctx,
579 object_data &odata, int bound, int comparator) {
580 CLS_LOG(20, "rebalance reading");
581 //if unwritable, return
582 int r = check_writable(hctx);
583 if (r < 0) {
584 odata.unwritable = true;
585 CLS_LOG(20, "rebalance read: error getting xattr %s: %d", "unwritable", r);
586 return r;
587 } else {
588 odata.unwritable = false;
589 }
590
591 //get the size attribute
592 bufferlist size;
593 r = cls_cxx_getxattr(hctx, "size", &size);
594 if (r < 0) {
595 CLS_LOG(20, "rebalance read: error getting xattr %s: %d", "size", r);
596 return r;
597 }
598 odata.size = atoi(string(size.c_str(), size.length()).c_str());
599
600 //check if it needs to be balanced
601 r = assert_size_in_bound(hctx, bound, comparator);
602 if (r < 0) {
603 CLS_LOG(20, "rebalance read: error on asserting size: %d", r);
604 return -EBALANCE;
605 }
606
607 //if the assert succeeded, it needs to be balanced
c07f9fc5
FG
608 bool more;
609 r = cls_cxx_map_get_vals(hctx, "", "", LONG_MAX, &odata.omap, &more);
7c673cae
FG
610 if (r < 0){
611 CLS_LOG(20, "rebalance read: getting kvs failed with error %d", r);
612 return r;
613 }
614
615 CLS_LOG(20, "rebalance read: size xattr is %llu, omap size is %llu",
616 (unsigned long long)odata.size,
617 (unsigned long long)odata.omap.size());
618 return 0;
619}
620
621static int maybe_read_for_balance_op(cls_method_context_t hctx,
622 bufferlist *in, bufferlist *out) {
623 CLS_LOG(20, "maybe_read_for_balance");
624 rebalance_args op;
11fdf7f2 625 auto it = in->cbegin();
7c673cae 626 try {
11fdf7f2 627 decode(op, it);
7c673cae
FG
628 } catch (buffer::error& err) {
629 return -EINVAL;
630 }
631 int r = maybe_read_for_balance(hctx, op.odata, op.bound, op.comparator);
632 if (r < 0) {
633 return r;
634 } else {
635 op.encode(*out);
636 return 0;
637 }
638}
639
640
641CLS_INIT(kvs)
642{
643 CLS_LOG(20, "Loaded assert condition class!");
644
645 cls_handle_t h_class;
646 cls_method_handle_t h_get_idata_from_key;
647 cls_method_handle_t h_get_next_idata;
648 cls_method_handle_t h_get_prev_idata;
649 cls_method_handle_t h_read_many;
650 cls_method_handle_t h_check_writable;
651 cls_method_handle_t h_assert_size_in_bound;
652 cls_method_handle_t h_omap_insert;
653 cls_method_handle_t h_create_with_omap;
654 cls_method_handle_t h_omap_remove;
655 cls_method_handle_t h_maybe_read_for_balance;
656
657 cls_register("kvs", &h_class);
658 cls_register_cxx_method(h_class, "get_idata_from_key",
659 CLS_METHOD_RD,
660 get_idata_from_key_op, &h_get_idata_from_key);
661 cls_register_cxx_method(h_class, "get_next_idata",
662 CLS_METHOD_RD,
663 get_next_idata_op, &h_get_next_idata);
664 cls_register_cxx_method(h_class, "get_prev_idata",
665 CLS_METHOD_RD,
666 get_prev_idata_op, &h_get_prev_idata);
667 cls_register_cxx_method(h_class, "read_many",
668 CLS_METHOD_RD,
669 read_many_op, &h_read_many);
670 cls_register_cxx_method(h_class, "check_writable",
671 CLS_METHOD_RD | CLS_METHOD_WR,
672 check_writable_op, &h_check_writable);
673 cls_register_cxx_method(h_class, "assert_size_in_bound",
674 CLS_METHOD_WR,
675 assert_size_in_bound_op, &h_assert_size_in_bound);
676 cls_register_cxx_method(h_class, "omap_insert",
677 CLS_METHOD_WR,
678 omap_insert_op, &h_omap_insert);
679 cls_register_cxx_method(h_class, "create_with_omap",
680 CLS_METHOD_WR,
681 create_with_omap_op, &h_create_with_omap);
682 cls_register_cxx_method(h_class, "omap_remove",
683 CLS_METHOD_WR,
684 omap_remove_op, &h_omap_remove);
685 cls_register_cxx_method(h_class, "maybe_read_for_balance",
686 CLS_METHOD_RD,
687 maybe_read_for_balance_op, &h_maybe_read_for_balance);
688
689 return;
690}