Exemple #1
0
class ScenarioManager:
    """ Manage scenarios : create them, evaluate them, etc ...
        A scenario instance contains a condition, which is a boolean
        combination of many tests,
        and a list of actions
        Each test can be :
         - test on the test of any device
         - test on the time
         - action triggered by user (click on UI for ex)
        The test on devices are managed directly by xpl Listeners
        The test on time will be managed by a TimeManager
        The actions will be managed by an ActionManager
        {
         "condition" :
            { "AND" : {
                    "OR" : {
                        "one-uuid" : {
                            "param_name_1" : {
                                "token1" : "value",
                                "token2" : "othervalue"
                            },
                            "param_name_2" : {
                                "token3" : "foo"
                            }
                        },
                        "another-uuid" : {
                            "param_name_1" : {
                                "token4" : "bar"
                            }
                        }
                    },
                    "yet-another-uuid" : {
                        "param_name_1" : {
                            "url" : "http://google.fr",
                            "interval" : "5"
                        }
                    }
                }
            },
         "actions" : [
            "uid-for-action" : {
                "param1" : "value1",
                "param2" : "value2"
            },
            "uid-for-action2" : {
                "param3" : "value3"
            }
         ]
        }
    """

    def __init__(self, log):
        """ Create ScenarioManager instance
            @param log : Logger instance
        """
        # Keep list of conditions as name : instance
        self._instances = {}
        # an instance of the logger
        self.log = log
        # load all scenarios from the DB
        self._db = DbHelper()
        self.load_scenarios()

    def load_scenarios(self):
        """ Loads all scenarios from the db
        for each scenario call the create_scenario method
        """
        try:
            with self._db.session_scope():
                ### TEST if database is up
                # TODO : move in a function and use it (also used in dbmgr)
                nb_test = 0
                db_ok = False
                while not db_ok and nb_test < DATABASE_CONNECTION_NUM_TRY:
                    nb_test += 1
                    try:
                        self._db.list_user_accounts()
                        db_ok = True
                    except:
                        msg = "The database is not responding. Check your configuration of if the database is up. Test {0}/{1}. The error while trying to connect to the database is : {2}".format(nb_test, DATABASE_CONNECTION_NUM_TRY, traceback.format_exc())
                        self.log.error(msg)
                        msg = "Waiting for {0} seconds".format(DATABASE_CONNECTION_WAIT)
                        self.log.info(msg)
                        time.sleep(DATABASE_CONNECTION_WAIT)

                if nb_test >= DATABASE_CONNECTION_NUM_TRY:
                    msg = "Exiting dbmgr!"
                    self.log.error(msg)
                    self.force_leave()
                    return

                ### Do the stuff
                msg = "Connected to the database"
                self.log.info(msg)
                for scenario in self._db.list_scenario():
                    self.create_scenario(scenario.name, scenario.json, int(scenario.id), scenario.disabled, scenario.description, scenario.state)
        except:
            self.log.error(u"Error while loading the scenarios! The error is : {0}".format(traceback.format_exc()))

    def shutdown(self):
        """ Callback to shut down all parameters
        """
        for cond in self._conditions.keys():
            self.delete_scenario(cond, db_delete=False)

    def get_parsed_condition(self, name):
        """ Call cond.get_parsed_condition on the cond with name 'name'
        @param name : name of the Condition
        @return {'name':name, 'data': parsed_condition} or raise Exception
        """
        if name not in self._conditions:
            raise KeyError('no key {0} in conditions table'.format(name))
        else:
            parsed = self._conditions[name].get_parsed_condition()
            return {'name': name, 'data': parsed}

    def update_scenario(self, cid, name, json_input, dis, desc):
        cid = int(cid)
        # TODO get the current state and store it
        state = True
        if cid != 0:
            self.del_scenario(cid, False)
        return self.create_scenario(name, json_input, cid, dis, desc, state, True)

    def del_scenario(self, cid, doDB=True):
        try:
            cid = int(cid)
            if cid == 0 or cid not in self._instances.keys():
                self.log.info(u"Scenario deletion : id '{0}' doesn't exist".format(cid))
                return {'status': 'ERROR', 'msg': u"Scenario {0} doesn't exist".format(cid)}
            else:
                self._instances[cid]['instance'].destroy()
                del(self._instances[cid])
                if doDB:
                    with self._db.session_scope():
                        self._db.del_scenario(cid)
                self.log.info(u"Scenario {0} deleted".format(cid))
        except:
            msg = u"Error while deleting the scenario id='{0}'. Error is : {1}".format(cid, traceback.format_exc())
            self.log.error(msg)
            return {'status': 'ERROR', 'msg': msg}

    def create_scenario(self, name, json_input, cid=0, dis=False, desc=None, state=False, update=False):
        """ Create a Scenario from the provided json.
        @param name : A name for the condition instance
        @param json_input : JSON representation of the condition
        The JSON will be parsed to get all the uuids, and test instances will be created.
        The json needs to have 2 keys:
            - condition => the json that will be used to create the condition instance
            - actions => the json that will be used for creating the actions instances
        @Return {'name': name} or raise exception
        """
        ocid = cid
        try:
            self.log.info(u"Create or save scenario : name = '{1}', id = '{1}', json = '{2}'".format(name, cid, json_input))
            payload = json.loads(json_input)  # quick test to check if json is valid
        except Exception as e:
            self.log.error(u"Creation of a scenario failed, invallid json: {0}".format(json_input))
            self.log.error(u"Error is : {0}".format(tracebeck.format_exc()))
            return {'status': 'ERROR', 'msg': 'invallid json'}

        #if 'IF' not in payload.keys():
        #        or 'DO' not in payload.keys():
        #    msg = u"the json for the scenario does not contain condition or actions for scenario {0}".format(name)
        #    self.log.error(msg)
        #    return {'status': 'ERROR', 'msg': msg}
        # db storage
        if int(ocid) == 0:
            with self._db.session_scope():
                scen = self._db.add_scenario(name, json_input, dis, desc, False)
                cid = scen.id
        elif update:
            with self._db.session_scope():
                self._db.update_scenario(cid, name, json_input, dis, desc)

        # create the condition itself
        try:
            scen = ScenarioInstance(self.log, cid, name, payload, dis, state, self._db)
            self._instances[cid] = {'name': name, 'json': payload, 'instance': scen, 'disabled': dis }
            self.log.debug(u"Create scenario instance {0} with payload {1}".format(name, payload))
            self._instances[cid]['instance'].eval_condition()
        except Exception as e:
            if int(ocid) == 0:
                with self._db.session_scope():
                    self._db.del_scenario(cid)
            self.log.error(u"Creation of a scenario failed. Error is : {0}".format(traceback.format_exc()))
            return {'status': 'ERROR', 'msg': 'Creation of scenario failed'}
        # return
        return {'name': name, 'status': 'OK', 'cid': cid}

    def eval_condition(self, name):
        """ Evaluate a condition calling eval_condition from Condition instance
        @param name : The name of the condition instance
        @return {'name':name, 'result': evaluation result} or raise Exception
        """
        if name not in self._conditions:
            raise KeyError('no key {0} in conditions table'.format(name))
        else:
            res = self._conditions[name].eval_condition()
            return {'name': name, 'result': res}

    def list_actions(self):
        """ Return the list of actions
        @return a hash of hashes for the different actions
        { "module1.Action1" : {
            "description" : "some description of the action",
            "parameters" : { "param1" : {
                ... see get_expected_entries for details
            }
        }
        """

        self.log.debug("ScenarioManager : list actions")
        res = {}
        actions = self.__return_list_of_classes(s_a)
        for name, cls in actions:
            if 'abstract' not in name.lower():
                self.log.debug("- {0}".format(name))
                inst = cls()
                res[name] = {"parameters": inst.get_expected_entries(),
                             "description": inst.get_description()}
        return res

    def list_tests(self):
        """ Return the list of tests
        @return a hash of hashes for the different tests
        { "module1.Test1" : {
            "description" : "some description of the test",
            "parameters" : { "param1" : {
                ... see list_parameters doc for detail on this part
            }
        }
        """

        self.log.debug("ScenarioManager : list tests")
        res = {}
        tests = self.__return_list_of_classes(s_t)

        for name, cls in tests:
            if 'abstract' not in name.lower():
                self.log.debug("- {0}".format(name))
                inst = cls(log = self.log)

                params = []
                for p, i in inst.get_parameters().items():
                    for param, info in i['expected'].items():
                        params.append({
                                "name": "{0}.{1}".format(p, param),
                                "description": info['description'],
                                "type": info['type'],
                                "values": info['values'],
                                "filters": info['filters'],
                            })

                res[name] = {"parameters": params,
                             "blockly": inst.get_blockly(),
                             "description": inst.get_description()}
        return res

    def list_conditions(self):
        """ Return the list of conditions as JSON
        """
        ret = []
        for cid, inst in self._instances.items():
            ret.append({'cid': cid, 'name': inst['name'], 'json': inst['json'], 'disabled': inst['disabled']})
        return ret

    def enable_scenario(self, cid):
        try:
            if cid == '' or int(cid) not in self._instances.keys():
                self.log.info(u"Scenario enable : id '{0}' doesn't exist".format(cid))
                return {'status': 'ERROR', 'msg': u"Scenario {0} doesn't exist".format(cid)}
            else:
                if self._instances[int(cid)]['instance'].enable():
                    self._instances[int(cid)]['disabled'] = False
                    with self._db.session_scope():
                        self._db.update_scenario(cid, disabled=False)
                    self.log.info(u"Scenario {0} enabled".format(cid))
                    return {'status': 'OK', 'msg': u"Scenario {0} enabled".format(cid)}
                else:
                    self.log.info(u"Scenario {0} already enabled".format(cid))
                    return {'status': 'ERROR', 'msg': u"Scenario {0} already enabled".format(cid)}
        except:
            msg = u"Error while enabling the scenario id='{0}'. Error is : {1}".format(cid, traceback.format_exc())
            self.log.error(msg)
            return {'status': 'ERROR', 'msg': msg}

    def disable_scenario(self, cid):
        try:
            if cid == '' or int(cid) not in self._instances.keys():
                self.log.info(u"Scenario disable : id '{0}' doesn't exist".format(cid))
                return {'status': 'ERROR', 'msg': u"Scenario {0} doesn't exist".format(cid)}
            else:
                if self._instances[int(cid)]['instance'].disable():
                    self._instances[int(cid)]['disabled'] = True
                    with self._db.session_scope():
                        self._db.update_scenario(cid, disabled=True)
                    self.log.info(u"Scenario {0} disabled".format(cid))
                    return {'status': 'OK', 'msg': u"Scenario {0} disabled".format(cid)}
                else:
                    self.log.info(u"Scenario {0} already disabled".format(cid))
                    return {'status': 'ERROR', 'msg': u"Scenario {0} already disabled".format(cid)}
        except:
            msg = u"Error while disabling the scenario id='{0}'. Error is : {1}".format(cid, traceback.format_exc())
            self.log.error(msg)
            return {'status': 'ERROR', 'msg': msg}

    def test_scenario(self, cid):
        try:
            if cid == '' or int(cid) not in self._instances.keys():
                self.log.info(u"Scenario test : id '{0}' doesn't exist".format(cid))
                return {'status': 'ERROR', 'msg': u"Scenario {0} doesn't exist".format(cid)}
            else:
                self._instances[int(cid)]['instance'].test_actions()
                self.log.info(u"Scenario {0} actions called".format(cid))
                return {'status': 'OK', 'msg': u"Scenario {0} actions called".format(cid)}
        except:
            msg = u"Error while calling actions for scenario id='{0}'. Error is : {1}".format(cid, traceback.format_exc())
            self.log.error(msg)
            return {'status': 'ERROR', 'msg': msg}

    def __return_list_of_classes(self, package):
        """ Return the list of module/classes in a package
        @param package : a reference to the package that need to be explored
        @return a list of tuple ('modulename.Classname', <instance of class>)
        """
        self.log.debug("Get list of classes for package : {0}".format(package))
        res = []
        mods = pkgutil.iter_modules(package.__path__)
        for module in mods:
            self.log.debug("- {0}".format(module))
            imported_mod = importlib.import_module('.' + module[1], package.__name__)
            #get the list of classes in the module
            classes = [m for m in inspect.getmembers(imported_mod) if inspect.isclass(m[1])]
            # Filter in order to keep only the classes that belong to domogik package and are not abstract
            res.extend([(module[1] + "." + c[0], c[1]) for c in filter(
                lambda x: x[1].__module__.startswith("domogik.scenario.") and not x[0].startswith("Abstract"), classes)])
        return res
