Exemple #1
0
def step_impl(context):
    """

    :type context behave.runner.Context

    """
    try:
        SchemaTools.check_schema_fields(context.schema_file, "test file")
    except Exception as e:
        if str(e) == 'MongoBackend.load_schemas_from_directory: ' \
                     'The "namespace" field is not in the schema-"test file"':
            ok_(True)
        else:
            ok_(False)
Exemple #2
0
    def __init__(self, _database, _json_schema_folders=None, _uri_handlers = None, _schema_tools = None):
        """
        Initialize a database access object
        :param _database: A MongoDB client object
        :param _json_schema_folders: A list of application specific JSON schema folders

        """

        self.database = _database

        self.logging = Logging(_database=self.database)

        # Database access is dependent on schema tools for validation.
        if _schema_tools:
            self.schema_tools = _schema_tools
        else:
            self.schema_tools = SchemaTools(_json_schema_folders = _json_schema_folders, _uri_handlers=_uri_handlers)
Exemple #3
0
class DatabaseAccess():
    """
        The database access class handles all communication with the database
        It provides logging, validation against a schema and hiding ObjectIds.
    """

    database = None
    schema_tools = None
    logging = None

    def __init__(self, _database, _json_schema_folders=None, _uri_handlers = None, _schema_tools = None):
        """
        Initialize a database access object
        :param _database: A MongoDB client object
        :param _json_schema_folders: A list of application specific JSON schema folders

        """

        self.database = _database

        self.logging = Logging(_database=self.database)

        # Database access is dependent on schema tools for validation.
        if _schema_tools:
            self.schema_tools = _schema_tools
        else:
            self.schema_tools = SchemaTools(_json_schema_folders = _json_schema_folders, _uri_handlers=_uri_handlers)

    def verify_document(self, _data, _collection_name, _caller_name):
        """
        Check so that the collection for the specified schema in the input data match the supplied collection in
        _collection_name.

        :param _data: The data to check.
        :param _collection_name: Name of the collection that it should be checked against
        :param _caller_name: The name of the calling function for logging


        """

        if "schemaRef" in _data:
            try:
                _curr_schema = self.schema_tools.json_schema_objects[_data["schemaRef"]]
            except:
                raise Exception(
                    _caller_name + ": Error - invalid data structure, schemaRef not found: " + _data["schemaRef"])

            if _curr_schema["collection"] != _collection_name:
                raise Exception(
                    _caller_name + ": Invalid schemaRef: \"" + _curr_schema["collection"] +"\". " + _caller_name + " is restricted to the \"" +
                    _collection_name + "\"-collection.")
        else:
            # An MBE document must *always* have a schemaRef property
            raise Exception(_caller_name + ": Missing schemaRef.")

    @staticmethod
    def verify_condition(_data, _collection_name, _caller_name):
        """
        Validates the conditions' structure and the collection it queries.

        :param _data: The data to check.
        :param _collection_name: Name of the collection that it should be checked against
        :param _caller_name: The name of the calling function for logging

        """

        if "conditions" not in _data:
            raise Exception(_caller_name + ": Error - Not a valid condition, \"conditions\"-property missing")

        if "collection" in _data and _data["collection"] != _collection_name:
            raise Exception(_caller_name + ": Error - collection omitted or wrong, only queries against"
                                           " the " + _collection_name + " collection is permitted.")

    def manage_input(self, _input, _schema_ref=None):
        """
        Validate, convert _id to objectId instances and parse collection from input, whether it is data to save or a\
        condition.
        Note: This should not be run twice on the same data.

        :param _input: Data to handle
        :param _schema_ref: If set the schema to validate against
        :return: A tuple with the data and the database collection specified in the schema.

        """
        try:
            # Apply(if _schema_ref is set, validate against that schema instead)
            _input, _schema_obj = self.schema_tools.apply(_input, _schema_ref)
        except ValidationError as e:
            raise ValidationError("handle_input: Validation error:" + str(e) + ". Data: \n" + str(_input))
        except Exception as e:
            raise Exception(
                "handle_input: Non-validation error from validation: Type: " + e.__class__.__name__ + " Message:" + str(
                    e) + ". Data: \n" + str(_input))

        # In a condition, the input data is under the conditions-field
        if _schema_ref == "ref://of.conditions":
            _data = copy.deepcopy(_input["conditions"])
            _collection = _input["collection"]
        else:
            _collection = _schema_obj["collection"]
            _data = copy.deepcopy(_input)

        return _data, self.database[_collection]

    @staticmethod
    def load_exactly_one_document(_collection, _id, _zero_error=None, _duplicate_error=None):
        """
        Raises provided errors if not exactly one row in result.

        :param _collection: The collection to load from
        :param _id: The _id of the document
        :param _zero_error: Error to report if no documents are found
        :param _duplicate_error: Error to report when duplicate documents are found
        :return: The document
        """

        _document_cursor = _collection.find({"_id": _id})
        if _document_cursor.count() == 0:
            if _zero_error is not None:
                raise Exception(_zero_error + "_id: " + str(
                    _id) + " could not be found.")
            else:
                return None
        elif _document_cursor.count() > 1:
            if _duplicate_error is not None:
                raise Exception(
                    "Access.save: Tried saving existing, but found duplicate documents with(try to remove either) _id: "
                    + str(_id) + ".")
            else:
                return None
        elif _document_cursor.count() == 1:
            return _document_cursor[0]

    def save(self, _document, _user, _old_document=None, _allow_save_id=False):
        """
        Save a document to a collection

        :param _document: The data to save
        :param _user: A user object
        :param _old_document: If available, the existing data, used to avoid an extra read when logging changes.
        :param _allow_save_id: If set, to not assume that _id being set means updating an existing document.
        :return: The object id of the saved document

        """
        if _user is not None:
            _user_id = _user["_id"]
        else:
            _user_id = None

        _document, _collection = self.manage_input(_document)

        if _old_document is None and "_id" in _document:
            # Load the existing data
            if not _allow_save_id:
                _zero_error = "Access.save: Tried to save data over existing but didn't find an existing node."
            else:
                _zero_error = None

            _old_document = self.load_exactly_one_document(_collection, _document["_id"], _zero_error=_zero_error,
                                                           _duplicate_error="Access.save: Tried to save data over "
                                                                            "existing but found duplicate nodes")
            if (_old_document is not None) and (str(_old_document["schemaRef"]) != str(_document["schemaRef"])):
                raise Exception(
                    "Access.save: Cannot change schema of an existing node, remove and add. _id: " + str(
                        _document["_id"]) + ", new:" + str(_old_document["schemaRef"]) + ", old:" + str(
                        _old_document["schemaRef"]))

        if _old_document is not None:
            _old_document, _dummy_collection = self.manage_input(_old_document)

        _result = str(_collection.save(_document))
        if _collection.name != "log":
            self.logging.log_save(_document, _user_id, _old_document)
        return _result

    def remove_documents(self, _documents, _user, _collection_name=None):
        """
        Remove the documents in the documents list

        :param _documents: The list of documents
        :param _user: A user object
        :param _collection_name: The collection from where to remove them.
        :return: Nothing
        """
        if _user is not None:
            _user_id = _user["_id"]
        else:
            _user_id = None
        _result = []
        # Loop through documents and remove them
        _collection = self.database[_collection_name]
        for _document in _documents:
            # noinspection PyUnusedLocal
            _result.append(_collection.remove(_document))

            self.logging.log_remove(_document, _user_id)

        return _result

    def remove_condition(self, _condition, _user):
        """
        Remove documents that match the supplied MBE condition

        :param _condition: A MongoDB search criteria
        :return:
        """
        if _user is not None:
            _user_id = _user["_id"]
        else:
            _user_id = None
        _raw_condition, _collection = self.manage_input(_condition, "ref://of.conditions")
        _removed_documents_cursor = _collection.find(_raw_condition)

        _documents = [x for x in _removed_documents_cursor]
        _result = []
        for _document in _documents:
            # noinspection PyUnusedLocal
            _result.append(_collection.remove(_raw_condition))
            self.logging.log_remove(_document, _user_id)

        return _result

    def find(self, _conditions, _do_not_fix_object_ids=False):
        """
        Return a list of documents that match the supplied MBE condition.
        :param _conditions: An MBE condition
        :return: A list of matching documents
        """

        def _recurse_object_ids(_data):

            if isinstance(_data, list):
                _destination = []
                for _curr_row in _data:
                    _destination.append(_recurse_object_ids(_curr_row))

                return _destination
            elif isinstance(_data, dict):
                _destination = {}
                for _curr_key, _curr_value in _data.items():
                    _destination[_curr_key] = _recurse_object_ids(_curr_value)
                return _destination

            elif isinstance(_data, ObjectId):
                return str(_data)
            else:
                return _data

        _raw_conditions, _collection = self.manage_input(_conditions, "ref://of.conditions")
        _result = list(_collection.find(_raw_conditions))
        if not _do_not_fix_object_ids:
            return _recurse_object_ids(_result)
        else:
            return _result

    # noinspection PyMethodMayBeStatic
    def transform_collection(self, _destination_schema, _map, _row_callback):
        """
        NOT IMPLEMENTED.
        :param _destination_schema:
        :param _map:
        :param _row_callback:
        :return:
        """

        pass
        # Iterate all documents

        # Copy _id to new document
        # Copy all data using map and field id:s to document
        # If set, use callback to finish what has to be manually done


        # Write cumulative log entry using map changes and results from _row_callback
