build: make the "rpm" rule work once again
[iwhd.git] / meta.cpp
blobb7ae903a34a4a43c1fb91cbeb065c62758e9b5cb
1 /* Copyright (C) 2010 Red Hat, Inc.
3 This program is free software: you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation, either version 3 of the License, or
6 (at your option) any later version.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
13 You should have received a copy of the GNU General Public License
14 along with this program. If not, see <http://www.gnu.org/licenses/>. */
16 #include <config.h>
18 #include <errno.h>
19 #include <pthread.h>
20 #include <stdio.h>
21 #include <sys/time.h>
22 #include <iostream>
23 #include "iwh.h"
24 #include "meta.h"
25 #include "query.h"
27 using namespace std;
29 /* Mongo (rather antisocially) tries to define this itself. */
30 #if defined(VERSION)
31 #undef VERSION
32 #endif
34 #include <mongo/client/dbclient.h>
35 using namespace mongo;
37 /* TBD: parameterize */
38 #define MAIN_TBL "repo.main"
41 * Since the client isn't inherently MT-safe, we serialize access to it
42 * ourselves. Fortunately, none of our metadata operations should be very
43 * long-lived; if they are it probably means our connection is FUBAR and other
44 * threads will be affected anyway.
47 #define SHOW_CONTENTION
49 pthread_mutex_t client_lock = PTHREAD_MUTEX_INITIALIZER;
50 #if defined(SHOW_CONTENTION)
51 #define CLIENT_LOCK do { \
52 if (pthread_mutex_trylock(&client_lock) != 0) { \
53 cout << "contention in " << __func__ << endl; \
54 pthread_mutex_lock(&client_lock); \
55 } \
56 } while (0)
57 #else
58 #define CLIENT_LOCK pthread_mutex_lock(&client_lock)
59 #endif
60 #define CLIENT_UNLOCK pthread_mutex_unlock(&client_lock)
62 void
63 dbl_to_str (double *foo, char *optr)
65 unsigned int i;
66 unsigned char *iptr = (unsigned char *)foo;
68 for (i = 0; i < sizeof(*foo); ++i) {
69 optr += sprintf(optr,"%02x",*(iptr++));
73 class RepoMeta;
74 class RepoQuery;
76 class RepoMeta {
78 public:
79 RepoMeta ();
80 ~RepoMeta ();
82 DBClientConnection client;
83 char addr[128];
85 char * DidPut (const char *bucket, const char *key,
86 const char *loc, size_t size);
87 void GotCopy (const char *bucket, const char *key,
88 const char *loc);
89 char * HasCopy (const char *bucket, const char *key,
90 const char *loc);
91 int SetValue (const char *bucket, const char *key,
92 const char *mkey, const char * mvalue);
93 int GetValue (const char *bucket, const char *key,
94 const char *mkey, char ** mvalue);
95 RepoQuery * NewQuery (const char *bucket, const char *key,
96 const char * expr);
97 auto_ptr<DBClientCursor> GetCursor (Query &q);
98 void Delete (const char *bucket, const char *key);
99 size_t GetSize (const char *bucket, const char *key);
100 int Check (const char *bucket, const char *key,
101 const char *depot);
102 void * GetAttrList (const char *bucket, const char *key);
105 class RepoQuery {
106 RepoMeta & parent;
107 DBClientCursor * curs;
108 value_t * expr;
109 public:
110 RepoQuery (const char *, const char *, const char *,
111 RepoMeta &);
112 ~RepoQuery ();
113 bool Next (void);
114 char *bucket;
115 char *key;
116 getter_t getter;
119 RepoMeta *it;
121 RepoMeta::RepoMeta ()
123 if (!verbose) {
124 cout.rdbuf(0);
125 cout << "bite me" << endl;
128 // TBD: assemble this string properly
129 sprintf(addr,"%s:%u",db_host,db_port);
130 try {
131 client.connect(addr);
133 catch (ConnectException &ce) {
134 cerr << "server down, no metadata access" << endl;
138 extern "C" void
139 meta_init (void)
141 it = new RepoMeta();
144 RepoMeta::~RepoMeta ()
148 extern "C" void
149 meta_fini (void)
151 delete it;
154 auto_ptr<DBClientCursor>
155 RepoMeta::GetCursor (Query &q)
157 auto_ptr<DBClientCursor> curs;
158 bool looping = false;
160 for (;;) {
161 if (!client.isFailed()) {
162 curs = client.query(MAIN_TBL,q);
163 if (curs.get()) {
164 break;
167 if (looping) {
168 break;
170 try {
171 client.connect(addr);
173 catch (ConnectException &ce) {
174 cerr << "reconnection to " << addr << " failed"
175 << endl;
177 looping = true;
180 return curs;
183 char *
184 RepoMeta::DidPut (const char *bucket, const char *key, const char *loc,
185 size_t size)
187 BSONObjBuilder bb;
188 struct timeval now_tv;
189 double now;
190 auto_ptr<DBClientCursor> curs;
191 Query q;
192 char now_str[sizeof(now)*2+1];
194 gettimeofday(&now_tv,NULL);
195 now = (double)now_tv.tv_sec + (double)now_tv.tv_usec / 1000000.0;
196 dbl_to_str(&now,now_str);
197 cout << "now_str = " << now_str << endl;
199 q = QUERY("_bucket"<<bucket<<"_key"<<key);
200 curs = GetCursor(q);
201 if (!curs.get()) {
202 cerr << "DidPut failed for " << bucket << "/" << key << endl;
203 return NULL;
205 if (curs->more()) {
206 /* Nice functionality, but what an ugly syntax! */
207 client.update(MAIN_TBL,q,BSON(
208 "$set"<<BSON("_loc"<<BSON_ARRAY(loc))
209 << "$set"<<BSON("_date"<<now)
210 << "$set"<<BSON("_etag"<<now_str)
211 << "$set"<<BSON("_size"<<(long long)size)));
212 #if 0
213 client.update(MAIN_TBL,q,
214 BSON("$set"<<BSON("_loc"<<BSON_ARRAY(loc))));
215 client.update(MAIN_TBL,q,
216 BSON("$set"<<BSON("_date"<<now)));
217 client.update(MAIN_TBL,q,
218 BSON("$set"<<BSON("_etag"<<now_str)));
219 client.update(MAIN_TBL,q,
220 BSON("$set"<<BSON("_size"<<(long long)size)));
221 #endif
223 else {
224 bb << "_bucket" << bucket << "_key" << key
225 << "_loc" << BSON_ARRAY(loc) << "_date" << now
226 << "_etag" << now_str << "_size" << (long long)size;
227 client.insert(MAIN_TBL,bb.obj());
230 return strdup(now_str);
233 extern "C" char *
234 meta_did_put (const char *bucket, const char *key, const char *loc, size_t size)
236 char *rc;
238 cout << "meta_did_put(" << bucket << "," << key << "," << loc << ")"
239 << endl;
241 CLIENT_LOCK;
242 rc = it->DidPut(bucket,key,loc,size);
243 CLIENT_UNLOCK;
245 return rc;
248 void
249 RepoMeta::GotCopy (const char *bucket, const char *key, const char *loc)
251 BSONObjBuilder bb;
252 auto_ptr<DBClientCursor> curs;
253 Query q;
255 q = QUERY("_bucket"<<bucket<<"_key"<<key);
256 curs = GetCursor(q);
257 if (!curs.get()) {
258 cerr << "GotCopy failed for " << bucket << "/" << key << endl;
259 return;
261 if (curs->more()) {
262 /* Nice functionality, but what an ugly syntax! */
263 client.update(MAIN_TBL,q,BSON("$addToSet"<<BSON("_loc"<<loc)));
265 else {
266 cerr << bucket << "/" << key << " not found in GotCopy!" << endl;
270 extern "C" void
271 meta_got_copy (const char *bucket, const char *key, const char *loc)
273 CLIENT_LOCK;
274 it->GotCopy(bucket,key,loc);
275 CLIENT_UNLOCK;
278 char *
279 RepoMeta::HasCopy (const char *bucket, const char *key, const char *loc)
281 BSONObjBuilder bb;
282 auto_ptr<DBClientCursor> curs;
283 Query q;
284 const char *value;
286 q = QUERY("_bucket"<<bucket<<"_key"<<key<<"_loc"<<loc);
287 curs = GetCursor(q);
288 if (!curs.get()) {
289 cerr << "HasCopy failed for " << bucket << "/" << key << endl;
290 return NULL;
292 if (!curs->more()) {
293 cout << bucket << "/" << key << " not found at " << loc << endl;
294 return (char *)"";
297 value = curs->next().getStringField("_etag");
298 if (!value || !*value) {
299 cout << bucket << "/" << key << " no _etag at " << loc << endl;
300 return (char *)"";
303 cout << bucket << "/" << key << " _etag = " << value << endl;
304 return strdup(value);
307 extern "C" char *
308 meta_has_copy (const char *bucket, const char *key, const char *loc)
310 char *rc;
312 CLIENT_LOCK;
313 rc = it->HasCopy(bucket,key,loc);
314 CLIENT_UNLOCK;
316 return rc;
320 RepoMeta::SetValue (const char *bucket, const char *key, const char *mkey,
321 const char * mvalue)
323 Query q = QUERY("_bucket"<<bucket<<"_key"<<key);
325 try {
326 client.update(MAIN_TBL,q,BSON("$set"<<BSON(mkey<<mvalue)),1);
328 catch (ConnectException &ce) {
329 cerr << "SetValue failed for " << bucket << "/" << key << ":"
330 << mkey << endl;
331 return ENOTCONN;
334 // TBD: check for and propagate errors.
335 return 0;
338 extern "C" int
339 meta_set_value (const char *bucket, const char *key, const char *mkey,
340 const char * mvalue)
342 int rc;
344 CLIENT_LOCK;
345 rc = it->SetValue(bucket,key,mkey,mvalue);
346 CLIENT_UNLOCK;
348 return rc;
352 RepoMeta::GetValue (const char *bucket, const char *key, const char *mkey,
353 char ** mvalue)
355 auto_ptr<DBClientCursor> curs;
356 Query q;
357 BSONObj bo;
358 const char * data;
360 q = QUERY("_bucket"<<bucket<<"_key"<<key);
361 curs = GetCursor(q);
362 if (!curs.get()) {
363 cerr << "GetValue failed for " << bucket << "/" << key << ":"
364 << mkey << endl;
365 return ENOTCONN;
367 if (!curs->more()) {
368 return ENXIO;
371 bo = curs->next();
372 data = bo.getStringField(mkey);
373 if (!data || !*data) {
374 return ENXIO;
377 *mvalue = strdup(data);
378 return 0;
381 extern "C" int
382 meta_get_value (const char *bucket, const char *key, const char *mkey,
383 char ** mvalue)
385 int rc;
387 CLIENT_LOCK;
388 rc = it->GetValue(bucket,key,mkey,mvalue);
389 CLIENT_UNLOCK;
391 return rc;
394 RepoQuery::RepoQuery (const char *bucket, const char *key, const char *qstr,
395 RepoMeta &p)
396 : parent(p)
398 Query q;
399 auto_ptr<DBClientCursor> tmp;
401 if (bucket) {
402 cout << "bucket is " << bucket << " and we don't care" << endl;
403 q = QUERY("_bucket"<<bucket);
405 else if (key) {
406 cout << "key is " << key << " and we don't care" << endl;
407 q = QUERY("_key"<<key);
409 else {
410 abort();
414 * TBD: we should really convert our query into one of Mongo's,
415 * and let them do all the work. Handling the general case
416 * would be pretty messy, but we could handle specific cases
417 * pretty easily. For example, a very high percentage of
418 * queries are likely to be a single field/value comparison.
419 * For now just punt, but revisit later.
422 if (qstr) {
423 expr = parse(qstr);
424 if (expr) {
425 print_value(expr);
427 else {
428 cout << "could not parse " << qstr << endl;
431 else {
432 expr = NULL;
435 curs = parent.GetCursor(q).release();
436 bucket = NULL;
437 key = NULL;
440 RepoQuery::~RepoQuery ()
442 cout << "in " << __func__ << endl;
443 if (expr) {
444 free_value(expr);
447 delete curs;
450 extern "C" void
451 meta_query_stop (void * qobj)
453 CLIENT_LOCK;
454 delete (RepoQuery *)qobj;
455 CLIENT_UNLOCK;
458 extern "C" const char *
459 query_getter (void *ctx, const char *id)
461 BSONObj *cur_bo = (BSONObj *)ctx;
463 return (char *)cur_bo->getStringField(id);
466 bool
467 RepoQuery::Next (void)
469 BSONObj bo;
471 if (!curs) {
472 return false;
475 while (curs->more()) {
476 bo = curs->next();
477 if (expr) {
478 getter.func = query_getter;
479 getter.ctx = (void *)&bo;
480 if (eval(expr,&getter,NULL) <= 0) {
481 continue;
484 bucket = (char *)bo.getStringField("_bucket");
485 key = (char *)bo.getStringField("_key");
486 return true;
489 return false;
492 RepoQuery *
493 RepoMeta::NewQuery (const char *bucket, const char *key, const char *expr)
495 return new RepoQuery(bucket,key,expr,*this);
498 extern "C" void *
499 meta_query_new (const char *bucket, const char *key, const char *expr)
501 void *rc;
503 if ((bucket && key) || (!bucket && !key)) {
504 return NULL;
507 CLIENT_LOCK;
508 rc = it->NewQuery(bucket,key,expr);
509 CLIENT_UNLOCK;
511 return rc;
514 extern "C" int
515 meta_query_next (void * qobj, char ** bucket, char ** key)
517 RepoQuery * rq = (RepoQuery *)qobj;
519 CLIENT_LOCK;
520 if (!rq->Next()) {
521 CLIENT_UNLOCK;
522 return 0;
524 CLIENT_UNLOCK;
526 *bucket = rq->bucket;
527 *key = rq->key;
528 return 1;
531 #if 0
532 char *
533 RepoMeta::BucketList (void)
536 * TBD: make this return values instead of producing output.
537 * This is just a code fragment showing how to get a list of buckets,
538 * in case I forget.
540 BSONObj repl;
542 BSONObj dist = BSON("distinct"<<"main"<<"_key"<<"_bucket");
543 if (client.runCommand("repo",dist,repl)) {
544 cout << repl.toString() << endl;
545 BSONObj elem = repl.getField("values").embeddedObject();
546 for (int i = 0; i < elem.nFields(); ++i) {
547 cout << elem[i].str() << endl;
551 #endif
553 void
554 RepoMeta::Delete (const char *bucket, const char *key)
556 Query q = QUERY("_bucket"<<bucket<<"_key"<<key);
558 try {
559 client.remove(MAIN_TBL,q);
561 catch (ConnectException &ce) {
562 cerr << "Delete failed for " << bucket << "/" << key << endl;
566 extern "C"
567 void
568 meta_delete (const char *bucket, const char *key)
570 CLIENT_LOCK;
571 it->Delete(bucket,key);
572 CLIENT_UNLOCK;
575 size_t
576 RepoMeta::GetSize (const char *bucket, const char *key)
578 auto_ptr<DBClientCursor> curs;
579 Query q;
580 BSONObj bo;
581 const char * data;
583 (void)data;
585 q = QUERY("_bucket"<<bucket<<"_key"<<key);
586 curs = GetCursor(q);
588 if (!curs->more()) {
589 return 0;
592 bo = curs->next();
593 return bo.getField("_size").numberLong();
596 extern "C"
597 size_t
598 meta_get_size (const char *bucket, const char *key)
600 size_t rc;
602 CLIENT_LOCK;
603 rc = it->GetSize(bucket,key);
604 CLIENT_UNLOCK;
606 return rc;
609 class AttrList {
610 public:
611 AttrList (BSONObj &);
612 int Next (const char **, const char **);
613 BSONObj obj;
614 vector<BSONElement> vec;
615 int idx;
618 AttrList::AttrList (BSONObj &bo)
620 obj = bo.copy();
621 obj.elems(vec);
622 idx = 0;
626 AttrList::Next (const char **name, const char **value)
628 BSONElement elem;
630 while (idx < vec.size()) {
631 elem = vec[idx++];
632 if (elem.type() == String) {
633 *name = elem.fieldName();
634 *value = elem.String().c_str();
635 return 1;
639 return 0;
642 void *
643 RepoMeta::GetAttrList (const char *bucket, const char *key)
645 auto_ptr<DBClientCursor> curs;
646 Query q;
647 BSONObj bo;
649 q = QUERY("_bucket"<<bucket<<"_key"<<key);
650 curs = GetCursor(q);
652 if (!curs->more()) {
653 return NULL;
655 bo = curs->next();
657 return new AttrList(bo);
659 extern "C"
660 void *
661 meta_get_attrs (const char *bucket, const char *key)
663 void *poc;
665 CLIENT_LOCK;
666 poc = it->GetAttrList(bucket,key);
667 CLIENT_UNLOCK;
669 return poc;
672 extern "C"
674 meta_attr_next (void *ctx, const char **name, const char **value)
676 AttrList *poc = (AttrList *)ctx;
678 return poc->Next(name,value);
681 extern "C"
682 void
683 meta_attr_stop (void *ctx)
685 AttrList *poc = (AttrList *)ctx;
687 delete poc;