コード例 #1
0
ファイル: jobrunner.py プロジェクト: repo-dpaes/lithops
    def __init__(self, job, jobrunner_conn, internal_storage):
        self.jobrunner_conn = jobrunner_conn
        self.internal_storage = internal_storage

        self.lithops_config = job.config
        self.executor_id = job.executor_id
        self.call_id = job.call_id
        self.job_id = job.job_id
        self.func_key = job.func_key
        self.data_key = job.data_key
        self.data_byte_range = job.data_byte_range
        self.output_key = create_output_key(JOBS_PREFIX, self.executor_id,
                                            self.job_id, self.call_id)

        # Setup stats class
        self.stats = stats(job.jr_stats_file)

        # Setup prometheus for live metrics
        prom_enabled = self.lithops_config['lithops'].get('monitoring')
        prom_config = self.lithops_config.get('prometheus', {})
        self.prometheus = PrometheusExporter(prom_enabled, prom_config)

        mode = self.lithops_config['lithops']['mode']
        self.customized_runtime = self.lithops_config[mode].get(
            'customized_runtime', False)
コード例 #2
0
ファイル: invokers.py プロジェクト: kpavel/lithops
    def __init__(self, config, executor_id, internal_storage, compute_handler,
                 job_monitor):
        log_level = logger.getEffectiveLevel()
        self.log_active = log_level != logging.WARNING
        self.log_level = LOGGER_LEVEL if not self.log_active else log_level

        self.config = config
        self.executor_id = executor_id
        self.storage_config = extract_storage_config(self.config)
        self.internal_storage = internal_storage
        self.compute_handler = compute_handler
        self.is_lithops_worker = is_lithops_worker()
        self.job_monitor = job_monitor

        self.workers = self.config['lithops'].get('workers')
        if self.workers:
            logger.debug('ExecutorID {} - Total workers: {}'.format(
                self.executor_id, self.workers))

        prom_enabled = self.config['lithops'].get('telemetry', False)
        prom_config = self.config.get('prometheus', {})
        self.prometheus = PrometheusExporter(prom_enabled, prom_config)

        self.mode = self.config['lithops']['mode']
        self.backend = self.config['lithops']['backend']
        self.runtime_name = self.config[self.backend]['runtime']

        self.customized_runtime = self.config[self.mode].get(
            'customized_runtime', False)
コード例 #3
0
    def __init__(self, job, jobrunner_conn, internal_storage):
        self.job = job
        self.jobrunner_conn = jobrunner_conn
        self.internal_storage = internal_storage
        self.lithops_config = job.config

        self.output_key = create_output_key(job.executor_id, job.job_id,
                                            job.call_id)

        # Setup stats class
        self.stats = JobStats(self.job.stats_file)

        # Setup prometheus for live metrics
        prom_enabled = self.lithops_config['lithops'].get('telemetry')
        prom_config = self.lithops_config.get('prometheus', {})
        self.prometheus = PrometheusExporter(prom_enabled, prom_config)
コード例 #4
0
    def __init__(self, task, jobrunner_conn, internal_storage):
        self.task = task
        self.jobrunner_conn = jobrunner_conn
        self.internal_storage = internal_storage
        self.lithops_config = task.config

        self.output_key = create_output_key(JOBS_PREFIX, self.task.executor_id,
                                            self.task.job_id, self.task.id)

        # Setup stats class
        self.stats = stats(self.task.stats_file)

        # Setup prometheus for live metrics
        prom_enabled = self.lithops_config['lithops'].get('monitoring')
        prom_config = self.lithops_config.get('prometheus', {})
        self.prometheus = PrometheusExporter(prom_enabled, prom_config)
