def _wok_run(self, case_name, conf_builder, flow_uri): wok = WokEngine(self.conf) wok.start(wait=False, single_run=True) try: case = wok.create_case(case_name, conf_builder, flow_uri) wok.wait() except KeyboardInterrupt: pass finally: try: wok.stop() except KeyboardInterrupt: self.log.warn("Ctrl-C pressed while stopping Wok engine.")
class WokServer(object): # signals case_started = Signal() case_finished = Signal() case_removed = Signal() def __init__(self, app=None, start_engine=True, **kwargs): self.app = app self._start_engine = start_engine self.engine = None self.lock = Lock() self._initialized = False self.logger = logger.get_logger("wok.server", level="info") if app is not None: self.init_app(app, **kwargs) def _generate_secret_key(self): path = os.path.join(self.engine.work_path, "secret_key.bin") if not os.path.exists(path): self.logger.info("Generating a new secret key ...") secret_key = os.urandom(24) with open(path, "wb") as f: f.write(secret_key) else: self.logger.info("Loading secret key ...") with open(path, "rb") as f: secret_key = f.read() if len(secret_key) < 24: self.logger.warn("Found a secret key too short. Generating a new larger one.") secret_key = os.urandom(24) return secret_key def _teardown_request(self, exception): db.Session.remove() def _init_flask(self, app): """ Initialize the Flask application. Override *_create_flask_app()* to use another class. """ self.logger.info("Initializing Flask application ...") app.wok = self app.logger_name = "web" app.teardown_request(self._teardown_request) login_manager = LoginManager() login_manager.init_app(self.app) login_manager.user_loader(self._load_user) login_manager.login_message = "Please sign in to access this page." #self.login_manager.anonymous_user = ... app.register_blueprint(core.bp, url_prefix="/core") def _conf_args(self, **kwargs): args = dict(conf_files=None, path_var="WOK_CONF_PATH", files_var="WOK_CONF_FILES", args_var="WOK_CONF_ARGS") for k, v in kwargs.items(): if k in args: args[k] = v return args def _init_conf(self, app, conf_files, path_var, files_var, args_var): self.logger.info("Checking Wok configuration files ...") args = [] conf_path = app.config.get(path_var, os.environ.get(path_var, os.getcwd())) wok_conf_files = _get_conf_files(conf_path, conf_files or [], app.config.get(files_var, os.environ.get(files_var)), ".conf") for path in wok_conf_files: if not os.path.exists(path): self.logger.error("--- {} (not found)".format(path)) continue self.logger.info("+++ {}".format(path)) args += ["-c", path] env_args = os.environ.get(args_var) if env_args is not None: env_args = env_args.strip().split(" ") wok_conf_args = app.config.get(args_var, env_args) if wok_conf_args is not None: args += wok_conf_args self.logger.debug("Arguments: {}".format(" ".join(wok_conf_args))) self.logger.info("Loading Wok configuration ...") parser = ArgumentParser() wok_args = Arguments(parser, case_name_args=False, logger=self.logger) wok_args.initialize(parser.parse_args(args)) self.conf_builder = wok_args.engine_conf_builder self.conf = wok_args.engine_conf.expand_vars() self.case_conf_builder = wok_args.case_conf_builder self.case_conf = wok_args.case_conf.expand_vars() # initialize logging according to the configuration log = logger.get_logger("") log.removeHandler(log.handlers[0]) logging_conf = self.conf.clone().expand_vars().get("wok.logging") logger.initialize(logging_conf) self.logger.debug(repr(self.conf)) def _init_engine(self): """ Initialize the Wok Engine. """ self.logger.info("Initializing Wok engine ...") self.engine = WokEngine(self.conf) self.engine.case_state_changed.connect(self._case_state_changed) self.engine.case_started.connect(self._case_started) self.engine.case_finished.connect(self._case_finished) self.engine.case_removed.connect(self._case_removed) def init_app(self, app, **kwargs): self._init_flask(app) self._init_conf(app, **self._conf_args(**kwargs)) self._init_engine() if app.secret_key is None: app.secret_key = self._generate_secret_key() app.extensions["wok"] = self.engine db_path = os.path.join(self.engine.work_path, "server.db") new_db = not os.path.exists(db_path) engine = db.create_engine(uri="sqlite:///{}".format(db_path)) session = db.Session() db_init(engine, session, new_db) session.commit() session.close() self._initialized = True if self._start_engine: self.start_engine() def start_engine(self): self.engine.start(wait=False) def _finalize(self): self.logger.info("Finalizing Wok server ...") with self.lock: self.logger.info("Finalizing Wok engine ...") self.engine.stop() def run(self, app=None, version=VERSION): if not self._initialized: raise Exception("The server requires initialization before running") from argparse import ArgumentParser parser = ArgumentParser() parser.add_argument('--version', action='version', version='%(prog)s ' + VERSION) parser.add_argument("--host", dest="host", metavar="HOST", default="0.0.0.0", help="Define the host to listen for") parser.add_argument("--port", dest="port", metavar="PORT", type=int, default=5000, help="Define the port to listen for") parser.add_argument("--pid-file", dest="pid_file", metavar="PATH", help="Save the PID into a file at PATH") parser.add_argument("--debug", dest="debug", action="store_true", default=False, help="Run in debug mode") args = parser.parse_args() if args.pid_file is not None: with open(args.pid_file, "w") as f: f.write(str(os.getpid())) if app is None: app = self.app if app is None: raise Exception("The server can not be run without the app") try: if not self.engine.running(): self.engine.start(wait=False) self.logger.info("Listening on {}:{} ...".format(args.host, args.port)) http_server = HTTPServer(WSGIContainer(app)) http_server.listen(args.port) IOLoop.instance().start() # user has pressed ctrl-C and flask stops finally: self._finalize() def shutdown(self): """ Shutdown the Werkzeug Server. Only valid if running under the provided Werkzeug Server. Requires Werkzeug 0.8+ """ werkzeug_shutdown = request.environ.get('werkzeug.server.shutdown') if werkzeug_shutdown is None: raise RuntimeError('Not running with the Werkzeug Server') werkzeug_shutdown() # Authentication --------------------------------------------------------------------------------------------------- def _load_user(self, user_id): "LoginManager user loader." user = User.query.filter_by(id=user_id).first() #current_app.logger.debug("load_user({}) = {}".format(user_id, repr(user))) return user # Users ------------------------------------------------------------------------------------------------------------ def register_user(self, **kwargs): """ Register a new user in the database. Either nick or email attributes is required. """ assert("nick" in kwargs or "email" in kwargs) session = db.Session() group = session.query(Group).filter(Group.name == USERS_GROUP).first() if group is None: group = Group(name=USERS_GROUP, desc="Users") session.add(group) user = User(**kwargs) if user.name is None: user.name = user.nick or user.email user.groups += [group] session.add(user) session.commit() return user def get_user_by_email(self, email): return User.query.filter_by(email=email).first() # Projects --------------------------------------------------------------------------------------------------------- def project_conf(self, *args, **kwargs): return self.engine.projects.project_conf(*args, **kwargs) # Cases ------------------------------------------------------------------------------------------------------------ def _case_state_changed(self, engine_case): session = db.Session() case = session.query(Case).filter(Case.engine_name == engine_case.name).first() if case is not None: case.state = engine_case.state session.commit() def _case_started(self, engine_case): session = db.Session() case = session.query(Case).filter(Case.engine_name == engine_case.name).first() if case is not None: case.started = engine_case.started self.case_started.send(case, server=self, logger=self.logger) session.commit() def _case_finished(self, engine_case): session = db.Session() case = session.query(Case).filter(Case.engine_name == engine_case.name).first() if case is not None: case.state = engine_case.state case.finished = engine_case.finished self.case_finished.send(case, server=self, logger=self.logger) session.commit() def _case_removed(self, engine_case): session = db.Session() case = session.query(Case).filter(Case.engine_name == engine_case.name).first() if case is not None: self.case_removed.send(case, server=self, logger=self.logger) session.delete(case) session.commit() def exists_case(self, user, case_name): engine_case_name = "{}-{}".format(user.nick, case_name) exists_in_db = lambda: Case.query.filter(Case.owner_id == user.id, Case.name == case_name).count() > 0 return self.engine.exists_case(engine_case_name) or exists_in_db() def create_case(self, user, case_name, conf_builder, project_name, flow_name, properties=None, start=True): case = Case( owner_id=user.id, name=case_name, project_name=project_name, flow_name=flow_name, conf=conf_builder.get_conf(), properties=Data.element(properties)) session = db.Session() session.add(case) session.commit() engine_case_name = "{}-{}".format(user.nick, case_name) #while self.engine.exists_case(engine_case_name): # engine_case_name = "{}-{}".format(user.nick, uuid4().hex[-6:]) engine_case = self.engine.create_case(engine_case_name, conf_builder, project_name, flow_name, engine_case_name) case.created = engine_case.created case.engine_name = engine_case_name session.commit() if start: engine_case.start() return case def remove_case(self, user, case): # The case will be remove from the db when the case remove signal arrives case.removed = True session = inspect(case).session session.commit() # FIXME sure to do the commit here ? if self.engine.exists_case(case.engine_name): self.engine.remove_case(case.engine_name) else: self.logger.debug("No engine case available: {}".format(case.engine_name)) self.case_removed.send(case) session.delete(case) session.commit() def case_by_id(self, case_id, user=None): q = Case.query.filter(Case.id == case_id) if user is not None: q = q.filter(Case.owner_id == user.id) return q.first() def cases(self, user=None): q = Case.query if user is not None: q = q.filter(Case.owner_id == user.id) return q.all() def cases_count(self, user=None): q = Case.query if user is not None: q = q.filter(Case.owner_id == user.id) return q.count()
class RunCommand(Command): DEFAULT_CONF_FILES = [ "system.conf", "run.conf" ] DEFAULT_REQUIRED_CONF = [ # "work_path", "temp_path"] ] def __init__(self, args_usage="", epilog="", logger_name=None): Command.__init__(self, args_usage, epilog, logger_name) signal.signal(signal.SIGINT, keyboardinterrupt_handler) signal.signal(signal.SIGTERM, keyboardinterrupt_handler) ''' # Override configuration path if required if self.args.conf_path is not None: self.conf_path = os.path.abspath(self.args.conf_path) ''' # Determine required and user configuration files and data self.engine_conf_args = ConfArgs(self.log, self.conf_path, self.args.engine_conf_files, self.args.engine_conf_data, self.DEFAULT_CONF_FILES, self.DEFAULT_REQUIRED_CONF) self.engine_conf_builder = self.engine_conf_args.conf_builder self.case_conf_args = ConfArgs(self.log, self.conf_path, self.args.case_conf_files, self.args.case_conf_data) self.case_conf_builder = self.case_conf_args.conf_builder # Workspace self.workspace = self.args.workspace # Case name self.case_name = self.args.case_name if self.case_name is not None: self.case_name = normalize_id(self.case_name) # max cores if self.args.max_cores is None: self.max_cores = 0 else: self.max_cores = self.args.max_cores def add_arguments(self, parser): "Called from __init__() to let the command to add more options to the OptionsParser" parser.add_argument("-w", "--workspace", dest = "workspace", metavar = "NAME", default="default", help = "Define the workspace name.") parser.add_argument("-u", "--user", dest="user", metavar="NAME", help="Define the user name.") parser.add_argument("-s", "--storage", dest="storage", metavar="NAME", help="Define the storage name.") g = parser.add_argument_group("Wok Options") g.add_argument("-n", "--case", dest = "case_name", metavar = "NAME", help = "Define the case name") g.add_argument("-c", "--engine-conf", action="append", dest="engine_conf_files", metavar="FILE", help="Load engine configuration from a file. Multiple files can be specified") g.add_argument("-d", action="append", dest="engine_conf_data", metavar="PARAM=VALUE", help="Define engine configuration parameter. Multiple definitions can be specified. Example: -d option1=value") g.add_argument("-C", "--conf", action="append", dest="case_conf_files", metavar="FILE", help="Load case configuration from a file. Multiple files can be specified") g.add_argument("-D", action="append", dest="case_conf_data", metavar="PARAM=VALUE", help="Define case configuration parameter. Multiple definitions can be specified. Example: -D option1=value") g.add_argument("-j", dest="max_cores", metavar="NUM", type=int, help="Define the maximum number of cores to use. Default all the cores available.") def build_conf(self): "Called from run() to prepare configuration before expansion of values" # Build the configuration self.engine_conf_args.load_files() self.case_conf_args.load_files() # General conf if self.case_name is None: if self.workspace is not None: self.case_name = self.workspace else: self.case_name = datetime.now().strftime("%Y-%m-%d-%H-%M-%S-%f") if self.max_cores > 0: self.engine_conf_builder.add_value("wok.platforms[0].jobs.max_cores", self.max_cores) if self.args.log_level is not None: self.case_conf_builder.add_value("logging", self.args.log_level) # Set user and workspace self.user_id = self.args.user or getpass.getuser() self.container = self.args.storage self.case_conf_builder.add_value("user_id", self.user_id) self.case_conf_builder.add_value("workspace", self.workspace) # Parse conf data self.engine_conf_args.parse_data() self.case_conf_args.parse_data() def process_conf(self): "Called from run() to process the configuration after expansion of values" # Configure wok work path if "wok.work_path" not in self.engine_conf: self.engine_conf_builder.add_value("wok.work_path", os.path.join(self.engine_conf.get("runtime_path", os.getcwd()), "wok")) def run(self): "Run the command and execute the command" self.build_conf() # Expand configuration variables self.engine_conf = self.engine_conf_args.validated_conf() self.case_conf = self.case_conf_args.validated_conf(expand_vars=False) # Validate and process configuration self.process_conf() # Regenerate configuration self.engine_conf = self.engine_conf_builder.get_conf() self.case_conf = self.case_conf_builder.get_conf() # Final logging configuration log = logger.get_logger("") log.removeHandler(log.handlers[0]) logging_conf = self.engine_conf.get("wok.logging") logger.initialize(logging_conf) # Show some debugging information self.log.debug("Root path = {}".format(self.root_path)) self.log.debug("Conf path = {}".format(self.conf_path)) #self.log.debug("Runtime path = {}".format(self.runtime_path)) #self.log.debug("Results path = {}".format(self.results_path)) #self.log.debug("Temp path = {}".format(self.temp_path)) self.engine_conf_args.log_debug() self.case_conf_args.log_debug() # Execute try: self.execute() except BaseException as ex: self.log.error(str(ex)) from traceback import format_exc self.log.debug(format_exc()) return -1 return 0 def _case_created(self, case, **kwargs): if hasattr(self, "projects") and self.projects is not None: upload_files(self.log, case.name, case.storages, self.projects) config = GlobalConfig( case.project.get_conf(platform_name=DEFAULT_PLATFORM_NAME), dict(user_id=self.user_id, workspace=self.workspace)) self.results_path = PathsConfig(config).results_path() def _case_finished(self, case, **kwargs): if hasattr(self, "results_path") and self.results_path is not None: download_files(self.log, case.name, case.storages, "results/", self.results_path) def _wok_run(self, flow, case_name=None, project=None, container=None): # Create and start the Wok engine self.wok = WokEngine(self.engine_conf, self.conf_path) self.wok.case_created.connect(self._case_created) self.wok.case_finished.connect(self._case_finished) self.wok.start(wait=False, single_run=True) # Create and start the case try: case_name = case_name or self.case_name case = self.wok.create_case( case_name, self.case_conf_builder, project or PROJECT_NAME, flow, container or self.container or case_name) case.start() self.wok.wait() except KeyboardInterrupt: pass finally: try: self.wok.stop() except KeyboardInterrupt: self.log.warn("Ctrl-C pressed while stopping Wok engine.")