bool get_field(doid_t do_id, const Field* field, val_t &value) { m_log->trace() << "Getting field on obj-" << do_id << endl; YAML::Node document; if(!load(do_id, document)) { return false; } // Get the fields from the file that are not being updated YAML::Node node = document["fields"][field->get_name()]; if(!node.IsDefined() || node.IsNull()) { return false; } m_log->trace() << "Found requested field: " + field->get_name() << endl; value = read_yaml_field(field, node, do_id); if(value.size() > 0) { return true; } return false; }
bool get_fields(doid_t do_id, const vector<const Field*> &fields, map_t &values) { m_log->trace() << "Getting fields on obj-" << do_id << endl; YAML::Node document; if(!load(do_id, document)) { return false; } // Get the fields from the file that are not being updated for(auto it = fields.begin(); it != fields.end(); ++it) { const Field* field = *it; m_log->trace() << "Searching for field: " << field->get_name() << endl; YAML::Node existing = document["fields"]; for(auto it2 = existing.begin(); it2 != existing.end(); ++it2) { if(it2->first.as<string>() == field->get_name()) { vector<uint8_t> value = read_yaml_field(field, it2->second, do_id); if(value.size() > 0) { values[*it] = value; m_log->trace() << "Found requested field: " + field->get_name() << endl; } } } } return true; }
// This is used when the monotonic counter is exhausted: doid_t assign_doid_reuse(DBClientBase *client) { BSONObj result; bool success = client->runCommand( m_db, BSON("findandmodify" << "astron.globals" << "query" << BSON( "_id" << "GLOBALS" << "doid.free.0" << BSON("$exists" << true) ) << "update" << BSON( "$pop" << BSON("doid.free" << -1) )), result); // If the findandmodify command failed, the document either doesn't // exist, or we ran out of reusable doids. if(!success || result["value"].isNull()) { m_log->error() << "Could not allocate a reused DOID!" << endl; return INVALID_DO_ID; } m_log->trace() << "assign_doid_reuse: got globals element: " << result << endl; // Otherwise, use the first one: doid_t doid; const BSONElement &element = result["value"]["doid"]["free"]; if(sizeof(doid) == sizeof(long long)) { doid = element.Array()[0].Long(); } else if(sizeof(doid) == sizeof(int)) { doid = element.Array()[0].Int(); } return doid; }
doid_t assign_doid_monotonic(DBClientBase *client) { BSONObj result; bool success = client->runCommand( m_db, BSON("findandmodify" << "astron.globals" << "query" << BSON( "_id" << "GLOBALS" << "doid.monotonic" << GTE << m_min_id << "doid.monotonic" << LTE << m_max_id ) << "update" << BSON( "$inc" << BSON("doid.monotonic" << 1) )), result); // If the findandmodify command failed, the document either doesn't // exist, or we ran out of monotonic doids. if(!success || result["value"].isNull()) { return INVALID_DO_ID; } m_log->trace() << "assign_doid_monotonic: got globals element: " << result << endl; doid_t doid; const BSONElement &element = result["value"]["doid"]["monotonic"]; if(sizeof(doid) == sizeof(long long)) { doid = element.Long(); } else if(sizeof(doid) == sizeof(int)) { doid = element.Int(); } return doid; }
void handle_delete(DBClientBase *client, DBOperation *operation) { BSONObj result; bool success; try { success = client->runCommand( m_db, BSON("findandmodify" << "astron.objects" << "query" << BSON( "_id" << operation->doid()) << "remove" << true), result); } catch(mongo::DBException &e) { m_log->error() << "Unexpected error while deleting " << operation->doid() << ": " << e.what() << endl; operation->on_failure(); return; } m_log->trace() << "handle_delete: got response: " << result << endl; // If the findandmodify command failed, there wasn't anything there // to delete in the first place. if(!success || result["value"].isNull()) { m_log->error() << "Tried to delete non-existent doid " << operation->doid() << endl; operation->on_failure(); return; } free_doid(client, operation->doid()); operation->on_complete(); }
void set_field(doid_t do_id, const Field* field, const val_t &value) { m_log->trace() << "Setting field on obj-" << do_id << endl; YAML::Node document; if(!load(do_id, document)) { return; } // Get the fields from the file that are not being updated const Class* dcc = g_dcf->get_class_by_name(document["class"].as<string>()); ObjectData dbo(dcc->get_id()); YAML::Node existing = document["fields"]; for(auto it = existing.begin(); it != existing.end(); ++it) { const Field* field = dcc->get_field_by_name(it->first.as<string>()); if(!field) { m_log->warning() << "Field '" << it->first.as<string>() << "', loaded from '" << filename(do_id) << "', does not exist." << endl; continue; } vector<uint8_t> value = read_yaml_field(field, it->second, do_id); if(value.size() > 0) { dbo.fields[field] = value; } } dbo.fields[field] = value; write_yaml_object(do_id, dcc, dbo); }
const Class* get_class(doid_t do_id) { m_log->trace() << "Getting dclass of obj-" << do_id << endl; // Open file for object YAML::Node document; if(!load(do_id, document)) { return NULL; } return g_dcf->get_class_by_name(document["class"].as<string>()); }
void handle_create(DBClientBase *client, DBOperation *operation) { // First, let's convert the requested object into BSON; this way, if // a failure happens, it happens before we waste a doid. BSONObjBuilder fields; try { for(auto it = operation->set_fields().begin(); it != operation->set_fields().end(); ++it) { DatagramPtr dg = Datagram::create(); dg->add_data(it->second); DatagramIterator dgi(dg); fields << it->first->get_name() << bamboo2bson(it->first->get_type(), dgi)["_"]; } } catch(mongo::DBException &e) { m_log->error() << "While formatting " << operation->dclass()->get_name() << " for insertion: " << e.what() << endl; operation->on_failure(); return; } doid_t doid = assign_doid(client); if(doid == INVALID_DO_ID) { // The error will already have been emitted at this point, so // all that's left for us to do is fail silently: operation->on_failure(); return; } BSONObj b = BSON("_id" << doid << "dclass" << operation->dclass()->get_name() << "fields" << fields.obj()); m_log->trace() << "Inserting new " << operation->dclass()->get_name() << "(" << doid << "): " << b << endl; try { client->insert(m_obj_collection, b); } catch(mongo::DBException &e) { m_log->error() << "Cannot insert new " << operation->dclass()->get_name() << "(" << doid << "): " << e.what() << endl; operation->on_failure(); return; } operation->on_complete(doid); }
// This returns a DOID to the free list: void free_doid(DBClientBase *client, doid_t doid) { m_log->trace() << "Returning doid " << doid << " to the free pool..." << endl; try { client->update( m_global_collection, BSON("_id" << "GLOBALS"), BSON("$push" << BSON("doid.free" << doid))); } catch(mongo::DBException &e) { m_log->error() << "Could not return doid " << doid << " to free pool: " << e.what() << endl; } }
bool set_field_if_equals(doid_t do_id, const Field* field, const val_t &equal, val_t &value) { m_log->trace() << "Setting field if equal on obj-" << do_id << endl; YAML::Node document; if(!load(do_id, document)) { value = vector<uint8_t>(); return false; } // Get current field values from the file const Class* dcc = g_dcf->get_class_by_name(document["class"].as<string>()); ObjectData dbo(dcc->get_id()); YAML::Node existing = document["fields"]; for(auto it = existing.begin(); it != existing.end(); ++it) { const Field* field = dcc->get_field_by_name(it->first.as<string>()); if(!field) { m_log->warning() << "Field '" << it->first.as<string>() << "', loaded from '" << filename(do_id) << "', does not exist." << endl; continue; } vector<uint8_t> value = read_yaml_field(field, it->second, do_id); if(value.size() > 0) { dbo.fields[field] = value; } } auto found = dbo.fields.find(field); if(found == dbo.fields.end() || found->second != equal) { value = dbo.fields[field]; return false; } dbo.fields[field] = value; write_yaml_object(do_id, dcc, dbo); return true; }
// Get a DBObjectSnapshot from a MongoDB BSON object; returns NULL if failure. DBObjectSnapshot *format_snapshot(doid_t doid, const BSONObj &obj) { m_log->trace() << "Formatting database snapshot of " << doid << ": " << obj << endl; try { string dclass_name = obj["dclass"].String(); const dclass::Class *dclass = g_dcf->get_class_by_name(dclass_name); if(!dclass) { m_log->error() << "Encountered unknown database object: " << dclass_name << "(" << doid << ")" << endl; return NULL; } BSONObj fields = obj["fields"].Obj(); DBObjectSnapshot *snap = new DBObjectSnapshot(); snap->m_dclass = dclass; for(auto it = fields.begin(); it.more(); ++it) { const char *name = (*it).fieldName(); const dclass::Field *field = dclass->get_field_by_name(name); if(!field) { m_log->warning() << "Encountered unexpected field " << name << " while formatting " << dclass_name << "(" << doid << "); ignored." << endl; continue; } { DatagramPtr dg = Datagram::create(); bson2bamboo(field->get_type(), *it, *dg); snap->m_fields[field].resize(dg->size()); memcpy(snap->m_fields[field].data(), dg->get_data(), dg->size()); } } return snap; } catch(mongo::DBException &e) { m_log->error() << "Unexpected error while trying to format" " database snapshot for " << doid << ": " << e.what() << endl; return NULL; } }
bool get_object(doid_t do_id, ObjectData &dbo) { m_log->trace() << "Getting obj-" << do_id << " ..." << endl; // Open file for object YAML::Node document; if(!load(do_id, document)) { return false; } // Read object's DistributedClass const Class* dcc = g_dcf->get_class_by_name(document["class"].as<string>()); dbo.dc_id = dcc->get_id(); // Read object's fields YAML::Node fields = document["fields"]; for(auto it = fields.begin(); it != fields.end(); ++it) { const Field* field = dcc->get_field_by_name(it->first.as<string>()); if(!field) { m_log->warning() << "Field '" << it->first.as<string>() << "', loaded from '" << filename(do_id) << "', does not exist." << endl; continue; } vector<uint8_t> value = read_yaml_field(field, it->second, do_id); if(value.size() > 0) { dbo.fields[field] = value; } } return true; }
void handle_modify(DBClientBase *client, DBOperation *operation) { // First, we have to format our findandmodify. BSONObjBuilder sets; bool has_sets = false; BSONObjBuilder unsets; bool has_unsets = false; for(auto it = operation->set_fields().begin(); it != operation->set_fields().end(); ++it) { stringstream fieldname; fieldname << "fields." << it->first->get_name(); if(it->second.empty()) { unsets << fieldname.str() << true; has_unsets = true; } else { DatagramPtr dg = Datagram::create(); dg->add_data(it->second); DatagramIterator dgi(dg); sets << fieldname.str() << bamboo2bson(it->first->get_type(), dgi)["_"]; has_sets = true; } } BSONObjBuilder updates_b; if(has_sets) { updates_b << "$set" << sets.obj(); } if(has_unsets) { updates_b << "$unset" << unsets.obj(); } BSONObj updates = updates_b.obj(); // Also format any criteria for the change: BSONObjBuilder query_b; query_b << "_id" << operation->doid(); for(auto it = operation->criteria_fields().begin(); it != operation->criteria_fields().end(); ++it) { stringstream fieldname; fieldname << "fields." << it->first->get_name(); if(it->second.empty()) { query_b << fieldname.str() << BSON("$exists" << false); } else { DatagramPtr dg = Datagram::create(); dg->add_data(it->second); DatagramIterator dgi(dg); query_b << fieldname.str() << bamboo2bson(it->first->get_type(), dgi)["_"]; } } BSONObj query = query_b.obj(); m_log->trace() << "Performing updates to " << operation->doid() << ": " << updates << endl; m_log->trace() << "Query is: " << query << endl; BSONObj result; bool success; try { success = client->runCommand( m_db, BSON("findandmodify" << "astron.objects" << "query" << query << "update" << updates), result); } catch(mongo::DBException &e) { m_log->error() << "Unexpected error while modifying " << operation->doid() << ": " << e.what() << endl; operation->on_failure(); return; } m_log->trace() << "Update result: " << result << endl; BSONObj obj; if(!success || result["value"].isNull()) { // Okay, something didn't work right. If we had criteria, let's // try to fetch the object without the criteria to see if it's a // criteria mismatch or a missing DOID. if(!operation->criteria_fields().empty()) { try { obj = client->findOne(m_obj_collection, BSON("_id" << operation->doid())); } catch(mongo::DBException &e) { m_log->error() << "Unexpected error while modifying " << operation->doid() << ": " << e.what() << endl; operation->on_failure(); return; } if(!obj.isEmpty()) { // There's the problem. Now we can send back a snapshot: DBObjectSnapshot *snap = format_snapshot(operation->doid(), obj); if(snap && operation->verify_class(snap->m_dclass)) { operation->on_criteria_mismatch(snap); return; } else { // Something else weird happened with our snapshot; // either the class wasn't recognized or it was the // wrong class. Either way, an error has been logged, // and we need to fail the operation. operation->on_failure(); return; } } } // Nope, not that. We're missing the DOID. m_log->error() << "Attempted to modify unknown DOID: " << operation->doid() << endl; operation->on_failure(); return; } // If we've gotten to this point: Hooray! The change has gone // through to the database. // Let's, however, double-check our changes. (Specifically, we should // run verify_class so that we know the frontend is happy with what // kind of object we just modified.) obj = result["value"].Obj(); try { string dclass_name = obj["dclass"].String(); const dclass::Class *dclass = g_dcf->get_class_by_name(dclass_name); if(!dclass) { m_log->error() << "Encountered unknown database object: " << dclass_name << "(" << operation->doid() << ")" << endl; } else if(operation->verify_class(dclass)) { // Yep, it all checks out. Complete the operation: operation->on_complete(); return; } } catch(mongo::DBException &e) { } // If we've gotten here, something is seriously wrong. We've just // mucked with an object without knowing the consequences! What have // we done?! We've created an abomination in the database! Kill it! // Kill it with fire! // All we really can do to mitigate this is scream at the user (which // the above verification has already done by now) and revert the // object back to how it was when we found it. // NOTE: This DOES have the potential for data loss, because we're // wiping out any changes that conceivably could have happened // between the findandmodify and now. In dev environments, (which we // are probably in right now, if other components are making // outlandish requests like this) this shouldn't be a huge issue. m_log->trace() << "Reverting changes made to " << operation->doid() << endl; try { client->update( m_obj_collection, BSON("_id" << operation->doid()), obj); } catch(mongo::DBException &e) { // Wow, we REALLY fail at life. m_log->error() << "Could not revert corrupting changes to " << operation->doid() << ": " << e.what() << endl; } operation->on_failure(); }
bool set_fields_if_equals(doid_t do_id, const map_t &equals, map_t &values) { m_log->trace() << "Setting fields if equals on obj-" << do_id << endl; YAML::Node document; if(!load(do_id, document)) { values.clear(); return false; } // Get current field values from the file const Class* dcc = g_dcf->get_class_by_name(document["class"].as<string>()); ObjectData dbo(dcc->get_id()); YAML::Node existing = document["fields"]; for(auto it = existing.begin(); it != existing.end(); ++it) { const Field* field = dcc->get_field_by_name(it->first.as<string>()); if(!field) { m_log->warning() << "Field '" << it->first.as<string>() << "', loaded from '" << filename(do_id) << "', does not exist." << endl; continue; } vector<uint8_t> value = read_yaml_field(field, it->second, do_id); if(value.size() > 0) { dbo.fields[field] = value; } } // Check if equals matches current values bool fail = false; for(auto it = equals.begin(); it != equals.end(); ++it) { auto found = dbo.fields.find(it->first); if(found == dbo.fields.end()) { values.erase(it->first); fail = true; } else if(it->second != found->second) { values.erase(it->first); fail = true; } } // Return current values on failure if(fail) { for(auto it = values.begin(); it != values.end(); ++it) { it->second = dbo.fields[it->first]; } return false; } // Update existing values on success for(auto it = values.begin(); it != values.end(); ++it) { dbo.fields[it->first] = it->second; } write_yaml_object(do_id, dcc, dbo); return true; }