Example #1
0
class SchedulerACS(ThreadManager):
    has_active = False
    lock = Lock()

    def __init__(self, base_config, interrupt_sig):
        self.log = LogParser(base_config=base_config, title=__name__)
        # self.log.info([['y', ' - SchedulerACS - '], ['g', base_config.site_type]])

        if SchedulerACS.has_active:
            raise Exception('Can not instantiate SchedulerACS more than once...')
        else:
            SchedulerACS.has_active = True

        self.base_config = base_config
        self.site_type = self.base_config.site_type
        self.clock_sim = self.base_config.clock_sim
        self.inst_data = self.base_config.inst_data

        self.interrupt_sig = interrupt_sig

        self.tel_ids = self.inst_data.get_inst_ids(inst_types=['LST', 'MST', 'SST'])

        self.no_sub_arr_name = self.base_config.no_sub_arr_name

        self.class_name = self.__class__.__name__
        self.redis = RedisManager(
            name=self.class_name, base_config=self.base_config, log=self.log
        )

        self.debug = not True
        self.expire_sec = 86400  # one day
        # self.expire_sec = 5

        self.MockSched = None

        # self.client = PySimpleClient()
        # self.supervisor = self.client.getComponent('ArraySupervisor')
        # self.log.info([['y',' - SchedulerACS - '],['p','got supervisor!']])

        self.phases_exe = dict()
        self.phases_exe['start'] = [
            'run_config_mount',
            'run_config_camera',
            'run_config_daq',
            'run_config_mirror',
        ]
        self.phases_exe['during'] = [
            'run_take_data',
        ]
        self.phases_exe['finish'] = [
            'run_finish_mount',
            'run_finish_camera',
            'run_finish_daq',
        ]

        self.az_min_max = [-180, 180]

        self.loop_sleep_sec = 3

        rnd_seed = get_rnd_seed()
        # rnd_seed = 10987268332
        self.rnd_gen = Random(rnd_seed)

        self.setup_threads()

        self.MockSched = MockSched(
            base_config=self.base_config, interrupt_sig=self.interrupt_sig
        )

        # ------------------------------------------------------------------
        # temporary hack to be consistent with SchedulerStandalone
        # ------------------------------------------------------------------
        self.external_events = []
        self.external_clock_events = []
        external_generate_clock_events(self)
        external_generate_events(self)

        return

    # ---------------------------------------------------------------------------
    def setup_threads(self):
        self.add_thread(target=self.loop_main)
        return

    # ------------------------------------------------------------------
    def reset_blocks(self):
        debug_tmp = False
        # debug_tmp = True

        if debug_tmp:
            self.log.info([['p', ' - SchedulerACS.reset_blocks() ...']])

        if self.MockSched is None:
            sleep(0.1)
            self.log.debug([[
                'r', ' - no MockSched ... will try to reset_blocks() again ...'
            ]])
            self.reset_blocks()
            return

        night_duration_sec = self.clock_sim.get_night_duration_sec()

        sched_blocks = self.MockSched.get_blocks()

        obs_block_ids = dict()
        obs_block_ids['wait'] = []
        obs_block_ids['run'] = []
        obs_block_ids['done'] = []
        obs_block_ids['cancel'] = []
        obs_block_ids['fail'] = []

        blocks_run = []
        active = sched_blocks['active']

        pipe = self.redis.get_pipe()

        for sched_blk_id, schBlock in sched_blocks['blocks'].items():

            sub_array_tels = (
                schBlock['sched_block'].config.instrument.sub_array.telescopes
            )
            tel_ids = [x.id for x in sub_array_tels]

            obs_blocks = schBlock['sched_block'].observation_blocks

            # get the total duration of all obs blocks
            block_duration_sec = 0
            for n_obs_block_now in range(len(obs_blocks)):
                obs_block_id = obs_blocks[n_obs_block_now].id
                block_duration_sec += (sched_blocks['metadata'][obs_block_id]['duration'])

            targets = get_rnd_targets(
                self=self,
                night_duration_sec=night_duration_sec,
                block_duration_sec=block_duration_sec,
            )

            for n_obs_block_now in range(len(obs_blocks)):
                obs_block_now = obs_blocks[n_obs_block_now]
                obs_block_id = obs_block_now.id
                # trg_id = obs_block_now.src.id
                # coords = obs_block_now.src.coords.horizontal

                sched_metadata = sched_blocks['metadata'][obs_block_id]
                timestamp = sched_metadata['timestamp']
                metadata = sched_metadata['metadata']
                status = sched_metadata['status']
                phases = sched_metadata['phases']
                duration = sched_metadata['duration']
                start_time_sec_plan = sched_metadata['start_time_sec_plan']
                start_time_sec_exe = sched_metadata['start_time_sec_exe']

                start_time_sec = (
                    start_time_sec_plan
                    if start_time_sec_exe is None else start_time_sec_exe
                )

                # state of ob
                if status == sb.OB_PENDING:
                    state = 'wait'
                elif status == sb.OB_RUNNING:
                    state = 'run'
                elif status == sb.OB_CANCELED:
                    state = 'cancel'
                elif status == sb.OB_FAILED:
                    state = 'fail'
                else:
                    state = 'done'

                # final sanity check
                if state == 'run' and sched_blk_id not in active:
                    state = 'done'

                run_phase = []
                if state == 'run':
                    for p in phases:
                        if p.status == sb.OB_RUNNING:
                            phase_name = 'run_' + p.name
                            for phases_exe in self.phases_exe:
                                if phase_name in self.phases_exe[phases_exe]:
                                    run_phase.append(phase_name)

                    if debug_tmp:
                        self.log.debug([
                            ['b', ' -- run_phase - '],
                            ['y', run_phase, ' '],
                            ['b', tel_ids],
                        ])

                can_run = True
                if state == 'cancel' or state == 'fail':
                    can_run = (self.clock_sim.get_time_now_sec() >= start_time_sec)

                exe_state = {'state': state, 'can_run': can_run}

                time = {
                    'start': start_time_sec,
                    'duration': duration,
                }
                time['end'] = time['start'] + time['duration']

                telescopes = {
                    'large': {
                        'min': int(len(list(filter(lambda x: 'L' in x, tel_ids))) / 2),
                        'max': 4,
                        'ids': list(filter(lambda x: 'L' in x, tel_ids))
                    },
                    'medium': {
                        'min': int(len(list(filter(lambda x: 'M' in x, tel_ids))) / 2),
                        'max': 25,
                        'ids': list(filter(lambda x: 'M' in x, tel_ids))
                    },
                    'small': {
                        'min': int(len(list(filter(lambda x: 'S' in x, tel_ids))) / 2),
                        'max': 70,
                        'ids': list(filter(lambda x: 'S' in x, tel_ids))
                    }
                }

                pointings = get_rnd_pointings(
                    self=self,
                    tel_ids=tel_ids,
                    targets=targets,
                    sched_block_id=sched_blk_id,
                    obs_block_id=obs_block_id,
                    n_obs_now=n_obs_block_now,
                )

                block = dict()
                block['sched_block_id'] = sched_blk_id
                block['obs_block_id'] = obs_block_id
                block['time'] = time
                block['metadata'] = metadata
                block['timestamp'] = timestamp
                block['telescopes'] = telescopes
                block['exe_state'] = exe_state
                block['run_phase'] = run_phase
                block['targets'] = targets
                block['pointings'] = pointings
                block['tel_ids'] = tel_ids

                if state == 'run':
                    blocks_run += [block]

                obs_block_ids[state].append(obs_block_id)

                pipe.set(
                    name=obs_block_id,
                    data=block,
                    expire_sec=self.expire_sec,
                    packed=True
                )

        pipe.set(name='obs_block_ids_' + 'wait', data=obs_block_ids['wait'], packed=True)
        pipe.set(name='obs_block_ids_' + 'run', data=obs_block_ids['run'], packed=True)
        pipe.set(name='obs_block_ids_' + 'done', data=obs_block_ids['done'], packed=True)
        pipe.set(
            name='obs_block_ids_' + 'cancel', data=obs_block_ids['cancel'], packed=True
        )
        pipe.set(name='obs_block_ids_' + 'fail', data=obs_block_ids['fail'], packed=True)

        pipe.execute()

        update_sub_arrs(self=self, blocks=blocks_run)

        return

    # # ------------------------------------------------------------------
    # #
    # # ------------------------------------------------------------------
    # def update_sub_arrs(self, blocks=None):
    #     # inst_pos = self.redis.h_get_all(name='inst_pos')

    #     pipe = self.redis.get_pipe()

    #     if blocks is None:
    #         obs_block_ids = self.redis.get(
    #             name=('obs_block_ids_' + 'run'), packed=True, default_val=[]
    #         )
    #         for obs_block_id in obs_block_ids:
    #             pipe.get(obs_block_id)

    #         blocks = pipe.execute(packed=True)

    #     # sort so last is first in the list (latest sub-array defined gets the telescope)
    #     blocks = sorted(
    #         blocks, cmp=lambda a, b: int(b['timestamp']) - int(a['timestamp'])
    #     )
    #     # print [a['timestamp'] for a in blocks]

    #     sub_arrs = []
    #     all_tel_ids_in = []
    #     for n_block in range(len(blocks)):
    #         block_tel_ids = blocks[n_block]['tel_ids']
    #         pnt_id = blocks[n_block]['point_id']
    #         pointing_name = blocks[n_block]['pointing_name']

    #         # compile the telescope list for this block
    #         tels = []
    #         for id_now in block_tel_ids:
    #             if id_now not in all_tel_ids_in:
    #                 all_tel_ids_in.append(id_now)
    #                 tels.append({'id': id_now})

    #         # add the telescope list for this block
    #         sub_arrs.append({'id': pnt_id, 'N': pointing_name, 'children': tels})

    #     # ------------------------------------------------------------------
    #     # now take care of all free telescopes
    #     # ------------------------------------------------------------------
    #     tels = []
    #     all_tel_ids = [x for x in self.tel_ids if x not in all_tel_ids_in]
    #     for id_now in all_tel_ids:
    #         tels.append({'id': id_now})

    #     sub_arrs.append({'id': self.no_sub_arr_name, 'children': tels})

    #     # ------------------------------------------------------------------
    #     # for now - a simple/stupid solution, where we write the sub-arrays and publish each
    #     # time, even if the content is actually the same ...
    #     # ------------------------------------------------------------------
    #     self.redis.set(name='sub_arrs', data=sub_arrs, packed=True)
    #     self.redis.publish(channel='sub_arrs')

    #     return

    # ------------------------------------------------------------------
    def loop_main(self):
        self.log.info([['g', ' - starting SchedulerACS.loop_main ...']])

        pipe = self.redis.get_pipe()

        pipe.set(name='obs_block_ids_' + 'wait', data='')
        pipe.set(name='obs_block_ids_' + 'run', data='')
        pipe.set(name='obs_block_ids_' + 'done', data='')
        pipe.set(name='obs_block_ids_' + 'cancel', data='')
        pipe.set(name='obs_block_ids_' + 'fail', data='')

        pipe.execute()

        update_sub_arrs(self=self, blocks=[])

        while self.can_loop():
            sleep(self.loop_sleep_sec)

            with SchedulerACS.lock:
                self.reset_blocks()

        self.log.info([['c', ' - ending SchedulerACS.loop_main ...']])

        return