Exemple #2
0
class ScenarioManager:
    """ Manage scenarios : create them, evaluate them, etc ...
        A scenario instance contains a condition, which is a boolean
        combination of many tests,
        and a list of actions
        Each test can be :
         - test on the test of any device
         - test on the time
         - action triggered by user (click on UI for ex)
        The test on devices are managed directly by xpl Listeners
        The test on time will be managed by a TimeManager
        The actions will be managed by an ActionManager
        { 
         "condition" :
            { "AND" : {
                    "OR" : {
                        "one-uuid" : {
                            "param_name_1" : {
                                "token1" : "value",
                                "token2" : "othervalue"
                            },
                            "param_name_2" : {
                                "token3" : "foo"
                            }
                        },
                        "another-uuid" : {
                            "param_name_1" : {
                                "token4" : "bar"
                            }
                        }
                    },
                    "yet-another-uuid" : {
                        "param_name_1" : {
                            "url" : "http://google.fr",
                            "interval" : "5"
                        }
                    }
                }
            },
         "actions" : [
            "uid-for-action" : {
                "param1" : "value1",
                "param2" : "value2"
            },
            "uid-for-action2" : {
                "param3" : "value3"
            }
         ]
        }
    """

    def __init__(self, log):
        """ Create ScenarioManager instance
            @param log : Logger instance
        """
        # Keep list of conditions as name : instance
        self._instances = {}
        # an instance of the logger
        self.log = log
        # load all scenarios from the DB
        self._db = DbHelper()
        self.load_scenarios()

    def load_scenarios(self):
        """ Loads all scenarios from the db
        for each scenario call the create_scenario method
        """
        with self._db.session_scope():
            for scenario in self._db.list_scenario():
                self.create_scenario(scenario.name, scenario.json, int(scenario.id), scenario.disabled, scenario.description)

    def shutdown(self):
        """ Callback to shut down all parameters
        """
        for cond in self._conditions.keys():
            self.delete_scenario(cond, db_delete=False)

    def get_parsed_condition(self, name):
        """ Call cond.get_parsed_condition on the cond with name 'name'
        @param name : name of the Condition
        @return {'name':name, 'data': parsed_condition} or raise Exception
        """
        if name not in self._conditions:
            raise KeyError('no key %s in conditions table' % name)
        else:
            parsed = self._conditions[name].get_parsed_condition()
            return {'name': name, 'data': parsed}

    def update_scenario(self, cid, name, json_input, dis, desc):
        if int(cid) != 0:
            self.del_scenario(cid, False)
        return self.create_scenario(name, json_input, cid, dis, desc, True)

    def del_scenario(self, cid, doDB=True):
        try:
            if cid == '' or int(cid) not in self._instances.keys():
                self.log.info(u"Scenario deletion : id '{0}' doesn't exist".format(cid))
                return {'status': 'ERROR', 'msg': u"Scenario {0} doesn't exist".format(cid)}
            else:
                self._instances[int(cid)]['instance'].destroy()
                del(self._instances[int(cid)])
                if doDB:
                    with self._db.session_scope():
                        self._db.del_scenario(cid)
                self.log.info(u"Scenario {0} deleted".format(cid))
        except:
            msg = u"Error while deleting the scenario id='{0}'. Error is : {1}".format(cid, traceback.format_exc())
            self.log.error(msg)
            return {'status': 'ERROR', 'msg': msg}

    def create_scenario(self, name, json_input, cid=0, dis=False, desc=None, update=False):
        """ Create a Scenario from the provided json.
        @param name : A name for the condition instance
        @param json_input : JSON representation of the condition
        The JSON will be parsed to get all the uuids, and test instances will be created.
        The json needs to have 2 keys:
            - condition => the json that will be used to create the condition instance
            - actions => the json that will be used for creating the actions instances
        @Return {'name': name} or raise exception
        """
        ocid = cid
        try:
            self.log.info(u"Create or save scenario : name = '{1}', id = '{1}', json = '{2}'".format(name, cid, json_input))
            payload = json.loads(json_input)  # quick test to check if json is valid
        except Exception as e:
            self.log.error(u"Creation of a scenario failed, invallid json: {0}".format(json_input))
            self.log.debug(e)
            return {'status': 'NOK', 'msg': 'invallid json'}

        if 'IF' not in payload.keys() \
                or 'DO' not in payload.keys():
            msg = u"the json for the scenario does not contain condition or actions for scenario {0}".format(name)
            self.log.error(msg)
            return {'status': 'NOK', 'msg': msg}
        # db storage
        if int(ocid) == 0:
            with self._db.session_scope():
                scen = self._db.add_scenario(name, json_input, dis, desc)
                cid = scen.id
        elif update:
            with self._db.session_scope():
                self._db.update_scenario(cid, name, json_input, dis, desc)

        # create the condition itself
        try:
            scen = ScenarioInstance(self.log, cid, name, payload, dis)
            self._instances[cid] = {'name': name, 'json': payload, 'instance': scen } 
            self.log.debug(u"Create scenario instance {0} with payload {1}".format(name, payload['IF']))
            self._instances[cid]['instance'].eval_condition()
        except Exception as e:  
            if int(ocid) == 0:
                with self._db.session_scope():
                    self._db.del_scenario(cid)
            self.log.error(u"Creation of a scenario failed")
            self.log.debug(e)
            return {'status': 'NOK', 'msg': 'Creation of scenario failed'}
        # return
        return {'name': name, 'cid': cid}

    def eval_condition(self, name):
        """ Evaluate a condition calling eval_condition from Condition instance
        @param name : The name of the condition instance
        @return {'name':name, 'result': evaluation result} or raise Exception
        """
        if name not in self._conditions:
            raise KeyError('no key %s in conditions table' % name)
        else:
            res = self._conditions[name].eval_condition()
            return {'name': name, 'result': res}

    def trigger_actions(self, name):
        """ Trigger that will be called when a condition evaluates to True
        """
        if name not in self._conditions_actions \
                or name not in self._conditions:
            raise KeyError('no key %s in one of the _conditions tables table' % name)
        else:
            for action in self._conditions_actions[name]:
                self._actions_mapping[action].do_action( \
                        self._conditions[name], \
                        self._conditions[name].get_mapping() \
                        )

    def list_actions(self):
        """ Return the list of actions
        @return a hash of hashes for the different actions
        { "module1.Action1" : {
            "description" : "some description of the action",
            "parameters" : { "param1" : {
                ... see get_expected_entries for details
            }
        }
        """

        self.log.debug("ScenarioManager : list actions")
        res = {}
        actions = self.__return_list_of_classes(s_a)
        for name, cls in actions:
            self.log.debug("- {0}".format(name))
            inst = cls()
            res[name] = {"parameters": inst.get_expected_entries(),
                         "description": inst.get_description()}
        return res

    def list_tests(self):
        """ Return the list of tests
        @return a hash of hashes for the different tests
        { "module1.Test1" : {
            "description" : "some description of the test",
            "parameters" : { "param1" : {
                ... see list_parameters doc for detail on this part
            }
        }
        """

        self.log.debug("ScenarioManager : list tests")
        res = {}
        tests = self.__return_list_of_classes(s_t)

        for name, cls in tests:
            self.log.debug("- {0}".format(name))
            inst = cls(log = self.log)

            params = []
            for p, i in inst.get_parameters().iteritems():
                for param, info in i['expected'].iteritems():
                    params.append({
                            "name": "{0}.{1}".format(p, param),
                            "description": info['description'],
                            "type": info['type'],
                            "values": info['values'],
                            "filters": info['filters'],
                        })

            res[name] = {"parameters": params,
                         "description": inst.get_description()}
        return res
        #for name, cls in tests:
        #    self.log.debug("- {0}".format(name))
        #    inst = cls(log = self.log)
        #    res[name] = []
        #    for p, i in inst.get_parameters().iteritems():
        #        for param, info in i['expected'].iteritems():
        #            res[name].append({
        #                    "name": "{0}.{1}".format(p, param),
        #                    "description": info['description'],
        #                    "type": info['type'],
        #                    "values": info['values'],
        #                    "filters": info['filters'],
        #                })
        #    inst.destroy()
        #return res

    def list_conditions(self):
        """ Return the list of conditions as JSON
        """
        ret = []
        for cid, inst in self._instances.iteritems():
            ret.append({'cid': cid, 'name': inst['name'], 'json': inst['json']})
        return ret

    def __return_list_of_classes(self, package):
        """ Return the list of module/classes in a package
        @param package : a reference to the package that need to be explored
        @return a list of tuple ('modulename.Classname', <instance of class>)
        """
        self.log.debug("Get list of classes for package : {0}".format(package))
        res = []
        mods = pkgutil.iter_modules(package.__path__)
        for module in mods:
            self.log.debug("- {0}".format(module))
            imported_mod = importlib.import_module('.' + module[1], package.__name__)
            #get the list of classes in the module
            classes = [m for m in inspect.getmembers(imported_mod) if inspect.isclass(m[1])]
            # Filter in order to keep only the classes that belong to domogik package and are not abstract
            res.extend([(module[1] + "." + c[0], c[1]) for c in filter(
                lambda x: x[1].__module__.startswith("domogik.scenario.") and not x[0].startswith("Abstract"), classes)])
        return res
