class ProcessStateGateIntTest(IonIntegrationTestCase): def setUp(self): from unittest import SkipTest raise SkipTest("Process dispatcher currently not supported") self._start_container() self.container.start_rel_from_url('res/deploy/basic.yml') self.pd_cli = ProcessDispatcherServiceClient() self.process_definition = IonObject(OT.ProcessDefinition, name='test_process') self.process_definition.executable = {'module': 'ion.services.test.test_process_state_gate', 'class': 'TestProcess'} self.process_definition_id = self.pd_cli.create_process_definition(self.process_definition) self.event_queue = queue.Queue() self.process_schedule = IonObject(OT.ProcessSchedule) self.process_schedule.queueing_mode = ProcessQueueingMode.ALWAYS self.pid = self.pd_cli.create_process(self.process_definition_id) self.event_queue = queue.Queue() self.event_sub = EventSubscriber(event_type="ProcessLifecycleEvent", callback=self._event_callback, origin=self.pid, origin_type="DispatchedProcess") def tearDown(self): #stop subscriber if its running if self.event_sub and self.event_sub._cbthread: self.event_sub.stop() self._stop_container() def _event_callback(self, event, *args, **kwargs): self.event_queue.put(event) def latest_event(self, timeout=10): # get latest event from our local event subscriber try: event = self.event_queue.get(timeout=timeout) except Empty: event = None return event def await_state(self, state, timeout=10): print "Emptying event queue" while True: event = self.latest_event(0) if event: print "State %s from event %s" % (event.state, event) else: break self.event_sub.start() #wait for process state print "Setting up %s gate" % ProcessStateEnum._str_map[state] gate = ProcessStateGate(self.pd_cli.read_process, self.pid, state) print "Waiting" ret = gate.await(timeout) print "Await got %s" % ret event = self.latest_event(timeout=1) # check false positives/negatives if ret and gate._get_first_chance() is None and event is None: self.fail("ProcessStateGate got an event that EventSubscriber didnt....") self.event_sub.stop() if (not ret) or gate._get_last_chance(): if event and event.state == state: self.fail("EventSubscriber got state event %s for process %s, ProcessStateGate missed it" % (ProcessStateEnum._str_map[event.state], self.pid)) return ret def process_start(self): print "Scheduling process...", self.pd_cli.schedule_process(self.process_definition_id, self.process_schedule, configuration={}, process_id=self.pid) print "Done scheduling process." def process_stop(self): print "STOPPING process...", self.pd_cli.cancel_process(self.pid) print "Done stopping process" def test_process_state_gate(self): self.assertFalse(self.await_state(ProcessStateEnum.RUNNING, 1), "The process was reported as spawned, but we didn't yet") print "GOING TO ACTUALLY START PROCESS NOW" spawn_later(1, self.process_start) self.assertTrue(self.await_state(ProcessStateEnum.RUNNING), "The process did not spawn") self.assertFalse(self.await_state(ProcessStateEnum.TERMINATED, 1), "The process claims to have terminated, but we didn't kill it") print "communicating with the process to make sure it is really running" test_client = TestClient() for i in range(5): self.assertEqual(i + 1, test_client.count(timeout=10)) spawn_later(1, self.process_stop) self.assertTrue(self.await_state(ProcessStateEnum.TERMINATED), "The process failed to be reported as terminated when it was terminated") self.assertFalse(self.await_state(ProcessStateEnum.RUNNING, 1), "The process was reported as spawned, but we killed it")
class ProcessDispatcherServiceIntTest(IonIntegrationTestCase): def setUp(self): raise SkipTest("Process dispatcher currently not supported") self._start_container() self.container.start_rel_from_url('res/deploy/basic.yml') self.rr_cli = ResourceRegistryServiceClient() self.pd_cli = ProcessDispatcherServiceClient() self.process_definition = ProcessDefinition(name='test_process') self.process_definition.executable = {'module': 'ion.services.test.test_process_dispatcher', 'class': 'TestProcess'} self.process_definition_id = self.pd_cli.create_process_definition(self.process_definition) self.waiter = ProcessStateWaiter() def tearDown(self): self.waiter.stop() def test_create_schedule_cancel(self): process_schedule = ProcessSchedule() process_schedule.queueing_mode = ProcessQueueingMode.ALWAYS proc_name = 'myreallygoodname' pid = self.pd_cli.create_process(self.process_definition_id) self.waiter.start(pid) pid2 = self.pd_cli.schedule_process(self.process_definition_id, process_schedule, configuration={}, process_id=pid, name=proc_name) self.assertEqual(pid, pid2) # verifies L4-CI-CEI-RQ141 and L4-CI-CEI-RQ142 self.waiter.await_state_event(pid, ProcessStateEnum.RUNNING) proc = self.pd_cli.read_process(pid) self.assertEqual(proc.process_id, pid) self.assertEqual(proc.process_configuration, {}) self.assertEqual(proc.process_state, ProcessStateEnum.RUNNING) # make sure process is readable directly from RR (mirrored) # verifies L4-CI-CEI-RQ63 # verifies L4-CI-CEI-RQ64 proc = self.rr_cli.read(pid) self.assertEqual(proc.process_id, pid) # now try communicating with the process to make sure it is really running test_client = TestClient() for i in range(5): self.assertEqual(i + 1, test_client.count(timeout=10)) # verifies L4-CI-CEI-RQ147 # check the process name was set in container got_proc_name = test_client.get_process_name(pid=pid2) self.assertEqual(proc_name, got_proc_name) # kill the process and start it again self.pd_cli.cancel_process(pid) self.waiter.await_state_event(pid, ProcessStateEnum.TERMINATED) pid2 = self.pd_cli.schedule_process(self.process_definition_id, process_schedule, configuration={}, process_id=pid) self.assertEqual(pid, pid2) self.waiter.await_state_event(pid, ProcessStateEnum.RUNNING) for i in range(5): self.assertEqual(i + 1, test_client.count(timeout=10)) # kill the process for good self.pd_cli.cancel_process(pid) self.waiter.await_state_event(pid, ProcessStateEnum.TERMINATED) def test_schedule_with_config(self): process_schedule = ProcessSchedule() process_schedule.queueing_mode = ProcessQueueingMode.ALWAYS pid = self.pd_cli.create_process(self.process_definition_id) self.waiter.start(pid) # verifies L4-CI-CEI-RQ66 # feed in a string that the process will return -- verifies that # configuration actually makes it to the instantiated process test_response = uuid.uuid4().hex configuration = {"test_response": test_response} pid2 = self.pd_cli.schedule_process(self.process_definition_id, process_schedule, configuration=configuration, process_id=pid) self.assertEqual(pid, pid2) self.waiter.await_state_event(pid, ProcessStateEnum.RUNNING) test_client = TestClient() # verifies L4-CI-CEI-RQ139 # assure that configuration block (which can contain inputs, outputs, # and arbitrary config) 1) makes it to the process and 2) is returned # in process queries self.assertEqual(test_client.query(), test_response) proc = self.pd_cli.read_process(pid) self.assertEqual(proc.process_id, pid) self.assertEqual(proc.process_configuration, configuration) # kill the process for good self.pd_cli.cancel_process(pid) self.waiter.await_state_event(pid, ProcessStateEnum.TERMINATED) def test_schedule_bad_config(self): process_schedule = ProcessSchedule() # a non-JSON-serializable IonObject o = ProcessTarget() with self.assertRaises(BadRequest) as ar: self.pd_cli.schedule_process(self.process_definition_id, process_schedule, configuration={"bad": o}) self.assertTrue(ar.exception.message.startswith("bad configuration")) def test_cancel_notfound(self): with self.assertRaises(NotFound): self.pd_cli.cancel_process("not-a-real-process-id") def test_create_invalid_definition(self): # create process definition missing module and class # verifies L4-CI-CEI-RQ137 executable = dict(url="http://somewhere.com/something.py") definition = ProcessDefinition(name="test_process", executable=executable) with self.assertRaises(BadRequest): self.pd_cli.create_process_definition(definition)