Example #2
0
class BaseWidget():
    # all session ids for this user/widget
    widget_group_sess = dict()

    # ------------------------------------------------------------------
    def __init__(self, widget_id=None, sm=None, *args, **kwargs):
        self.log = LogParser(base_config=sm.base_config, title=__name__)

        # the parent of this widget
        self.sm = sm
        # the shared basic configuration class
        self.base_config = self.sm.base_config
        self.sess_id = self.sm.sess_id
        self.user_id = self.sm.user_id

        # the id of this instance
        self.widget_id = widget_id
        # widget-class and widget group names
        self.widget_type = self.__class__.__name__
        # for common threading
        self.widget_group = self.sm.user_group_id + '_' + self.widget_type

        # redis interface
        self.redis = RedisManager(
            name=self.widget_type, base_config=self.base_config, log=self.log
        )

        # turn on periodic data updates
        self.do_data_updates = True
        # some etra logging messages for this module
        self.log_send_packet = False

        # fixed or dynamic icon
        self.n_icon = None
        # self.n_icon = -1
        # self.icon_id = -1

        # list of utility classes to loop over
        self.my_utils = dict()

        # arguments given to the setup function, to later be
        # passed to utils if needed
        self.setup_args = None

        return

    # ------------------------------------------------------------------
    async def setup(self, *args):
        self.setup_args = args

        widget_info = await self.sm.get_lazy_widget_info(
            sess_id=self.sess_id,
            widget_id=self.widget_id,
        )
        if widget_info is None:
            return

        if self.n_icon is None:
            self.n_icon = widget_info['n_icon']
            # self.icon_id = widget_info['icon_id']

        # override the global logging variable with a
        # name corresponding to the current session id
        self.log = LogParser(
            base_config=self.base_config,
            title=(
                str(self.user_id) + '/' + str(self.sm.sess_id) + '/' + __name__ + '/'
                + self.widget_id
            ),
        )

        return

    # ------------------------------------------------------------------
    async def util_setup(self, data):
        """load a utility class and set it up
        """

        util_id = data['util_id']
        util_type = data['util_type']

        self.log.debug([
            ['b', ' - util_setup: '],
            ['y', util_type],
            ['b', ' with '],
            ['y', util_id],
            ['b', ' to '],
            ['o', self.widget_id],
        ])

        widget_info = await self.sm.get_lazy_widget_info(
            sess_id=self.sess_id,
            widget_id=self.widget_id,
        )
        if widget_info is None:
            return

        if util_id not in widget_info['util_ids']:
            widget_info['util_ids'] += [util_id]

        # dynamic loading of the module
        util_source = self.sm.util_module_dir + '.' + util_type
        util_module = importlib.import_module(util_source, package=None)
        util_cls = getattr(util_module, util_type)

        # instantiate the util class
        self.my_utils[util_id] = util_cls(util_id=util_id, parent=self)

        # add a lock for initialisation. needed in case of restoration
        # of sessions, in order to make sure the initialisation
        # method is called before any other
        expire_sec = self.my_utils[util_id].sm.get_expite_sec(name='widget_init_expire')

        self.my_utils[util_id].locker.semaphores.add(
            name=self.get_util_lock_name(util_id),
            key=util_id,
            expire_sec=expire_sec,
        )

        # run the setup function of the util
        await self.my_utils[util_id].setup(self.setup_args)

        self.redis.h_set(name='ws;widget_info', key=self.widget_id, data=widget_info)

        return

    # ------------------------------------------------------------------
    async def util_func(self, data):
        """execute util methods following client events
        """

        util_id = data['util_id']
        method_name = data['method_name']
        method_args = data['method_args'] if 'method_args' in data else dict()

        # is this the initialisation method
        is_init_func = (method_name == 'util_init')

        # in case this is the first call, the util should be loaded
        if util_id not in self.my_utils:
            await self.util_setup(data)

            # if the first call is not the initialisation method, this is probably
            # a restored session. in this case we send the client a request to
            # send the initialisation method request, and the current method call will
            # be blocked by the initialisation lock
            if not is_init_func:
                opt_in = {
                    'widget': self,
                    'event_name': ('ask_init_util;' + util_id),
                }
                await self.sm.emit_widget_event(opt_in=opt_in)

        # block non-initialisation calls if initialisation has not finished yet
        if not is_init_func:
            max_lock_sec = self.sm.get_expite_sec(
                name='widget_init_expire',
                is_lock_check=True,
            )
            await self.my_utils[util_id].locker.semaphores.async_block(
                is_locked=await self.is_util_init_locked(util_id),
                max_lock_sec=max_lock_sec,
            )

        # execute the requested method
        init_func = getattr(self.my_utils[util_id], method_name)
        await init_func(method_args)

        # remove the initialisation lock
        if is_init_func:
            self.my_utils[util_id].locker.semaphores.remove(
                name=self.get_util_lock_name(util_id),
                key=util_id,
            )

        return

    # ------------------------------------------------------------------
    def get_util_lock_name(self, util_id):
        """a unique name for initialisation locking
        """

        lock_name = (
            'ws;base_widget;util_func;' + self.my_utils[util_id].class_name + ';'
            + self.my_utils[util_id].util_id
        )

        return lock_name

    # ------------------------------------------------------------------
    async def is_util_init_locked(self, util_id):
        """a function which checks if the initialisation is locked
        """
        async def is_locked():
            locked = self.my_utils[util_id].locker.semaphores.check(
                name=self.get_util_lock_name(util_id),
                key=util_id,
            )
            return locked

        return is_locked

    # ------------------------------------------------------------------
    async def back_from_offline(self, *args):
        """interface function for back-from-offline events
        """

        # check if any util is missing (e.g., in case we are back
        # after a session recovery) and ask the client to respond

        widget_info = self.redis.h_get(name='ws;widget_info', key=self.widget_id)
        util_ids = widget_info['util_ids']
        util_ids = [u for u in util_ids if u not in self.my_utils.keys()]

        for util_id in util_ids:
            opt_in = {
                'widget': self,
                'event_name': ('ask_init_util;' + util_id),
            }
            await self.sm.emit_widget_event(opt_in=opt_in)

        # loop over utils
        for util_now in self.my_utils.values():
            await util_now.back_from_offline(args)

        return