コード例 #5
0
    def __init__(self, jr_config, jobrunner_conn, internal_storage):
        self.jr_config = jr_config
        self.jobrunner_conn = jobrunner_conn
        self.internal_storage = internal_storage

        self.lithops_config = self.jr_config['lithops_config']
        self.call_id = self.jr_config['call_id']
        self.job_id = self.jr_config['job_id']
        self.func_key = self.jr_config['func_key']
        self.data_key = self.jr_config['data_key']
        self.data_byte_range = self.jr_config['data_byte_range']
        self.output_key = self.jr_config['output_key']

        self.stats = stats(self.jr_config['stats_filename'])

        prom_enabled = self.lithops_config['lithops'].get('monitoring')
        prom_config = self.lithops_config.get('prometheus', {})
        self.prometheus = PrometheusExporter(prom_enabled, prom_config)
コード例 #6
0
class JobRunner:
    def __init__(self, job, jobrunner_conn, internal_storage):
        self.job = job
        self.jobrunner_conn = jobrunner_conn
        self.internal_storage = internal_storage
        self.lithops_config = job.config

        self.output_key = create_output_key(job.executor_id, job.job_id,
                                            job.call_id)

        # Setup stats class
        self.stats = JobStats(self.job.stats_file)

        # Setup prometheus for live metrics
        prom_enabled = self.lithops_config['lithops'].get('telemetry')
        prom_config = self.lithops_config.get('prometheus', {})
        self.prometheus = PrometheusExporter(prom_enabled, prom_config)

    def _fill_optional_args(self, function, data):
        """
        Fills in those reserved, optional parameters that might be write to the function signature
        """
        func_sig = inspect.signature(function)

        if len(data) == 1 and 'future' in data:
            # Function chaining feature
            out = [
                data.pop('future').result(
                    internal_storage=self.internal_storage)
            ]
            data.update(verify_args(function, out, None)[0])

        if 'ibm_cos' in func_sig.parameters:
            if 'ibm_cos' in self.lithops_config:
                if self.internal_storage.backend == 'ibm_cos':
                    ibm_boto3_client = self.internal_storage.get_client()
                else:
                    ibm_boto3_client = Storage(config=self.lithops_config,
                                               backend='ibm_cos').get_client()
                data['ibm_cos'] = ibm_boto3_client
            else:
                raise Exception(
                    'Cannot create the ibm_cos client: missing configuration')

        if 'storage' in func_sig.parameters:
            data['storage'] = self.internal_storage.storage

        if 'rabbitmq' in func_sig.parameters:
            if 'rabbitmq' in self.lithops_config:
                rabbit_amqp_url = self.lithops_config['rabbitmq'].get(
                    'amqp_url')
                params = pika.URLParameters(rabbit_amqp_url)
                connection = pika.BlockingConnection(params)
                data['rabbitmq'] = connection
            else:
                raise Exception(
                    'Cannot create the rabbitmq client: missing configuration')

        if 'id' in func_sig.parameters:
            data['id'] = int(self.job.call_id)

    def _wait_futures(self, data):
        logger.info('Reduce function: waiting for map results')
        fut_list = data['results']
        wait(fut_list, self.internal_storage, download_results=True)
        results = [f.result() for f in fut_list if f.done and not f.futures]
        fut_list.clear()
        data['results'] = results

    def _load_object(self, data):
        """
        Loads the object in /tmp in case of object processing
        """
        extra_get_args = {}

        obj = data['obj']

        if hasattr(obj, 'bucket') and not hasattr(obj, 'path'):
            logger.info('Getting dataset from {}://{}/{}'.format(
                obj.backend, obj.bucket, obj.key))

            if obj.backend == self.internal_storage.backend:
                storage = self.internal_storage.storage
            else:
                storage = Storage(config=self.lithops_config,
                                  backend=obj.backend)

            if obj.data_byte_range is not None:
                extra_get_args['Range'] = 'bytes={}-{}'.format(
                    *obj.data_byte_range)
                logger.info('Chunk: {} - Range: {}'.format(
                    obj.part, extra_get_args['Range']))
                sb = storage.get_object(obj.bucket,
                                        obj.key,
                                        stream=True,
                                        extra_get_args=extra_get_args)
                wsb = WrappedStreamingBodyPartition(sb, obj.chunk_size,
                                                    obj.data_byte_range)
                obj.data_stream = wsb
            else:
                sb = storage.get_object(obj.bucket,
                                        obj.key,
                                        stream=True,
                                        extra_get_args=extra_get_args)
                obj.data_stream = sb

        elif hasattr(obj, 'url'):
            logger.info('Getting dataset from {}'.format(obj.url))
            if obj.data_byte_range is not None:
                range_str = 'bytes={}-{}'.format(*obj.data_byte_range)
                extra_get_args['Range'] = range_str
                logger.info('Chunk: {} - Range: {}'.format(
                    obj.part, extra_get_args['Range']))
            resp = requests.get(obj.url, headers=extra_get_args, stream=True)
            obj.data_stream = resp.raw

        elif hasattr(obj, 'path'):
            logger.info('Getting dataset from {}'.format(obj.path))
            with open(obj.path, "rb") as f:
                if obj.data_byte_range is not None:
                    extra_get_args['Range'] = 'bytes={}-{}'.format(
                        *obj.data_byte_range)
                    logger.info('Chunk: {} - Range: {}'.format(
                        obj.part, extra_get_args['Range']))
                    first_byte, last_byte = obj.data_byte_range
                    f.seek(first_byte)
                    buffer = io.BytesIO(f.read(last_byte - first_byte + 1))
                    sb = WrappedStreamingBodyPartition(buffer, obj.chunk_size,
                                                       obj.data_byte_range)
                else:
                    sb = io.BytesIO(f.read())
            obj.data_stream = sb

    # Decorator to execute pre-run and post-run functions provided via environment variables
    def prepost(func):
        def call(envVar):
            if envVar in os.environ:
                method = locate(os.environ[envVar])
                method()

        def wrapper_decorator(*args, **kwargs):
            call('PRE_RUN')
            value = func(*args, **kwargs)
            call('POST_RUN')
            return value

        return wrapper_decorator

    @prepost
    def run(self):
        """
        Runs the function
        """
        # self.stats.write('worker_jobrunner_start_tstamp', time.time())
        logger.debug("Process started")
        result = None
        exception = False
        fn_name = None

        try:
            func = pickle.loads(self.job.func)
            data = pickle.loads(self.job.data)

            if strtobool(os.environ.get('__LITHOPS_REDUCE_JOB', 'False')):
                self._wait_futures(data)
            elif is_object_processing_function(func):
                self._load_object(data)

            self._fill_optional_args(func, data)

            fn_name = func.__name__ if inspect.isfunction(func) \
                or inspect.ismethod(func) else type(func).__name__

            self.prometheus.send_metric(name='function_start',
                                        value=time.time(),
                                        type='gauge',
                                        labels=(('job_id', self.job.job_key),
                                                ('call_id', '-'.join([
                                                    self.job.job_key,
                                                    self.job.call_id
                                                ])), ('function_name', fn_name
                                                      or 'undefined')))

            logger.info("Going to execute '{}()'".format(str(fn_name)))
            print('---------------------- FUNCTION LOG ----------------------')
            function_start_tstamp = time.time()
            result = func(**data)
            function_end_tstamp = time.time()
            print('----------------------------------------------------------')
            logger.info("Success function execution")

            self.stats.write('worker_func_start_tstamp', function_start_tstamp)
            self.stats.write('worker_func_end_tstamp', function_end_tstamp)
            self.stats.write(
                'worker_func_exec_time',
                round(function_end_tstamp - function_start_tstamp, 8))

            # Check for new futures
            if result is not None:
                if isinstance(result, ResponseFuture) or isinstance(result, FuturesList) \
                   or (type(result) == list and len(result) > 0 and isinstance(result[0], ResponseFuture)):
                    self.stats.write('new_futures', pickle.dumps(result))
                    result = None
                else:
                    self.stats.write("result", True)
                    logger.debug("Pickling result")
                    output_dict = {'result': result}
                    pickled_output = pickle.dumps(output_dict)
                    self.stats.write('func_result_size', len(pickled_output))

            if result is None:
                logger.debug("No result to store")
                self.stats.write("result", False)
                self.stats.write('func_result_size', 0)

            # self.stats.write('worker_jobrunner_end_tstamp', time.time())

        except Exception:
            exception = True
            self.stats.write("exception", True)
            exc_type, exc_value, exc_traceback = sys.exc_info()
            print('----------------------- EXCEPTION !-----------------------')
            traceback.print_exc(file=sys.stdout)
            print('----------------------------------------------------------')

            try:
                logger.debug("Pickling exception")
                pickled_exc = pickle.dumps(
                    (exc_type, exc_value, exc_traceback))
                pickle.loads(
                    pickled_exc
                )  # this is just to make sure they can be unpickled
                self.stats.write("exc_info", str(pickled_exc))

            except Exception as pickle_exception:
                # Shockingly often, modules like subprocess don't properly
                # call the base Exception.__init__, which results in them
                # being unpickleable. As a result, we actually wrap this in a try/catch block
                # and more-carefully handle the exceptions if any part of this save / test-reload
                # fails
                self.stats.write("exc_pickle_fail", True)
                pickled_exc = pickle.dumps({
                    'exc_type': str(exc_type),
                    'exc_value': str(exc_value),
                    'exc_traceback': exc_traceback,
                    'pickle_exception': pickle_exception
                })
                pickle.loads(pickled_exc
                             )  # this is just to make sure it can be unpickled
                self.stats.write("exc_info", str(pickled_exc))

        finally:
            self.prometheus.send_metric(name='function_end',
                                        value=time.time(),
                                        type='gauge',
                                        labels=(('job_id', self.job.job_key),
                                                ('call_id', '-'.join([
                                                    self.job.job_key,
                                                    self.job.call_id
                                                ])), ('function_name', fn_name
                                                      or 'undefined')))

            store_result = strtobool(os.environ.get('STORE_RESULT', 'True'))
            if result is not None and store_result and not exception:
                output_upload_start_tstamp = time.time()
                logger.info("Storing function result - Size: {}".format(
                    sizeof_fmt(len(pickled_output))))
                self.internal_storage.put_data(self.output_key, pickled_output)
                output_upload_end_tstamp = time.time()
                self.stats.write(
                    "worker_result_upload_time",
                    round(
                        output_upload_end_tstamp - output_upload_start_tstamp,
                        8))
            self.jobrunner_conn.send("Finished")
            logger.info("Process finished")