Exemple #3
0
class ScenarioManager:
    """ Manage scenarios : create them, evaluate them, etc ...
        A scenario instance contains a condition, which is a boolean
        combination of many tests,
        and a list of actions
        Each test can be :
         - test on the test of any device
         - test on the time
         - action triggered by user (click on UI for ex)
        The test on devices are managed directly by xpl Listeners
        The test on time will be managed by a TimeManager
        The actions will be managed by an ActionManager
        { 
         "condition" :
            { "AND" : {
                    "OR" : {
                        "one-uuid" : {
                            "param_name_1" : {
                                "token1" : "value",
                                "token2" : "othervalue"
                            },
                            "param_name_2" : {
                                "token3" : "foo"
                            }
                        },
                        "another-uuid" : {
                            "param_name_1" : {
                                "token4" : "bar"
                            }
                        }
                    },
                    "yet-another-uuid" : {
                        "param_name_1" : {
                            "url" : "http://google.fr",
                            "interval" : "5"
                        }
                    }
                }
            },
         "actions" : [
            "uid-for-action" : {
                "param1" : "value1",
                "param2" : "value2"
            },
            "uid-for-action2" : {
                "param3" : "value3"
            }
         ]
        }
    """

    def __init__(self, log):
        """ Create ScenarioManager instance
            @param log : Logger instance
        """
        # Keep uuid <-> instance mapping for tests and actions
        self._tests_mapping = {}
        self._actions_mapping = {}
        # Keep list of conditions as name : instance
        self._conditions = {}
        # Keep list of actions uuid linked to a condition  as name : [uuid1, uuid2, ... ]
        self._conditions_actions = {}
        # an instance of the logger
        self.log = log
        # As we lazy-load all the tests/actions in __instanciate
        # we need to keep the module in a list so that we don't need to reload them
        # every time.
        self._test_cache = {}
        self._action_cache = {}
        # load all scenarios from the DB
        self._db = DbHelper()
        self.load_scenarios()

    def load_scenarios(self):
        """ Loads all scenarios from the db
        for each scenario call the create_scenario method
        """
        with self._db.session_scope():
            for scenario in self._db.list_scenario():
                # add all uuids to the mappings
                for uid in scenario.uuids:
                    if uid.is_test:
                        self.__ask_instance(uid.key, self._tests_mapping, uid.uuid)
                    else:
                        self.__ask_instance(uid.key, self._actions_mapping, uid.uuid)
                # now all uuids are created go and install the condition
                self.create_scenario(scenario.name, scenario.json, store=False)
        for (name, cond) in self._conditions.items():
            cond.parse_condition()

    def __ask_instance(self, obj, mapping, set_uuid=None):
        """ Generate an uuid corresponding to the object passed as parameter
        @param obj : a string as "objectmodule.objectClass" to instanciate
        @param mapping : in what map to store the new uuid
        @param uuid : if the uuid is provided, don't generate it
        @return an uuid referencing a new instance of the object
        """
        if set_uuid is None:
            _uuid = self.get_uuid()
        else:
            _uuid = set_uuid
        mapping[_uuid] = obj
        return _uuid

    def ask_test_instance(self, obj):
        """ Generate an uuid corresponding to the object passed as parameter
        @param obj : a string as "objectmodule.objectClass" to instanciate
        @return an uuid referencing a new instance of the object
        """
        return self.__ask_instance(obj, self._tests_mapping)

    def ask_action_instance(self, obj):
        """ Generate an uuid corresponding to the object passed as parameter
        @param obj : a string as "objectmodule.objectClass" to instanciate
        @return an uuid referencing a new instance of the object
        """
        return self.__ask_instance(obj, self._actions_mapping)

    def __instanciate(self):
        """ This method will read the list of uuids, and create coresponding
        instances if they don't exist yet.
        If they don't exist yet the type of the instans is str or unicode
        """
        for _uuid in self._tests_mapping:
            inst = self._tests_mapping[_uuid]
            if type(inst) in [str, unicode]:
                # _test_cache keeps a list of classname/class object
                # so we have to load the module/class etc ... only once
                if inst not in self._test_cache:
                    mod, clas = inst.split('.')
                    module_name = "domogik.common.scenario.tests.{0}".format(mod)
                    cobj = getattr(__import__(module_name, fromlist=[mod]), clas)
                    self._test_cache[inst] = cobj
                    self.log.debug(u"Add class {0} to test cache".format(inst))
                self.log.debug(u"Create test instance {0} with uuid {1}".format(inst, _uuid))
                self._tests_mapping[_uuid] = self._test_cache[inst](self.log, trigger=self.generic_trigger)
        for _uuid in self._actions_mapping:
            inst = self._actions_mapping[_uuid]
            if type(inst) in [str, unicode]:
                # _action_cache keeps a list of classname/class object
                # so we have to load the module/class etc ... only once
                if inst not in self._action_cache:
                    mod, clas = inst.split('.')
                    module_name = "domogik.common.scenario.actions.{0}".format(mod)
                    cobj = getattr(__import__(module_name, fromlist=[mod]), clas)
                    self._action_cache[inst] = cobj
                    self.log.debug(u"Add class {0} to action cache".format(inst))
                self.log.debug(u"Create action instance {0} with uuid {1}".format(inst, _uuid))
                self._actions_mapping[_uuid] = self._action_cache[inst](self.log)

    def shutdown(self):
        """ Callback to shut down all parameters
        """
        for cond in self._conditions.keys():
            self.delete_scenario(cond, db_delete=False)

    def generic_trigger(self, test_i):
        """ Generic trigger to refresh a condition state when some value change
        @todo allow custom triggers
        @param test_i the test instance
        """
        if test_i.get_condition():
            cond = test_i.get_condition()
            if cond.get_parsed_condition is None:
                return
            st = cond.eval_condition()
            test_i._log.info("state of condition '%s' is %s" % (cond.get_parsed_condition(), st))
        else:  # We have no condition, just evaluate test
            test_i.evaluate()

    def get_parsed_condition(self, name):
        """ Call cond.get_parsed_condition on the cond with name 'name'
        @param name : name of the Condition
        @return {'name':name, 'data': parsed_condition} or raise Exception
        """
        if name not in self._conditions:
            raise KeyError('no key %s in conditions table' % name)
        else:
            parsed = self._conditions[name].get_parsed_condition()
            return {'name': name, 'data': parsed}

    def get_uuid(self):
        """ Return some random uuid
        Needs to verify that the uuid is not already used
        Does this in the mappings (for actions and tests)
        """
        _uuid = str(uuid.uuid4())
        while _uuid in self._tests_mapping.keys() \
                or uuid in self._actions_mapping.keys():
            _uuid = str(uuid.uuid4())
        return _uuid

    def delete_scenario(self, name, db_delete=True):
        if name not in self._conditions:
            self.log.info(u"Scenario {0} doesn't exist".format(name))
            return {'status': 'ERROR', 'msg': u"Scenario {0} doesn't exist".format(name)}
        else:
            # the condition and the tests
            cond = self._conditions[name]
            for tuuid in cond.destroy():
                del self._tests_mapping[tuuid]
            cond = None
            del self._conditions[name]
            # the actions
            for action in self._conditions_actions[name]:
                self._actions_mapping[action].destroy()
            del self._conditions_actions[name]
            # delete from the db
            with self._db.session_scope():
                scen = self._db.get_scenario_by_name(name)
                print scen
                if scen:
                    self._db.del_scenario(scen.id)
            self.log.info(u"Scenario {0} deleted".format(name))

    def create_scenario(self, name, json_input, store=True):
        """ Create a Scenario from the provided json.
        @param name : A name for the condition instance
        @param json_input : JSON representation of the condition
        The JSON will be parsed to get all the uuids, and test instances will be created.
        The json needs to have 2 keys:
            - condition => the json that will be used to create the condition instance
            - actions => the json that will be used for creating the actions instances
        @Return {'name': name} or raise exception
        """
        if name in self._conditions.keys():
            self.log.error(u"A scenario with name '{0}' already exists.".format(name))
            return {'status': 'NOK', 'msg': 'a scenario with this name already exists'}

        try:
            payload = json.loads(json_input)  # quick test to check if json is valid
        except Exception as e:
            self.log.error(u"Creation of a scenario failed, invallid json: {0}".format(json_input))
            self.log.debug(e)
            return {'status': 'NOK', 'msg': 'invallid json'}

        if 'condition' not in payload.keys() \
                or 'actions' not in payload.keys():
            msg = u"the json for the scenario does not contain condition or actions for scenario {0}".format(name)
            self.log.error(msg)
            return {'status': 'NOK', 'msg': msg}

        # instantiate all objects
        self.__instanciate()
        # create the condition itself
        c = Condition(self.log, name, json.dumps(payload['condition']), mapping=self._tests_mapping, on_true=self.trigger_actions)
        self._conditions[name] = c
        self._conditions_actions[name] = []
        self.log.debug(u"Create condition {0} with payload {1}".format(name, payload['condition']))
        # build a list of actions
        for action in payload['actions'].keys():
            # action is now a tuple
            #   (uid, params)
            self._conditions_actions[name].append(action) 
            self._actions_mapping[action].do_init(payload['actions'][action]) 
 
        # store the scenario in the db
        if store:
            with self._db.session_scope():
                # store the scenario
                scen = self._db.add_scenario(name, json_input)
                # store the tests
                for uuid in c.get_mapping():
                    cls = str(self._tests_mapping[uuid].__class__).replace('domogik.common.scenario.tests.', '')
                    self._db.add_scenario_uuid(scen.id, uuid, cls, 1)
                # store the actions
                for uuid in self._conditions_actions[name]:
                    cls = str(self._actions_mapping[uuid].__class__).replace('domogik.common.scenario.actions.', '')
                    self._db.add_scenario_uuid(scen.id, uuid, cls, 0)
        # return
        return {'name': name}

    def eval_condition(self, name):
        """ Evaluate a condition calling eval_condition from Condition instance
        @param name : The name of the condition instance
        @return {'name':name, 'result': evaluation result} or raise Exception
        """
        if name not in self._conditions:
            raise KeyError('no key %s in conditions table' % name)
        else:
            res = self._conditions[name].eval_condition()
            return {'name': name, 'result': res}

    def trigger_actions(self, name):
        """ Trigger that will be called when a condition evaluates to True
        """
        if name not in self._conditions_actions \
                or name not in self._conditions:
            raise KeyError('no key %s in one of the _conditions tables table' % name)
        else:
            for action in self._conditions_actions[name]:
                self._actions_mapping[action].do_action( \
                        self._conditions[name], \
                        self._conditions[name].get_mapping() \
                        )

    def list_actions(self):
        """ Return the list of actions
        @return a hash of hashes for the different actions
        { "module1.Action1" : {
            "description" : "some description of the action",
            "parameters" : { "param1" : {
                ... see get_expected_entries for details
            }
        }
        """

        res = {}
        actions = self.__return_list_of_classes(s_a)
        for name, cls in actions:
            inst = cls()
            res[name] = {"parameters": inst.get_expected_entries(),
                         "description": inst.get_description()}
        return json.dumps(res)

    def list_tests(self):
        """ Return the list of tests
        @return a hash of hashes for the different tests
        { "module1.Test1" : {
            "description" : "some description of the test",
            "parameters" : { "param1" : {
                ... see list_parameters doc for detail on this part
            }
        }
        """

        res = {}
        tests = self.__return_list_of_classes(s_t)
        for name, cls in tests:
            inst = cls()
            res[name] = {"parameters": inst.get_parameters(),
                         "description": inst.get_description()}
            inst.destroy()
        return json.dumps(res)

    def list_conditions(self):
        """ Return the list of conditions as JSON
        """

        classes = self.__return_list_of_classes(s_c)
        res = []
        for c in classes:
            res.append(c[0])
        return json.dumps(res)

    def list_parameters(self):
        """ Return the list of parameters as JSON
        @return a hash of hash for parameters, like :
        { "module1.Parameter1" : {
                "token1": {
                    "type" : "type of token",
                    "description": "Description of token",
                    "default" : "default value or empty",
                    "values" : [ "list","of","value","that","user","can","choose"],
                    "filters" : [ "list","of","filters","or",""]
                },
                "token2" : { ... }
            }
            "module2.Parameter1" : { ... }
        }
        """
        res = {}
        params = self.__return_list_of_classes(s_p)
        for name, cls in params:
            inst = cls()
            res[name] = inst.get_expected_entries()
            inst.destroy()
        return json.dumps(res)

    def __return_list_of_classes(self, package):
        """ Return the list of module/classes in a package
        @param package : a reference to the package that need to be explored
        @return a list of tuple ('modulename.Classname', <instance of class>)
        """
        res = []
        mods = pkgutil.iter_modules(package.__path__)
        for module in mods:
            imported_mod = importlib.import_module('.' + module[1], package.__name__)
            #get the list of classes in the module
            classes = [m for m in inspect.getmembers(imported_mod) if inspect.isclass(m[1])]
            # Filter in order to keep only the classes that belong to domogik package and are not abstract
            res.extend([(module[1] + "." + c[0], c[1]) for c in filter(
                lambda x: x[1].__module__.startswith("domogik.common.scenario.") and not x[0].startswith("Abstract"), classes)])
        return res