Exemple #4
0
def start_agent(_cfg_filename = None):
    """
    Starts the agent; Loads settings, connects to database, registers process and starts the web server.
    """

    global process_id, _control_monitor, _terminated, _address, _process_queue_manager, _broker_url, \
        _username, _password, _peers, _log_to_database_severity, _verify_SSL


    _process_id = str(ObjectId())
    of.common.logging.callback = log_locally
    _terminated = False

    # Handle multiprocessing on windows
    freeze_support()

    write_srvc_dbg("=====start_agent===============================")
    try:

        if _cfg_filename is None:
            _cfg_filename = resolve_config_path()

        _settings = JSONXPath(_cfg_filename)

    except Exception as e:
        write_to_log("Error loading settings: " + str(e), _category=EC_SERVICE, _severity=SEV_FATAL,
                     _process_id=_process_id)
        return

    of.common.logging.severity = of.common.logging.severity_identifiers.index(
        _settings.get("agent/logging/severityLevel", _default="warning"))

    _log_to_database_severity = of.common.logging.severity_identifiers.index(
        _settings.get("agent/logging/brokerLevel", _default="warning"))

    write_srvc_dbg("===register signal handlers===")
    register_signals(stop_agent)

    # An _address is completely necessary.
    _address = _settings.get("agent/address", _default=None)
    if not _address or _address == "":
        raise Exception(write_to_log(
            "Fatal error: Agent cannot start, missing [agent] _address setting in configuration file.",
            _category=EC_SERVICE, _severity=SEV_FATAL))
    # An _address is completely necessary.
    _verify_SSL = _settings.get("agent/verifySSL", _default=True)
    # Gather credentials
    _broker_url = _settings.get("agent/brokerUrl", _default="127.0.0.1:8080")
    _username = _settings.get("agent/username")
    if not _username:
        raise Exception(write_to_log("Username must be configured", _category=EC_SERVICE, _severity=SEV_FATAL))


    _password = _settings.get("agent/password")
    if not _password:
        raise Exception(write_to_log("Password must be configured", _category=EC_SERVICE, _severity=SEV_FATAL))
    _retries = int(_settings.get("agent/connectionRetries", 5))

    # Register at the broker
    if not register_agent(_retries):
        raise Exception(write_to_log("Fatal: The agent failed to register with the broker, tried " + str(
            _retries + 1) + " time(s), quitting.", _category=EC_SERVICE, _severity=SEV_FATAL))
        os._exit(1)

    of.common.logging.callback = log_to_database

    _repository_base_folder = _settings.get_path("agent/repositoryFolder",
                                            _default=os.path.join(os.path.dirname(__file__), "repositories"))

    write_srvc_dbg("Load schema tool")

    try:
        # Initiate a schema tools instance for validation other purposes.
        _schema_tools = SchemaTools(_json_schema_folders=[of_schema_folder(),
                                                          os.path.abspath(os.path.join(script_dir, "..", "schemas", "namespaces"))
                                                          ],
                                    _uri_handlers={"ref": None})
    except Exception as e:
        raise Exception(write_to_log("An error occurred while loading schema tools:" + str(e),
                                     _category=EC_SERVICE, _severity=SEV_FATAL))
        os._exit(1)
        return

    write_srvc_dbg("Load schema tool done")

    try:



        write_srvc_dbg("Initializing monitors")

        # Init the monitor for incoming messages
        _message_monitor = Monitor(
            _handler=AgentWebSocketHandler(_process_id=_process_id,
                                           _peers=_peers,
                                           _schema_tools=_schema_tools,
                                           _address=_address,
                                           _broker_address="broker"))

        # The manager for the process queue
        _process_queue_manager = multiprocessing.Manager()

        # Init the monitor for the worker queue
        _worker_monitor = Monitor(
            _handler=WorkerSupervisor(_process_id=_process_id,
                                      _message_monitor=_message_monitor,
                                      _repo_base_folder=_repository_base_folder,
                                      _severity=of.common.logging.severity),
            _queue=_process_queue_manager.Queue())

        # Init the monitor for the agent queue
        _control_monitor = Monitor(
            _handler=ControlHandler(_process_id=_process_id,
                                    _message_monitor=_message_monitor,
                                    _worker_monitor=_worker_monitor,
                                    _stop_agent=stop_agent
                                    ))

        # The global variable for handling websockets. TODO: Could this be done without globals? (PROD-33)

        of.common.messaging.websocket.monitor = _message_monitor
        write_srvc_dbg("Initializing monitors done")

    except Exception as e:
        raise Exception(write_to_log("Fatal: An error occurred while initiating the monitors and handlers:" + str(e),
                                         _category=EC_SERVICE, _severity=SEV_FATAL))
        os._exit(1)

    # Try to connect to websocket, quit on failure
    if not connect_to_websocket():
        os._exit(1)

    write_srvc_dbg("Register agent system process")
    _control_monitor.handler.message_monitor.queue.put(
        [None, store_process_system_document(_process_id=_process_id,
                                             _name="Agent instance(" + _address + ")")])
    write_srvc_dbg("Log agent system state")
    _control_monitor.handler.message_monitor.queue.put([None,
                                                        log_process_state_message(_changed_by=zero_object_id,
                                                                                  _state="running",
                                                                                  _process_id=_process_id,
                                                                                  _reason="Agent starting up at " +
                                                                                          _address)])


    # Security check to remind broker if it is unsecured

    if not _verify_SSL:
        try:
            call_api("https://"+ _broker_url + "/status", _data={}, _session_id= _session_id, _verify_SSL=True)
        except SSLError as e:
                write_to_log("There is a problem with the security certificate:\n" + str(e) + "\n"
                             "This is a security risk, and and important thing to _address.",
                             _category=EC_NOTIFICATION, _severity=SEV_WARNING)
        except Exception as e:
            write_to_log("An error occured while checking status of broker and SSL certificate:" + str(e),
                             _category=EC_NOTIFICATION, _severity=SEV_ERROR)

    write_srvc_dbg("Agent up and running.")

    while not _terminated:
        time.sleep(0.1)

    write_srvc_dbg("Exiting main thread")