コード例 #7
0
ファイル: jobrunner.py プロジェクト: repo-dpaes/lithops
class JobRunner:
    def __init__(self, job, jobrunner_conn, internal_storage):
        self.jobrunner_conn = jobrunner_conn
        self.internal_storage = internal_storage

        self.lithops_config = job.config
        self.executor_id = job.executor_id
        self.call_id = job.call_id
        self.job_id = job.job_id
        self.func_key = job.func_key
        self.data_key = job.data_key
        self.data_byte_range = job.data_byte_range
        self.output_key = create_output_key(JOBS_PREFIX, self.executor_id,
                                            self.job_id, self.call_id)

        # Setup stats class
        self.stats = stats(job.jr_stats_file)

        # Setup prometheus for live metrics
        prom_enabled = self.lithops_config['lithops'].get('monitoring')
        prom_config = self.lithops_config.get('prometheus', {})
        self.prometheus = PrometheusExporter(prom_enabled, prom_config)

        mode = self.lithops_config['lithops']['mode']
        self.customized_runtime = self.lithops_config[mode].get(
            'customized_runtime', False)

    def _get_function_and_modules(self):
        """
        Gets and unpickles function and modules from storage
        """
        logger.debug("Getting function and modules")
        func_download_start_tstamp = time.time()
        func_obj = None
        if self.customized_runtime:
            func_obj = self._get_func()
        else:
            func_obj = self.internal_storage.get_func(self.func_key)
        loaded_func_all = pickle.loads(func_obj)
        func_download_end_tstamp = time.time()
        self.stats.write(
            'worker_func_download_time',
            round(func_download_end_tstamp - func_download_start_tstamp, 8))

        return loaded_func_all

    def _get_func(self):
        func_path = '/'.join([LITHOPS_TEMP_DIR, self.func_key])
        with open(func_path, "rb") as f:
            return f.read()

    def _save_modules(self, module_data):
        """
        Save modules, before we unpickle actual function
        """
        if module_data:
            logger.debug("Writing Function dependencies to local disk")
            module_path = os.path.join(PYTHON_MODULE_PATH, self.executor_id,
                                       self.job_id, self.call_id)
            # shutil.rmtree(PYTHON_MODULE_PATH, True)  # delete old modules
            os.makedirs(module_path, exist_ok=True)
            sys.path.append(module_path)

            for m_filename, m_data in module_data.items():
                m_path = os.path.dirname(m_filename)

                if len(m_path) > 0 and m_path[0] == "/":
                    m_path = m_path[1:]
                to_make = os.path.join(module_path, m_path)
                try:
                    os.makedirs(to_make)
                except OSError as e:
                    if e.errno == 17:
                        pass
                    else:
                        raise e
                full_filename = os.path.join(to_make,
                                             os.path.basename(m_filename))

                with open(full_filename, 'wb') as fid:
                    fid.write(b64str_to_bytes(m_data))

    def _unpickle_function(self, pickled_func):
        """
        Unpickle function; it will expect modules to be there
        """
        return pickle.loads(pickled_func)

    def _load_data(self):
        """
        Loads iteradata
        """
        extra_get_args = {}
        if self.data_byte_range is not None:
            range_str = 'bytes={}-{}'.format(*self.data_byte_range)
            extra_get_args['Range'] = range_str

        logger.debug("Getting function data")
        data_download_start_tstamp = time.time()
        data_obj = self.internal_storage.get_data(
            self.data_key, extra_get_args=extra_get_args)
        loaded_data = pickle.loads(data_obj)
        data_download_end_tstamp = time.time()
        self.stats.write(
            'worker_data_download_time',
            round(data_download_end_tstamp - data_download_start_tstamp, 8))

        return loaded_data

    def _fill_optional_args(self, function, data):
        """
        Fills in those reserved, optional parameters that might be write to the function signature
        """
        func_sig = inspect.signature(function)

        if 'ibm_cos' in func_sig.parameters:
            if 'ibm_cos' in self.lithops_config:
                if self.internal_storage.backend == 'ibm_cos':
                    ibm_boto3_client = self.internal_storage.get_client()
                else:
                    ibm_boto3_client = Storage(config=self.lithops_config,
                                               backend='ibm_cos').get_client()
                data['ibm_cos'] = ibm_boto3_client
            else:
                raise Exception(
                    'Cannot create the ibm_cos client: missing configuration')

        if 'storage' in func_sig.parameters:
            data['storage'] = self.internal_storage.storage

        if 'rabbitmq' in func_sig.parameters:
            if 'rabbitmq' in self.lithops_config:
                rabbit_amqp_url = self.lithops_config['rabbitmq'].get(
                    'amqp_url')
                params = pika.URLParameters(rabbit_amqp_url)
                connection = pika.BlockingConnection(params)
                data['rabbitmq'] = connection
            else:
                raise Exception(
                    'Cannot create the rabbitmq client: missing configuration')

        if 'id' in func_sig.parameters:
            data['id'] = int(self.call_id)

    def _wait_futures(self, data):
        logger.info('Reduce function: waiting for map results')
        fut_list = data['results']
        wait_storage(fut_list, self.internal_storage, download_results=True)
        results = [f.result() for f in fut_list if f.done and not f.futures]
        fut_list.clear()
        data['results'] = results

    def _load_object(self, data):
        """
        Loads the object in /tmp in case of object processing
        """
        extra_get_args = {}

        if 'url' in data:
            url = data['url']
            logger.info('Getting dataset from {}'.format(url.path))
            if url.data_byte_range is not None:
                range_str = 'bytes={}-{}'.format(*url.data_byte_range)
                extra_get_args['Range'] = range_str
                logger.info('Chunk: {} - Range: {}'.format(
                    url.part, extra_get_args['Range']))
            resp = requests.get(url.path, headers=extra_get_args, stream=True)
            url.data_stream = resp.raw

        if 'obj' in data:
            obj = data['obj']
            logger.info('Getting dataset from {}://{}/{}'.format(
                obj.backend, obj.bucket, obj.key))

            if obj.backend == self.internal_storage.backend:
                storage = self.internal_storage.storage
            else:
                storage = Storage(config=self.lithops_config,
                                  backend=obj.backend)

            if obj.data_byte_range is not None:
                extra_get_args['Range'] = 'bytes={}-{}'.format(
                    *obj.data_byte_range)
                logger.info('Chunk: {} - Range: {}'.format(
                    obj.part, extra_get_args['Range']))
                sb = storage.get_object(obj.bucket,
                                        obj.key,
                                        stream=True,
                                        extra_get_args=extra_get_args)
                wsb = WrappedStreamingBodyPartition(sb, obj.chunk_size,
                                                    obj.data_byte_range)
                obj.data_stream = wsb
            else:
                sb = storage.get_object(obj.bucket,
                                        obj.key,
                                        stream=True,
                                        extra_get_args=extra_get_args)
                obj.data_stream = sb

    # Decorator to execute pre-run and post-run functions provided via environment variables
    def prepost(func):
        def call(envVar):
            if envVar in os.environ:
                method = locate(os.environ[envVar])
                method()

        def wrapper_decorator(*args, **kwargs):
            call('PRE_RUN')
            value = func(*args, **kwargs)
            call('POST_RUN')
            return value

        return wrapper_decorator

    @prepost
    def run(self):
        """
        Runs the function
        """
        # self.stats.write('worker_jobrunner_start_tstamp', time.time())
        logger.debug("Process started")
        result = None
        exception = False
        try:
            function = None

            if self.customized_runtime:
                function = self._get_function_and_modules()
            else:
                loaded_func_all = self._get_function_and_modules()
                self._save_modules(loaded_func_all['module_data'])
                function = self._unpickle_function(loaded_func_all['func'])
            data = self._load_data()

            if strtobool(os.environ.get('__LITHOPS_REDUCE_JOB', 'False')):
                self._wait_futures(data)
            elif is_object_processing_function(function):
                self._load_object(data)

            self._fill_optional_args(function, data)

            self.prometheus.send_metric(name='function_start',
                                        value=time.time(),
                                        labels=(('job_id', self.job_id),
                                                ('call_id', self.call_id),
                                                ('function_name',
                                                 function.__name__)))

            logger.info("Going to execute '{}()'".format(str(
                function.__name__)))
            print('---------------------- FUNCTION LOG ----------------------')
            function_start_tstamp = time.time()
            result = function(**data)
            function_end_tstamp = time.time()
            print('----------------------------------------------------------')
            logger.info("Success function execution")

            self.prometheus.send_metric(name='function_end',
                                        value=time.time(),
                                        labels=(('job_id', self.job_id),
                                                ('call_id', self.call_id),
                                                ('function_name',
                                                 function.__name__)))

            self.stats.write('worker_func_start_tstamp', function_start_tstamp)
            self.stats.write('worker_func_end_tstamp', function_end_tstamp)
            self.stats.write(
                'worker_func_exec_time',
                round(function_end_tstamp - function_start_tstamp, 8))

            # Check for new futures
            if result is not None:
                self.stats.write("result", True)
                if isinstance(result, ResponseFuture) or \
                   (type(result) == list and len(result) > 0 and isinstance(result[0], ResponseFuture)):
                    self.stats.write('new_futures', True)

                logger.debug("Pickling result")
                output_dict = {'result': result}
                pickled_output = pickle.dumps(output_dict)

            else:
                logger.debug("No result to store")
                self.stats.write("result", False)

            # self.stats.write('worker_jobrunner_end_tstamp', time.time())

        except Exception:
            exception = True
            self.stats.write("exception", True)
            exc_type, exc_value, exc_traceback = sys.exc_info()
            print('----------------------- EXCEPTION !-----------------------')
            traceback.print_exc(file=sys.stdout)
            print('----------------------------------------------------------')

            try:
                logger.debug("Pickling exception")
                pickled_exc = pickle.dumps(
                    (exc_type, exc_value, exc_traceback))
                pickle.loads(
                    pickled_exc
                )  # this is just to make sure they can be unpickled
                self.stats.write("exc_info", str(pickled_exc))

            except Exception as pickle_exception:
                # Shockingly often, modules like subprocess don't properly
                # call the base Exception.__init__, which results in them
                # being unpickleable. As a result, we actually wrap this in a try/catch block
                # and more-carefully handle the exceptions if any part of this save / test-reload
                # fails
                self.stats.write("exc_pickle_fail", True)
                pickled_exc = pickle.dumps({
                    'exc_type': str(exc_type),
                    'exc_value': str(exc_value),
                    'exc_traceback': exc_traceback,
                    'pickle_exception': pickle_exception
                })
                pickle.loads(pickled_exc
                             )  # this is just to make sure it can be unpickled
                self.stats.write("exc_info", str(pickled_exc))
        finally:
            store_result = strtobool(os.environ.get('STORE_RESULT', 'True'))
            if result is not None and store_result and not exception:
                output_upload_start_tstamp = time.time()
                logger.info("Storing function result - Size: {}".format(
                    sizeof_fmt(len(pickled_output))))
                self.internal_storage.put_data(self.output_key, pickled_output)
                output_upload_end_tstamp = time.time()
                self.stats.write(
                    "worker_result_upload_time",
                    round(
                        output_upload_end_tstamp - output_upload_start_tstamp,
                        8))
            self.jobrunner_conn.send("Finished")
            logger.info("Process finished")
