Beispiel #1
0
		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;
		}
Beispiel #2
0
		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;
		}
Beispiel #3
0
    // 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;
    }
Beispiel #4
0
    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;
    }
Beispiel #5
0
    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();
    }
Beispiel #6
0
		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);
		}
Beispiel #7
0
		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>());
		}
Beispiel #8
0
    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);
    }
Beispiel #9
0
    // 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;
        }
    }
Beispiel #10
0
		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;
		}
Beispiel #11
0
    // 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;
        }
    }
Beispiel #12
0
		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;
		}
Beispiel #13
0
    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();
    }
Beispiel #14
0
		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;
		}