def task2(self, session, params=None): """ TASK (non-blocking) In a non-blocking implementation of a Task or Process function, we may: - use twisted functions freely, as we are running in the reactor thread. - make blocking i/o requests by wrapping them in deferToThread. We may not: - cause the function to block or sleep using non-twisted methods. Within twisted, asynchronous routines that perform blocking operations (without blocking the reactor thread) will return a Deferred object, immediately, instead of the result of the request you have made. To suspend the function until the actual result is ready, you should: - decorate the function with @inlineCallbacks. - use the idiom "x = yield Function(...)" idiom. This latter idiom is used on the dsleep function; not because we care what the function returned but because we definitely want to wait until that function has completed before proceeding with the next step in our operation. """ for step in range(5): session.add_message('task2-nonblocking step %i' % step) yield dsleep(1) return True, 'task-2 nonblocking complete.'
def _run_script(self, script, args, log, session): """ Runs a pysmurf control script. Can only run from the reactor. Arguments ---------- script: string path to the script you wish to run args: list, optional List of command line arguments to pass to the script. Defaults to []. log: string/bool, optional Determines if and how the process's stdout should be logged. You can pass the path to a logfile, True to use the agent's log, or False to not log at all. """ with self.protocol_lock.acquire_timeout(0, job=script) as acquired: if not acquired: return False, "The requested script cannot be run because " \ "script {} is already running".format(self.protocol_lock.job) self.current_session = session try: # IO is not really safe from the reactor thread, so we possibly # need to find another way to do this if people use it and it # causes problems... logger = None if isinstance(log, str): self.log.info("Logging output to file {}".format(log)) log_file = yield threads.deferToThread(open, log, 'a') logger = Logger( observer=FileLogObserver(log_file, log_formatter)) elif log: # If log==True, use agent's logger logger = self.log self.prot = PysmurfScriptProtocol(script, log=logger) self.prot.deferred = Deferred() python_exec = sys.executable cmd = [python_exec, '-u', script] + list(map(str, args)) self.log.info("{exec}, {cmd}", exec=python_exec, cmd=cmd) reactor.spawnProcess(self.prot, python_exec, cmd, env=os.environ) rc = yield self.prot.deferred return (rc == 0 ), "Script has finished with exit code {}".format(rc) finally: # Sleep to allow any remaining messages to be put into the # session var yield dsleep(1.0) self.current_session = None
def dsleep(self): """ Sleeps in a non-blocking way by returning the deferred created by twisted's sleep method. """ now = time.time() if now < self.next_sample: yield dsleep(self.next_sample - now) self._set_next_sample()
def start_acq(self, session, params=None): """start_acq(params=None) OCS Process for fetching values from the M1000 via SNMP. The session.data object stores each unique OID with its latest status, decoded value, and the last time the value was retrived. This will look like the example here:: >>> session.data {"mbgLtNgRefclockLeapSecondDate_1": {"status": "not announced", "lastGet":1598626144.5365012}, "mbgLtNgPtpPortState_1": {"status": 3, "description": "disabled", "lastGet": 1598543397.689727}, "mbgLtNgNtpCurrentState_0": {"status": 1, "description": "not synchronized", "lastGet": 1598543363.289597}, "mbgLtNgRefclockState_1": {"status": 2, "description": "not synchronized", "lastGet": 1598543359.6326838}, "mbgLtNgSysPsStatus_1": {"status": 2, "description": "up", "lastGet": 1598543359.6326838}, "mbgLtNgSysPsStatus_2": {"status": 2, "description": "up", "lastGet": 1598543359.6326838}, "mbgLtNgEthPortLinkState_1": {"status": 1, "description": "up", "lastGet": 1598543359.6326838}} Note that session.data is populated within the self.meinberg.run_snmp_get() call. """ if params is None: params = {} self.is_streaming = True while self.is_streaming: yield self.meinberg.run_snmp_get(session) self.log.debug("{data}", data=session.data) yield dsleep(0.1) return True, "Finished Recording"
def stop_and_clear(self, session, params=None): """TASK stop_and_clear Changes the azimuth and elevation modes to Stop and clears points uploaded to the stack. """ ok, msg = self.try_set_job('control') if not ok: self.set_job_done('control') yield dsleep(0.1) self.try_set_job('control') self.log.info('try_set_job ok') yield self.acu.stop() self.log.info('Stop called') yield dsleep(5) yield self.acu.http.Command('DataSets.CmdTimePositionTransfer', 'Clear Stack') yield dsleep(0.1) self.log.info('Cleared stack.') self.set_job_done('control') return True, 'Job completed'
def set_boresight(self, session, params=None): """TASK set_boresight Moves the telescope to a particular third-axis angle. Params: b (float): destination angle for boresight rotation """ ok, msg = self.try_set_job('control') if not ok: return ok, msg bs_destination = params.get('b') yield self.acu.stop() yield dsleep(5) yield self.acu.go_3rd_axis(bs_destination) current_position = self.data['status']['summary']\ ['Boresight_current_position'] while current_position != bs_destination: yield dsleep(1) current_position = self.data['status']['summary']\ ['Boresight_current_position'] yield self.acu.stop() self.set_job_done('control') return True, 'Moved to new 3rd axis position'
def delay_task(self, session, params): """delay_task(delay=5, succeed=True) **Task** - Sleep (delay) for the requested number of seconds. This can run simultaneously with the acq Process. This Task should run in the reactor thread. Args: delay (float, optional): Time to wait before returning, in seconds. Defaults to 5. succeed (bool, optional): Whether to return success or not. Defaults to True. Notes: The session data will be updated with the requested delay as well as the time elapsed so far, for example:: >>> response.session['data'] {'requested_delay': 5., 'delay_so_far': 1.2} """ delay = params['delay'] succeed = params['succeed'] is True session.data = {'requested_delay': delay, 'delay_so_far': 0} session.set_status('running') t0 = time.time() while True: session.data['delay_so_far'] = time.time() - t0 sleep_time = min(0.5, delay - session.data['delay_so_far']) if sleep_time < 0: break yield dsleep(sleep_time) return succeed, 'Exited after %.1f seconds' % session.data[ 'delay_so_far']
def acq(self, session, params=None): """acq() **Process** - Fetch values from the M1000 via SNMP. Notes: The session.data object stores each unique OID with its latest status, decoded value, and the last time the value was retrived. This will look like the example here:: >>> response.session['data'] {"mbgLtNgRefclockLeapSecondDate_1": {"status": "not announced", "lastGet":1598626144.5365012}, "mbgLtNgPtpPortState_1": {"status": 3, "description": "disabled", "lastGet": 1598543397.689727}, "mbgLtNgNtpCurrentState_0": {"status": 1, "description": "not synchronized", "lastGet": 1598543363.289597}, "mbgLtNgRefclockState_1": {"status": 2, "description": "not synchronized", "lastGet": 1598543359.6326838}, "mbgLtNgSysPsStatus_1": {"status": 2, "description": "up", "lastGet": 1598543359.6326838}, "mbgLtNgSysPsStatus_2": {"status": 2, "description": "up", "lastGet": 1598543359.6326838}, "mbgLtNgEthPortLinkState_1": {"status": 1, "description": "up", "lastGet": 1598543359.6326838} "m1000_connection": {"last_attempt": 1598543359.6326838, "connected": True}} Note that session.data is populated within the :func:`MeinbergSNMP.run_snmp_get` call. """ if params is None: params = {} # Make an initial attempt at connection. # Allows us to fail early if misconfigured. yield self.meinberg.run_snmp_get(session) if not self.meinberg.oid_cache['m1000_connection'].get('connected', False): self.log.error('No initial SNMP response.') self.log.error('Either there is a network connection issue, ' + 'or maybe you are using the wrong SNMP ' + 'version. Either way, we are exiting.') reactor.callFromThread(reactor.stop) return False, 'acq process failed - No connection to M1000' self.is_streaming = True while self.is_streaming: yield self.meinberg.run_snmp_get(session) self.log.debug("{data}", data=session.data) yield dsleep(1) return True, "Finished Recording"
def main(self, session: ocs_agent.OpSession, params): """main(test_mode=False) **Process** - Main run process for the Registry agent. This will loop and keep track of which agents have expired. It will keep track of current active agents in the session.data variable so it can be seen by clients. Parameters: test_mode (bool, optional): Run the main Process loop only once. This is meant only for testing. Default is False. Notes: The session data object for this process will be a dictionary containing the encoded RegisteredAgent objects for each agent observed during the lifetime of the Registry. For instance, this might look like:: >>> response.session['data'] {'observatory.aggregator': {'expired': False, 'last_updated': 1583179794.5175, 'time_expired': None}, 'observatory.faker1': {'expired': False, 'last_updated': 1583179795.072248, 'time_expired': None}, 'observatory.faker2': {'expired': True, 'last_updated': 1583179777.0211036, 'time_expired': 1583179795.3862052}} """ session.set_status('starting') self._run = True session.set_status('running') while self._run: yield dsleep(1) for k, agent in self.registered_agents.items(): if time.time() - agent.last_updated > self.agent_timeout: agent.expire() session.data = { k: agent.encoded() for k, agent in self.registered_agents.items() } for addr, agent in self.registered_agents.items(): msg = { 'block_name': addr, 'timestamp': time.time(), 'data': {} } for op_name, op_code in agent.op_codes.items(): field = f'{addr}_{op_name}' field = field.replace('.', '_') field = field.replace('-', '_') field = Feed.enforce_field_name_rules(field) try: Feed.verify_data_field_string(field) except ValueError as e: self.log.warn(f"Improper field name: {field}\n{e}") continue msg['data'][field] = op_code if msg['data']: self.agent.publish_to_feed('agent_operations', msg) if params['test_mode']: break return True, "Stopped registry main process"
def generate_scan(self, session, params=None): """ Scan generator, currently only works for constant-velocity az scans with fixed elevation. Args: scantype (str): type of scan you are generating. For dev, preset to 'linear'. stop_iter (float): how many times the generator should generate a new set of points before forced to stop az_endpoint1 (float): first endpoint of a linear azimuth scan az_endpoint2 (float): second endpoint of a linear azimuth scan az_speed (float): azimuth speed for constant-velocity scan acc (float): turnaround acceleration for a constant-velocity scan el_endpoint1 (float): first endpoint of elevation motion el_endpoint2 (float): second endpoint of elevation motion. For dev, currently both el endpoints should be equal el_speed (float): speed of motion for a scan with changing elevation. For dev, currently set to 0.0 """ ok, msg = self.try_set_job('control') if not ok: return ok, msg self.log.info('try_set_job ok') # scantype = params.get('scantype') scantype = 'linear' stop_iter = params.get('stop_iter') az_endpoint1 = params.get('az_endpoint1') az_endpoint2 = params.get('az_endpoint2') az_speed = params.get('az_speed') acc = params.get('acc') el_endpoint1 = params.get('el_endpoint1') el_endpoint2 = params.get('el_endpoint2') el_speed = params.get('el_speed') self.log.info('scantype is ' + str(scantype)) yield self.acu.stop() if scantype != 'linear': self.log.warn('Scan type not supported') return False g = sh.generate(stop_iter, az_endpoint1, az_endpoint2, az_speed, acc, el_endpoint1, el_endpoint2, el_speed) self.acu.mode('ProgramTrack') while True: lines = next(g) current_lines = lines group_size = 250 while len(current_lines): upload_lines = current_lines[:group_size] text = ''.join(upload_lines) current_lines = current_lines[group_size:] free_positions = self.data['status']['summary']\ ['Qty_of_free_program_track_stack_positions'] while free_positions < 5099: yield dsleep(0.1) free_positions = self.data['status']['summary']\ ['Qty_of_free_program_track_stack_positions'] yield self.acu.http.UploadPtStack(text) yield self.acu.stop() self.set_job_done('control') return True, 'Track generation ended cleanly'
def run_specified_scan(self, session, params=None): """TASK run_specifid_scan Upload and execute a scan pattern. The pattern may be specified by a numpy file, parameters for a linear scan in one direction, or a linear scan with a turnaround. Params: scantype (str): the type of scan information you are uploading. Options are 'from_file', 'linear_1dir', or 'linear_turnaround'. Optional params: filename (str): full path to desired numpy file. File contains an array of three lists ([list(times), list(azimuths), list(elevations)]). Times begin from 0.0. Applies to scantype 'from_file'. azpts (tuple): spatial endpoints of the azimuth scan. Applies to scantype 'linear_1dir' (2 values) and 'linear_turnaround' (3 values). el (float): elevation for a linear velocity azimuth scan. Applies to scantype 'linear_1dir' and 'linear_turnaround'. azvel (float): velocity of the azimuth axis in a linear velocity azimuth scan. Applies to scantype 'linear_1dir' and 'linear_turnaround'. acc (float): acceleration of the turnaround for a linear velocity scan with a turnaround. Applies to scantype 'linear_turnaround'. ntimes (int): number of times the platform traverses between azimuth endpoints for a 'linear_turnaround' scan. """ ok, msg = self.try_set_job('control') if not ok: return ok, msg self.log.info('try_set_job ok') scantype = params.get('scantype') if scantype == 'from_file': filename = params.get('filename') times, azs, els, vas, ves, azflags, elflags =\ sh.from_file(filename) elif scantype == 'linear_1dir': azpts = params.get('azpts') el = params.get('el') azvel = params.get('azvel') total_time = (azpts[1] - azpts[0]) / azvel azs = np.linspace(azpts[0], azpts[1], total_time * 10) els = np.linspace(el, el, total_time * 10) times = np.linspace(0.0, total_time, total_time * 10) elif scantype == 'linear_turnaround_sameends': # from parameters, generate the full set of scan points self.log.info('scantype is' + str(scantype)) azpts = params.get('azpts') el = params.get('el') azvel = params.get('azvel') acc = params.get('acc') ntimes = params.get('ntimes') times, azs, els, vas, ves, azflags, elflags =\ sh.linear_turnaround_scanpoints(azpts, el, azvel, acc, ntimes) # Switch to Stop mode and clear the stack yield self.acu.stop() self.log.info('Stop called') yield dsleep(5) yield self.acu.http.Command('DataSets.CmdTimePositionTransfer', 'Clear Stack') yield dsleep(0.1) self.log.info('Cleared stack.') # Move to the starting position for the scan and then switch to Stop # mode start_az = azs[0] start_el = els[0] upload_publish_dict = { 'Start_Azimuth': start_az, 'Start_Elevation': start_el, 'Start_Boresight': 0, 'Upload_Type': 2, 'Preset_Azimuth': 0, 'Preset_Elevation': 0, 'Upload_Lines': [] } # Follow the scan in ProgramTrack mode, then switch to Stop mode if scantype == 'linear_turnaround_sameends': all_lines = sh.write_lines(times, azs, els, vas, ves, azflags, elflags) elif scantype == 'from_file': all_lines = sh.write_lines(times, azs, els, vas, ves, azflags, elflags) # Other scan types not yet implemented, so break else: return False, 'Not enough information to scan' self.log.info('all_lines generated') yield self.acu.mode('ProgramTrack') self.log.info('mode is now ProgramTrack') group_size = 120 while len(all_lines): upload_lines = all_lines[:group_size] text = ''.join(upload_lines) all_lines = all_lines[group_size:] free_positions = self.data['status']['summary']\ ['Qty_of_free_program_track_stack_positions'] while free_positions < 9899: free_positions = self.data['status']['summary']\ ['Qty_of_free_program_track_stack_positions'] yield dsleep(0.1) yield self.acu.http.UploadPtStack(text) upload_publish_dict['Upload_Lines'] = upload_lines acu_upload = { 'timestamp': self.data['broadcast']['Time'], 'block_name': 'ACU_upload', 'data': upload_publish_dict } self.agent.publish_to_feed('acu_upload', acu_upload) self.log.info('Uploaded a group') self.log.info('No more lines to upload') current_az = round(self.data['broadcast']['Azimuth_Corrected'], 4) current_el = round(self.data['broadcast']['Elevation_Corrected'], 4) while current_az != azs[-1] or current_el != els[-1]: yield dsleep(0.1) modes = (self.data['status']['summary']['Azimuth_mode'], self.data['status']['summary']['Elevation_mode']) if modes != ('ProgramTrack', 'ProgramTrack'): return False, 'Fault triggered (not ProgramTrack)!' current_az = round(self.data['broadcast']['Azimuth_Corrected'], 4) current_el = round(self.data['broadcast']['Elevation_Corrected'], 4) yield dsleep(self.sleeptime) yield self.acu.stop() self.set_job_done('control') return True, 'Track completed.'
def go_to(self, session, params=None): """ TASK "go_to" Moves the telescope to a particular point (azimuth, elevation) in Preset mode. When motion has ended and the telescope reaches the preset point, it returns to Stop mode and ends. Params: az (float): destination angle for the azimuthal axis el (float): destination angle for the elevation axis wait (float): amount of time to wait for motion to end """ ok, msg = self.try_set_job('control') if not ok: return ok, msg az = params.get('az') el = params.get('el') wait_for_motion = params.get('wait', 1) current_az = round(self.data['broadcast']['Azimuth_Corrected'], 4) current_el = round(self.data['broadcast']['Elevation_Corrected'], 4) publish_dict = { 'Start_Azimuth': current_az, 'Start_Elevation': current_el, 'Start_Boresight': 0, 'Upload_Type': 1, 'Preset_Azimuth': az, 'Preset_Elevation': el, 'Upload_Lines': [] } acu_upload = { 'timestamp': self.data['broadcast']['Time'], 'block_name': 'ACU_upload', 'data': publish_dict } self.agent.publish_to_feed('acu_upload', acu_upload) # Check whether the telescope is already at the point self.log.info('Checking current position') if current_az == az and current_el == el: self.log.info('Already positioned at %.2f, %.2f' % (current_az, current_el)) self.set_job_done('control') return True, 'Pointing completed' yield self.acu.stop() self.log.info('Stopped') yield dsleep(0.1) yield self.acu.go_to(az, el) mdata = self.data['status']['summary'] # Wait for telescope to start moving self.log.info('Moving to commanded position') while mdata['Azimuth_current_velocity'] == 0.0 and\ mdata['Elevation_current_velocity'] == 0.0: yield dsleep(wait_for_motion) mdata = self.data['status']['summary'] moving = True while moving: mdata = self.data['status']['summary'] ve = round(mdata['Elevation_current_velocity'], 2) va = round(mdata['Azimuth_current_velocity'], 2) if (ve != 0.0) or (va != 0.0): moving = True yield dsleep(wait_for_motion) else: moving = False mdata = self.data['status']['summary'] pe = round(mdata['Elevation_current_position'], 2) pa = round(mdata['Azimuth_current_position'], 2) if pe != el or pa != az: yield self.acu.stop() self.log.warn('Stopped before reaching commanded point!') return False, 'Something went wrong!' modes = (mdata['Azimuth_mode'], mdata['Elevation_mode']) if modes != ('Preset', 'Preset'): return False, 'Fault triggered!' yield self.acu.stop() self.set_job_done('control') return True, 'Pointing completed'
def start_udp_monitor(self, session, params=None): """PROCESS broadcast This process reads UDP data from the port specified by self.acu_config, decodes it, and publishes to an HK feed. """ ok, msg = self.try_set_job('broadcast') if not ok: return ok, msg session.set_status('running') FMT = '<iddddd' FMT_LEN = struct.calcsize(FMT) UDP_PORT = self.acu_config['PositionBroadcast_target'].split(':')[1] udp_data = [] class MonitorUDP(protocol.DatagramProtocol): def datagramReceived(self, data, src_addr): host, port = src_addr offset = 0 while len(data) - offset >= FMT_LEN: d = struct.unpack(FMT, data[offset:offset + FMT_LEN]) udp_data.append(d) offset += FMT_LEN handler = reactor.listenUDP(int(UDP_PORT), MonitorUDP()) while self.jobs['broadcast'] == 'run': if udp_data: self.health_check['broadcast'] = True process_data = udp_data[:200] udp_data = udp_data[200:] year = datetime.datetime.now().year gyear = calendar.timegm(time.strptime(str(year), '%Y')) sample_rate = (len(process_data) / ( (process_data[-1][0] - process_data[0][0]) * 86400 + process_data[-1][1] - process_data[0][1])) latest_az = process_data[2] latest_el = process_data[3] latest_az_raw = process_data[4] latest_el_raw = process_data[5] session.data = { 'sample_rate': sample_rate, 'latest_az': latest_az, 'latest_el': latest_el, 'latest_az_raw': latest_az_raw, 'latest_el_raw': latest_el_raw } pd0 = process_data[0] pd0_gday = (pd0[0] - 1) * 86400 pd0_sec = pd0[1] pd0_data_ctime = gyear + pd0_gday + pd0_sec pd0_azimuth_corrected = pd0[2] pd0_azimuth_raw = pd0[4] pd0_elevation_corrected = pd0[3] pd0_elevation_raw = pd0[5] bcast_first = { 'Time': pd0_data_ctime, 'Azimuth_Corrected': pd0_azimuth_corrected, 'Azimuth_Raw': pd0_azimuth_raw, 'Elevation_Corrected': pd0_elevation_corrected, 'Elevation_Raw': pd0_elevation_raw, } acu_broadcast_influx = { 'timestamp': bcast_first['Time'], 'block_name': 'ACU_position', 'data': bcast_first, } self.agent.publish_to_feed('acu_broadcast_influx', acu_broadcast_influx) for d in process_data: gday = (d[0] - 1) * 86400 sec = d[1] data_ctime = gyear + gday + sec azimuth_corrected = d[2] azimuth_raw = d[4] elevation_corrected = d[3] elevation_raw = d[5] self.data['broadcast'] = { 'Time': data_ctime, 'Azimuth_Corrected': azimuth_corrected, 'Azimuth_Raw': azimuth_raw, 'Elevation_Corrected': elevation_corrected, 'Elevation_Raw': elevation_raw, } acu_udp_stream = { 'timestamp': self.data['broadcast']['Time'], 'block_name': 'ACU_position', 'data': self.data['broadcast'] } self.agent.publish_to_feed('acu_udp_stream', acu_udp_stream) else: yield dsleep(1) yield dsleep(0.005) handler.stopListening() self.set_job_done('broadcast') return True, 'Acquisition exited cleanly.'
def start_monitor(self, session, params=None): """PROCESS "monitor". This process refreshes the cache of SATP ACU status information, and reports it on HK feeds 'acu_status_summary' and 'acu_status_full'. Summary parameters are ACU-provided time code, Azimuth mode, Azimuth position, Azimuth velocity, Elevation mode, Elevation position, Elevation velocity, Boresight mode, and Boresight position. """ ok, msg = self.try_set_job('monitor') if not ok: return ok, msg session.set_status('running') report_t = time.time() report_period = 10 n_ok = 0 min_query_period = 0.05 # Seconds query_t = 0 summary_params = [ 'Time', 'Azimuth mode', 'Azimuth current position', 'Azimuth current velocity', 'Elevation mode', 'Elevation current position', 'Elevation current velocity', # 'Boresight mode', # 'Boresight current position', 'Qty of free program track stack positions', ] mode_key = { 'Stop': 0, 'Preset': 1, 'ProgramTrack': 2, 'Stow': 3, 'SurvivalMode': 4, } tfn_key = {'None': 0, 'False': 0, 'True': 1} char_replace = [' ', '-', ':', '(', ')', '+', ',', '/'] while self.jobs['monitor'] == 'run': now = time.time() if now > report_t + report_period: self.log.info('Responses ok at %.3f Hz' % (n_ok / (now - report_t))) self.health_check['status'] = True report_t = now n_ok = 0 if now - query_t < min_query_period: yield dsleep(now - query_t) query_t = now try: # j = yield self.acu.http.Values('DataSets.StatusSATPDetailed8100') j = yield self.acu.http.Values( 'DataSets.StatusCCATDetailed8100') n_ok += 1 session.data = j except Exception as e: # Need more error handling here... errormsg = {'aculib_error_message': str(e)} self.log.error(errormsg) acu_error = { 'timestamp': time.time(), 'block_name': 'ACU_error', 'data': errormsg } self.agent.publish_to_feed('acu_error', acu_error) yield dsleep(1) for (key, value) in session.data.items(): ocs_key = key for char in char_replace: ocs_key = ocs_key.replace(char, '_') ocs_key = ocs_key.replace('24V', 'V24') if key in summary_params: self.data['status']['summary'][ocs_key] = value if key == 'Azimuth mode': self.data['status']['summary']['Azimuth_mode_num'] =\ mode_key[value] elif key == 'Elevation mode': self.data['status']['summary']['Elevation_mode_num'] =\ mode_key[value] else: self.data['status']['full_status'][ocs_key] = str(value) influx_status = {} for v in self.data['status']['full_status']: try: influx_status[str(v) + '_influx'] =\ float(self.data['status']['full_status'][v]) except ValueError: influx_status[str(v) + '_influx'] =\ tfn_key[self.data['status']['full_status'][v]] self.data['status']['summary']['ctime'] =\ timecode(self.data['status']['summary']['Time']) acustatus_summary = { 'timestamp': self.data['status']['summary']['ctime'], 'block_name': 'ACU_summary_output', 'data': self.data['status']['summary'] } acustatus_full = { 'timestamp': self.data['status']['summary']['ctime'], 'block_name': 'ACU_fullstatus_output', 'data': self.data['status']['full_status'] } acustatus_influx = { 'timestamp': self.data['status']['summary']['ctime'], 'block_name': 'ACU_fullstatus_ints', 'data': influx_status } self.agent.publish_to_feed('acu_status_summary', acustatus_summary) self.agent.publish_to_feed('acu_status_full', acustatus_full) self.agent.publish_to_feed('acu_status_influx', acustatus_influx) self.set_job_done('monitor') return True, 'Acquisition exited cleanly.'
def master(self, session, params): """master(**kwargs) **Process** - The "master" Process maintains a list of child Agents for which it is responsible. In response to requests from a client, the Process will launch or terminate child Agents. If an Agent process exits unexpectedly, it will be relaunched within a few seconds. When the master Process receives a stop request, it will terminate all child agents before moving to the 'done' state. Parameters: **kwargs: Passed directly to ``_update_target_states(params=kwargs)``; see :func:`HostMaster._update_target_states`. """ self.running = True session.set_status('running') self._update_target_states(session, params) session.data = {} dying_words = ['down', 'kill', 'wait_dead'] #allowed during shutdown any_jobs = False while self.running or any_jobs: sleep_time = 1. any_jobs = False for key, db in self.database.items(): # State machine. prot = db['prot'] # If Process exit is requested, force all targets to down. if not self.running: db['target_state'] = 'down' # The uninterruptible transition state(s) are most easily handled # in the same way regardless of target state. # Transitional: wait_start, which bridges from start -> up. if db['next_action'] == 'wait_start': if prot is not None: session.add_message( 'Launched {full_name}'.format(**db)) db['next_action'] = 'up' else: if time.time() >= db['at']: session.add_message( 'Launch not detected for ' '{full_name}! Will retry.'.format(**db)) db['next_action'] = 'start_at' db['at'] = time.time() + 5. # Transitional: wait_dead, which bridges from kill -> idle. elif db['next_action'] == 'wait_dead': if prot is None: stat, t = 0, None else: stat, t = prot.status if stat is not None: db['next_action'] = 'down' elif time.time() >= db['at']: if stat is None: session.add_message('Agent instance {full_name} ' 'refused to die.'.format(**db)) db['next_action'] = 'down' else: sleep_time = min(sleep_time, db['at'] - time.time()) # State handling when target is to be 'up'. elif db['target_state'] == 'up': if db['next_action'] == 'start_at': if time.time() >= db['at']: db['next_action'] = 'start' else: sleep_time = min(sleep_time, db['at'] - time.time()) elif db['next_action'] == 'start': # Launch. if db['agent_script'] is None: session.add_message( 'No Agent script registered for ' 'class: {class_name}'.format(**db)) db['next_action'] = 'down' else: session.add_message( 'Requested launch for {full_name}'.format( **db)) db['prot'] = None reactor.callFromThread(self._launch_instance, key, db['agent_script'], db['instance_id']) db['next_action'] = 'wait_start' db['at'] = time.time() + 1. elif db['next_action'] == 'up': stat, t = prot.status if stat is not None: # Right here would be a great place to check # the stat return code, and include a traceback from stderr session.add_message('Detected exit of {full_name} ' 'with code {stat}.'.format( stat=stat, **db)) db['next_action'] = 'start_at' db['at'] = time.time() + 3 else: # 'down' db['next_action'] = 'start' # State handling when target is to be 'down'. elif db['target_state'] == 'down': if db['next_action'] == 'down': pass elif db['next_action'] == 'up': session.add_message('Requesting termination of ' '{full_name}'.format(**db)) self._terminate_instance(key) db['next_action'] = 'wait_dead' db['at'] = time.time() + 5 else: # 'start_at', 'start' session.add_message( 'Modifying state of {full_name} from ' '{next_action} to idle'.format(**db)) db['next_action'] = 'down' # Should not get here. else: session.add_message( 'State machine failure: state={next_action}, target_state' '={target_state}'.format(**db)) any_jobs = (any_jobs or (db['next_action'] != 'down')) # Clean up retired items. self.database = { k: v for k, v in self.database.items() if not v.get('retired') or v['next_action'] != 'down' } # Update session info. child_states = [] for k, v in self.database.items(): child_states.append({ _k: v[_k] for _k in [ 'next_action', 'target_state', 'class_name', 'instance_id' ] }) session.data = child_states yield dsleep(max(sleep_time, .001)) return True, 'Exited.'