コード例 #8
0
ファイル: invokers.py プロジェクト: kpavel/lithops
class Invoker:
    """
    Abstract invoker class
    """
    def __init__(self, config, executor_id, internal_storage, compute_handler,
                 job_monitor):
        log_level = logger.getEffectiveLevel()
        self.log_active = log_level != logging.WARNING
        self.log_level = LOGGER_LEVEL if not self.log_active else log_level

        self.config = config
        self.executor_id = executor_id
        self.storage_config = extract_storage_config(self.config)
        self.internal_storage = internal_storage
        self.compute_handler = compute_handler
        self.is_lithops_worker = is_lithops_worker()
        self.job_monitor = job_monitor

        self.workers = self.config['lithops'].get('workers')
        if self.workers:
            logger.debug('ExecutorID {} - Total workers: {}'.format(
                self.executor_id, self.workers))

        prom_enabled = self.config['lithops'].get('telemetry', False)
        prom_config = self.config.get('prometheus', {})
        self.prometheus = PrometheusExporter(prom_enabled, prom_config)

        self.mode = self.config['lithops']['mode']
        self.backend = self.config['lithops']['backend']
        self.runtime_name = self.config[self.backend]['runtime']

        self.customized_runtime = self.config[self.mode].get(
            'customized_runtime', False)

    def select_runtime(self, job_id, runtime_memory):
        """
        Return the runtime metadata
        """
        if self.mode == SERVERLESS:
            runtime_memory = runtime_memory or self.config[self.backend].get(
                'runtime_memory')
            runtime_timeout = self.config[self.backend].get('runtime_timeout')
        elif self.mode == STANDALONE:
            runtime_memory = None
            runtime_timeout = self.config[STANDALONE]['hard_dismantle_timeout']
        elif self.mode == LOCALHOST:
            runtime_memory = None
            runtime_timeout = None

        msg = ('ExecutorID {} | JobID {} - Selected Runtime: {} '.format(
            self.executor_id, job_id, self.runtime_name))
        msg = msg + '- {}MB'.format(runtime_memory) if runtime_memory else msg
        logger.info(msg)

        runtime_key = self.compute_handler.get_runtime_key(
            self.runtime_name, runtime_memory)
        runtime_meta = self.internal_storage.get_runtime_meta(runtime_key)

        if not runtime_meta:
            msg = 'Runtime {}'.format(self.runtime_name)
            msg = msg + ' with {}MB'.format(
                runtime_memory) if runtime_memory else msg
            logger.info(msg + ' is not yet installed')
            runtime_meta = self.compute_handler.create_runtime(
                self.runtime_name, runtime_memory, runtime_timeout)
            runtime_meta['runtime_timeout'] = runtime_timeout
            self.internal_storage.put_runtime_meta(runtime_key, runtime_meta)

        # Verify python version and lithops version
        if lithops_version != runtime_meta['lithops_version']:
            raise Exception(
                "Lithops version mismatch. Host version: {} - Runtime version: {}"
                .format(lithops_version, runtime_meta['lithops_version']))

        py_local_version = version_str(sys.version_info)
        py_remote_version = runtime_meta['python_version']
        if py_local_version != py_remote_version:
            raise Exception(
                ("The indicated runtime '{}' is running Python {} and it "
                 "is not compatible with the local Python version {}").format(
                     self.runtime_name, py_remote_version, py_local_version))

        return runtime_meta

    def _create_payload(self, job):
        """
        Creates the default pyload dictionary
        """
        payload = {
            'config': self.config,
            'chunksize': job.chunksize,
            'log_level': self.log_level,
            'func_key': job.func_key,
            'data_key': job.data_key,
            'extra_env': job.extra_env,
            'total_calls': job.total_calls,
            'execution_timeout': job.execution_timeout,
            'data_byte_ranges': job.data_byte_ranges,
            'executor_id': job.executor_id,
            'job_id': job.job_id,
            'job_key': job.job_key,
            'workers': self.workers,
            'call_ids': None,
            'host_submit_tstamp': time.time(),
            'lithops_version': lithops_version,
            'runtime_name': job.runtime_name,
            'runtime_memory': job.runtime_memory,
            'worker_processes': job.worker_processes
        }

        return payload

    def _run_job(self, job):
        """
        Run a job
        """
        if self.customized_runtime:
            logger.debug(
                'ExecutorID {} | JobID {} - Customized runtime activated'.
                format(job.executor_id, job.job_id))
            job.runtime_name = self.runtime_name
            extend_runtime(job, self.compute_handler, self.internal_storage)
            self.runtime_name = job.runtime_name

        logger.info('ExecutorID {} | JobID {} - Starting function '
                    'invocation: {}() - Total: {} activations'.format(
                        job.executor_id, job.job_id, job.function_name,
                        job.total_calls))

        logger.debug('ExecutorID {} | JobID {} - Chunksize:'
                     ' {} - Worker processes: {}'.format(
                         job.executor_id, job.job_id, job.chunksize,
                         job.worker_processes))

        self.prometheus.send_metric(name='job_total_calls',
                                    value=job.total_calls,
                                    type='counter',
                                    labels=(('job_id_', job.job_key),
                                            ('function_name',
                                             job.function_name)))

        self.prometheus.send_metric(name='job_runtime_memory',
                                    value=job.runtime_memory or 0,
                                    type='counter',
                                    labels=(('job_id_', job.job_key),
                                            ('function_name',
                                             job.function_name)))

        try:
            job.runtime_name = self.runtime_name
            self._invoke_job(job)
        except (KeyboardInterrupt, Exception) as e:
            self.stop()
            raise e

        log_file = os.path.join(LOGS_DIR, job.job_key + '.log')
        logger.info(
            "ExecutorID {} | JobID {} - View execution logs at {}".format(
                job.executor_id, job.job_id, log_file))

        # Create all futures
        futures = []
        for i in range(job.total_calls):
            call_id = "{:05d}".format(i)
            fut = ResponseFuture(call_id, job, job.metadata.copy(),
                                 self.storage_config)
            fut._set_state(ResponseFuture.State.Invoked)
            futures.append(fut)

        job.futures = futures

        return futures

    def stop(self):
        """
        Stop invoker-related processes
        """
        pass