def setUp(self):
     super(HeartbeatListenerTestCase, self).setUp()
     self.stdin = StringIO()
     self.stdout = StringIO()
     self.stderr = StringIO()
     self.rpc = mock.Mock()
     self.listener = HeartbeatListener(1,
                                       10, ['foo'], [],
                                       self.rpc,
                                       stdin=self.stdin,
                                       stdout=self.stdout,
                                       stderr=self.stderr)
     self.next_fail = {}
     self.handler = self.add_memento_handler(  # add the log handler
         self.listener.logger, level=logging.DEBUG)
     self.processes = [
         dict(name="heartbeat", group="heartbeat", pid="101", state=RUNNING)
     ]
 def setUp(self):
     super(HeartbeatListenerTestCase, self).setUp()
     self.stdin = StringIO()
     self.stdout = StringIO()
     self.stderr = StringIO()
     self.mocker = Mocker()
     self.rpc = self.mocker.mock()
     self.listener = HeartbeatListener(1,
                                       10, ['foo'], [],
                                       self.rpc,
                                       stdin=self.stdin,
                                       stdout=self.stdout,
                                       stderr=self.stderr)
     self.next_fail = {}
     self.handler = MementoHandler()
     self.listener.logger.addHandler(self.handler)
     self.listener.logger.setLevel(logging.DEBUG)
     self.handler.setLevel(logging.DEBUG)
     self.listener.logger.propagate = False
     self.processes = [
         dict(name="heartbeat", group="heartbeat", pid="101", state=RUNNING)
     ]
     self.handler.debug = True
 def setUp(self):
     super(HeartbeatListenerTestCase, self).setUp()
     self.stdin = StringIO()
     self.stdout = StringIO()
     self.stderr = StringIO()
     self.mocker = Mocker()
     self.rpc = self.mocker.mock()
     self.listener = HeartbeatListener(1, 10, ['foo'], [], self.rpc,
                                       stdin=self.stdin, stdout=self.stdout,
                                       stderr=self.stderr)
     self.next_fail = {}
     self.handler = MementoHandler()
     self.listener.logger.addHandler(self.handler)
     self.listener.logger.setLevel(logging.DEBUG)
     self.handler.setLevel(logging.DEBUG)
     self.listener.logger.propagate = False
     self.processes = [dict(name="heartbeat", group="heartbeat", pid="101",
                            state=RUNNING)]
     self.handler.debug = True
class HeartbeatListenerTestCase(TestCase):
    """Tests for HeartbeatListener class."""

    def setUp(self):
        super(HeartbeatListenerTestCase, self).setUp()
        self.stdin = StringIO()
        self.stdout = StringIO()
        self.stderr = StringIO()
        self.mocker = Mocker()
        self.rpc = self.mocker.mock()
        self.listener = HeartbeatListener(1, 10, ['foo'], [], self.rpc,
                                          stdin=self.stdin, stdout=self.stdout,
                                          stderr=self.stderr)
        self.next_fail = {}
        self.handler = MementoHandler()
        self.listener.logger.addHandler(self.handler)
        self.listener.logger.setLevel(logging.DEBUG)
        self.handler.setLevel(logging.DEBUG)
        self.listener.logger.propagate = False
        self.processes = [dict(name="heartbeat", group="heartbeat", pid="101",
                               state=RUNNING)]
        self.handler.debug = True

    def tearDown(self):
        self.listener.logger.removeHandler(self.handler)
        self.handler.close()
        self.next_fail = None
        self.handler = None
        self.listener = None
        super(HeartbeatListenerTestCase, self).tearDown()

    def fail_next_stop(self, pname):
        """Make next stopProcess to fail."""
        expect(self.rpc.supervisor.stopProcess(pname)).throw(
            xmlrpclib.Fault(42, "Failed to stop the process."))

    def fail_next_start(self, pname):
        """Make next startProcess to fail."""
        expect(self.rpc.supervisor.startProcess(pname)).throw(
            xmlrpclib.Fault(42, "Failed to start the process."))

    def test_restart(self):
        """Test the restart method."""
        expect(self.rpc.supervisor.stopProcess("foo"))
        expect(self.rpc.supervisor.startProcess("foo"))
        with self.mocker:
            self.listener.restart("foo", "testing")
        self.assertTrue(self.handler.check_info("Restarting foo (last "
                                                "hearbeat: testing)"))

    def test_restart_fail_stop(self):
        """Test the restart method failing to stop the process."""
        self.fail_next_stop("foo")
        last = time.time()
        with self.mocker:
            try:
                self.listener.restart("foo", last)
            except xmlrpclib.Fault:
                msg = ("Failed to stop process %s (last heartbeat: %s), "
                       "exiting: %s") % \
                    ("foo", last, "<Fault 42: 'Failed to stop the process.'>")
                self.assertTrue(self.handler.check_error(msg))
            else:
                self.fail("Should get an xmlrpclib.Fault")

    def test_restart_fail_start(self):
        """Test the restart method failing to start the process."""
        expect(self.rpc.supervisor.stopProcess("foo"))
        self.fail_next_start("foo")
        last = time.time()
        with self.mocker:
            try:
                self.listener.restart("foo", last)
            except xmlrpclib.Fault:
                msg = (
                    'Failed to start process %s after stopping it, exiting: %s'
                ) % ("foo", "<Fault 42: 'Failed to start the process.'>")
                self.assertTrue(self.handler.check_error(msg))
            else:
                self.fail("Should get an xmlrpclib.Fault")

    def test_check_processes(self):
        """Test the check_processes method."""
        # add the fake process to the process list
        self.processes.append(dict(name="foo", group="foo", pid="42",
                                   state=RUNNING))
        self.processes.append(dict(name="bar", group="bar", pid="43",
                                   state=RUNNING))
        self.listener.processes = ['bar']
        # 2 process to restart
        self.listener.data['foo'] = {
            'time': time.time() - (self.listener.timeout + 2)}
        self.listener.data['bar'] = {
            'time': time.time() - (self.listener.timeout + 3)}
        self.listener.data['p-1'] = {
            'time': time.time() - (self.listener.timeout - 1)}
        expect(self.rpc.supervisor.getAllProcessInfo()).result(self.processes)
        expect(self.rpc.supervisor.stopProcess("foo:"))
        expect(self.rpc.supervisor.startProcess("foo:"))
        expect(self.rpc.supervisor.stopProcess("bar:bar"))
        expect(self.rpc.supervisor.startProcess("bar:bar"))
        with self.mocker:
            self.listener.check_processes()

    def test_check_processes_no_data(self):
        """Test the check_processes method with no data of a process."""
        # add the fake process to the process list
        self.processes.append(dict(name="foo", group="foo", pid="42",
                                   state=RUNNING))
        self.processes.append(dict(name="bar", group="bar", pid="43",
                                   state=RUNNING))
        self.listener.processes = ['bar']
        expect(self.rpc.supervisor.getAllProcessInfo()).result(self.processes)
        expect(self.rpc.supervisor.stopProcess("foo:"))
        expect(self.rpc.supervisor.startProcess("foo:"))
        expect(self.rpc.supervisor.stopProcess("bar:bar"))
        expect(self.rpc.supervisor.startProcess("bar:bar"))
        with self.mocker:
            # one process to restart
            self.listener.check_processes()
        self.assertTrue(self.handler.check_warning(
            "Restarting process foo:foo (42), as we never received a hearbeat"
            " event from it"))
        self.assertTrue(self.handler.check_warning(
            "Restarting process bar:bar (43), as we never received a hearbeat"
            " event from it"))

    def test_check_processes_untracked(self):
        """Test the check_processes method with a untracked proccess."""
        # add the fake process to the process list
        self.processes.append(dict(name="foo-untracked", group="untracked",
                                   pid="43", state=RUNNING))
        # add a new tracked process from an untracked group
        self.processes.append(dict(name="bar-untracked", group="bar", pid="44",
                                   state=RUNNING))
        self.listener.processes = ['bar']
        expect(self.rpc.supervisor.getAllProcessInfo()).result(self.processes)
        with self.mocker:
            self.listener.check_processes()
        self.assertTrue(self.handler.check_info(
            "Ignoring untracked:foo-untracked (43) as isn't tracked."))
        self.assertTrue(self.handler.check_info(
            "Ignoring bar:bar-untracked (44) as isn't tracked."))

    def test_check_processes_not_running(self):
        """Test the check_processes method if the proccess isn't running."""
        # add the fake process to the process list
        self.processes.append(dict(name="foo", group="foo", pid="42",
                                   state=states.ProcessStates.STARTING))
        # add a new tracked process from an untracked group
        self.processes.append(dict(name="bar", group="bar", pid="43",
                                   state=states.ProcessStates.STARTING))
        self.listener.processes = ['bar']
        # 2 processes to restart
        self.listener.data['foo'] = {
            'time': time.time() - (self.listener.timeout + 2)}
        self.listener.data['bar'] = {
            'time': time.time() - (self.listener.timeout + 2)}
        expect(self.rpc.supervisor.getAllProcessInfo()).result(self.processes)
        with self.mocker:
            self.listener.check_processes()
        self.assertTrue(self.handler.check_info(
            "Ignoring foo:foo (42) as isn't running."))
        self.assertTrue(self.handler.check_info(
            "Ignoring bar:bar (43) as isn't running."))

    def test_handle_heartbeat(self):
        """Test handle_heartbeat method."""
        payload = {"time": time.time()}
        self.listener.handle_heartbeat('process_name', 'group_name',
                                       '42', payload)
        info = {"pid": "42", "time": payload["time"],
                "received": self.listener.data["process_name"]["received"]}
        self.assertEqual({"process_name": info}, self.listener.data)

    def test_handle_event(self):
        """Test handle_event method."""
        # patch handle_heartbeat
        called = []

        def handle_heartbeat(process_name, group_name, pid, payload):
            """Fake handle_heartbeat."""
            called.append((process_name, group_name, pid, payload))

        self.listener.handle_heartbeat = handle_heartbeat
        payload_dict = {u"time": time.time(), "type": "heartbeat"}
        raw_data = ("processname:ticker groupname:ticker pid:42\n" +
                    json.dumps(payload_dict))
        raw_header = ("ver:3.0 server:supervisor serial:1 pool:listener "
                      "poolserial:10 eventname:PROCESS_COMMUNICATION_STDOUT"
                      " len:%s\n" % len(raw_data))
        self.stdin.write(raw_header + raw_data)
        self.stdin.seek(0)
        headers = childutils.get_headers(raw_header)
        self.listener._handle_event()
        # check
        self.assertEqual(1, len(called))
        del payload_dict['type']
        self.assertEqual(('ticker', 'ticker', '42', payload_dict), called[0])
        self.assertTrue(self.handler.check_debug(
            "Event '%s' received: %r" % (headers['eventname'], raw_data)))
        # check the stdout info
        self.assertEqual(["READY", "RESULT 2", "OK"],
                         self.stdout.getvalue().split("\n"))

    def test_invalid_event_type(self):
        """Test with an invalid type."""
        payload_dict = {u"time": time.time(), "type": "ping"}
        raw_data = 'processname:ticker groupname:ticker pid:42\n' + \
            json.dumps(payload_dict)
        raw_header = ("ver:3.0 server:supervisor serial:1 pool:listener "
                      "poolserial:10 eventname:PROCESS_COMMUNICATION_STDOUT"
                      " len:%s\n" % len(raw_data))
        self.stdin.write(raw_header + raw_data)
        self.stdin.seek(0)
        self.listener._handle_event()
        # check
        self.assertTrue(self.handler.check_error(
            "Unable to handle event type '%s' - %r" % ('ping', raw_data)))

    def test_invalid_payload(self):
        """Test with an invalid payload."""
        payload_dict = {u"time": time.time(), "type": "ping"}
        raw_data = 'processname:ticker groupname:ticker pid:42\n' + \
            json.dumps(payload_dict) + "<!foo>"
        raw_header = ("ver:3.0 server:supervisor serial:1 pool:listener "
                      "poolserial:10 eventname:PROCESS_COMMUNICATION_STDOUT"
                      " len:%s\n" % len(raw_data))
        self.stdin.write(raw_header + raw_data)
        self.stdin.seek(0)
        self.listener._handle_event()
        # check
        self.assertTrue(self.handler.check_error(
            "Unable to handle event type '%s' - %r" % ('None', raw_data)))

    def test_unhandled_event(self):
        """A unhandled event type."""
        payload_dict = {u"time": time.time(), "type": "ping"}
        raw_data = 'processname:ticker groupname:ticker pid:42\n' + \
            json.dumps(payload_dict)
        raw_header = "ver:3.0 server:supervisor serial:1 pool:heartbeat " + \
            "poolserial:1 eventname:UNKNOWN len:%s\n" % len(raw_data)
        self.stdin.write(raw_header + raw_data)
        self.stdin.seek(0)
        self.listener._handle_event()
        # check
        self.assertTrue(self.handler.check_warning(
            "Received unsupported event: %s - %r" % ('UNKNOWN', raw_data)))

    def test_check_interval(self):
        """Check that we properly check on the specified interval."""
        header = "ver:3.0 server:supervisor serial:1 pool:heartbeat " + \
                 "poolserial:1 eventname:TICK_5 len:0\n"
        expect(self.rpc.supervisor.getAllProcessInfo()).result([])
        self.stdin.write(header)
        self.stdin.seek(0)
        self.listener._handle_event()
        self.assertEqual(self.listener.tick_count, 1)
        self.stdin.seek(0)
        with self.mocker:
            self.listener._handle_event()
class HeartbeatListenerTestCase(BaseTestCase):
    """Tests for HeartbeatListener class."""
    def setUp(self):
        super(HeartbeatListenerTestCase, self).setUp()
        self.stdin = StringIO()
        self.stdout = StringIO()
        self.stderr = StringIO()
        self.rpc = mock.Mock()
        self.listener = HeartbeatListener(1,
                                          10, ['foo'], [],
                                          self.rpc,
                                          stdin=self.stdin,
                                          stdout=self.stdout,
                                          stderr=self.stderr)
        self.next_fail = {}
        self.handler = self.add_memento_handler(  # add the log handler
            self.listener.logger, level=logging.DEBUG)
        self.processes = [
            dict(name="heartbeat", group="heartbeat", pid="101", state=RUNNING)
        ]

    def test_restart(self):
        """Test the restart method."""
        self.listener.restart("foo", "testing")

        self.handler.assert_info("Restarting foo (last hearbeat: testing)")
        self.rpc.supervisor.stopProcess.assert_called_once_with("foo")
        self.rpc.supervisor.startProcess.assert_called_once_with("foo")

    def test_restart_fail_stop(self):
        """Test the restart method failing to stop the process."""
        self.rpc.supervisor.stopProcess.side_effect = Fault(
            42, "Failed to stop the process.")

        last = time.time()
        with self.assertRaises(Fault):
            self.listener.restart("foo", last)

        msg = "Failed to stop process %s (last heartbeat: %s), exiting: %s"
        args = ("foo", last, "<Fault 42: 'Failed to stop the process.'>")
        self.handler.assert_error(msg % args)
        self.rpc.supervisor.stopProcess.assert_called_once_with("foo")

    def test_restart_fail_start(self):
        """Test the restart method failing to start the process."""
        self.rpc.supervisor.startProcess.side_effect = Fault(
            42, "Failed to start the process.")

        last = time.time()
        with self.assertRaises(Fault):
            self.listener.restart("foo", last)

        msg = 'Failed to start process %s after stopping it, exiting: %s'
        self.handler.assert_error(
            msg % ("foo", "<Fault 42: 'Failed to start the process.'>"))

        self.rpc.supervisor.stopProcess.assert_called_once_with("foo")
        self.rpc.supervisor.startProcess.assert_called_once_with("foo")

    def test_check_processes(self):
        """Test the check_processes method."""
        # add the fake process to the process list
        self.processes.append(
            dict(name="foo", group="foo", pid="42", state=RUNNING))
        self.processes.append(
            dict(name="bar", group="bar", pid="43", state=RUNNING))
        self.listener.processes = ['bar']
        # 2 process to restart
        self.listener.data['foo'] = {
            'time': time.time() - (self.listener.timeout + 2)
        }
        self.listener.data['bar'] = {
            'time': time.time() - (self.listener.timeout + 3)
        }
        self.listener.data['p-1'] = {
            'time': time.time() - (self.listener.timeout - 1)
        }
        self.rpc.supervisor.getAllProcessInfo.return_value = self.processes

        self.listener.check_processes()

        expected_calls = [
            mock.call.getAllProcessInfo(),
            mock.call.stopProcess("foo:"),
            mock.call.startProcess("foo:"),
            mock.call.stopProcess("bar:bar"),
            mock.call.startProcess("bar:bar"),
        ]
        self.assertEqual(self.rpc.supervisor.mock_calls, expected_calls)

    def test_check_processes_no_data(self):
        """Test the check_processes method with no data of a process."""
        # add the fake process to the process list
        self.processes.append(
            dict(name="foo", group="foo", pid="42", state=RUNNING))
        self.processes.append(
            dict(name="bar", group="bar", pid="43", state=RUNNING))
        self.listener.processes = ['bar']
        self.rpc.supervisor.getAllProcessInfo.return_value = self.processes

        # one process to restart
        self.listener.check_processes()

        self.handler.assert_warning(
            "Restarting process foo:foo (42), as we never received a hearbeat"
            " event from it")
        self.handler.assert_warning(
            "Restarting process bar:bar (43), as we never received a hearbeat"
            " event from it")
        expected_calls = [
            mock.call.getAllProcessInfo(),
            mock.call.stopProcess("foo:"),
            mock.call.startProcess("foo:"),
            mock.call.stopProcess("bar:bar"),
            mock.call.startProcess("bar:bar"),
        ]
        self.assertEqual(self.rpc.supervisor.mock_calls, expected_calls)

    def test_check_processes_untracked(self):
        """Test the check_processes method with a untracked proccess."""
        # add the fake process to the process list
        self.processes.append(
            dict(name="foo-untracked",
                 group="untracked",
                 pid="43",
                 state=RUNNING))
        # add a new tracked process from an untracked group
        self.processes.append(
            dict(name="bar-untracked", group="bar", pid="44", state=RUNNING))
        self.listener.processes = ['bar']
        self.rpc.supervisor.getAllProcessInfo.return_value = self.processes

        self.listener.check_processes()

        self.handler.assert_info(
            "Ignoring untracked:foo-untracked (43) as isn't tracked.")
        self.handler.assert_info(
            "Ignoring bar:bar-untracked (44) as isn't tracked.")
        self.rpc.supervisor.getAllProcessInfo.assert_called_once_with()

    def test_check_processes_not_running(self):
        """Test the check_processes method if the proccess isn't running."""
        # add the fake process to the process list
        self.processes.append(
            dict(name="foo",
                 group="foo",
                 pid="42",
                 state=states.ProcessStates.STARTING))
        # add a new tracked process from an untracked group
        self.processes.append(
            dict(name="bar",
                 group="bar",
                 pid="43",
                 state=states.ProcessStates.STARTING))
        self.listener.processes = ['bar']
        # 2 processes to restart
        self.listener.data['foo'] = {
            'time': time.time() - (self.listener.timeout + 2)
        }
        self.listener.data['bar'] = {
            'time': time.time() - (self.listener.timeout + 2)
        }
        self.rpc.supervisor.getAllProcessInfo.return_value = self.processes

        self.listener.check_processes()

        self.handler.assert_info("Ignoring foo:foo (42) as isn't running.")
        self.handler.assert_info("Ignoring bar:bar (43) as isn't running.")
        self.rpc.supervisor.getAllProcessInfo.assert_called_once_with()

    def test_handle_heartbeat(self):
        """Test handle_heartbeat method."""
        payload = {"time": time.time()}
        self.listener.handle_heartbeat('process_name', 'group_name', '42',
                                       payload)
        info = {
            "pid": "42",
            "time": payload["time"],
            "received": self.listener.data["process_name"]["received"]
        }
        self.assertEqual({"process_name": info}, self.listener.data)

    def test_handle_event(self):
        """Test handle_event method."""
        # patch handle_heartbeat
        called = []

        def handle_heartbeat(process_name, group_name, pid, payload):
            """Fake handle_heartbeat."""
            called.append((process_name, group_name, pid, payload))

        self.listener.handle_heartbeat = handle_heartbeat
        payload_dict = {u"time": time.time(), "type": "heartbeat"}
        raw_data = ("processname:ticker groupname:ticker pid:42\n" +
                    json.dumps(payload_dict))
        raw_header = ("ver:3.0 server:supervisor serial:1 pool:listener "
                      "poolserial:10 eventname:PROCESS_COMMUNICATION_STDOUT"
                      " len:%s\n" % len(raw_data))
        self.stdin.write(raw_header + raw_data)
        self.stdin.seek(0)
        headers = childutils.get_headers(raw_header)
        self.listener._handle_event()
        # check
        self.assertEqual(1, len(called))
        del payload_dict['type']
        self.assertEqual(('ticker', 'ticker', '42', payload_dict), called[0])
        self.handler.assert_debug("Event '%s' received: %r" %
                                  (headers['eventname'], raw_data))
        # check the stdout info
        self.assertEqual(["READY", "RESULT 2", "OK"],
                         self.stdout.getvalue().split("\n"))

    def test_invalid_event_type(self):
        """Test with an invalid type."""
        payload_dict = {u"time": time.time(), "type": "ping"}
        raw_data = 'processname:ticker groupname:ticker pid:42\n' + \
            json.dumps(payload_dict)
        raw_header = ("ver:3.0 server:supervisor serial:1 pool:listener "
                      "poolserial:10 eventname:PROCESS_COMMUNICATION_STDOUT"
                      " len:%s\n" % len(raw_data))
        self.stdin.write(raw_header + raw_data)
        self.stdin.seek(0)
        self.listener._handle_event()
        # check
        self.handler.assert_error("Unable to handle event type '%s' - %r" %
                                  ('ping', raw_data))

    def test_invalid_payload(self):
        """Test with an invalid payload."""
        payload_dict = {u"time": time.time(), "type": "ping"}
        raw_data = 'processname:ticker groupname:ticker pid:42\n' + \
            json.dumps(payload_dict) + "<!foo>"
        raw_header = ("ver:3.0 server:supervisor serial:1 pool:listener "
                      "poolserial:10 eventname:PROCESS_COMMUNICATION_STDOUT"
                      " len:%s\n" % len(raw_data))
        self.stdin.write(raw_header + raw_data)
        self.stdin.seek(0)
        self.listener._handle_event()
        # check
        self.handler.assert_error("Unable to handle event type '%s' - %r" %
                                  ('None', raw_data))

    def test_unhandled_event(self):
        """A unhandled event type."""
        payload_dict = {u"time": time.time(), "type": "ping"}
        raw_data = 'processname:ticker groupname:ticker pid:42\n' + \
            json.dumps(payload_dict)
        raw_header = "ver:3.0 server:supervisor serial:1 pool:heartbeat " + \
            "poolserial:1 eventname:UNKNOWN len:%s\n" % len(raw_data)
        self.stdin.write(raw_header + raw_data)
        self.stdin.seek(0)
        self.listener._handle_event()
        # check
        self.handler.assert_warning("Received unsupported event: %s - %r" %
                                    ('UNKNOWN', raw_data))

    def test_check_interval(self):
        """Check that we properly check on the specified interval."""
        header = ("ver:3.0 server:supervisor serial:1 pool:heartbeat "
                  "poolserial:1 eventname:TICK_5 len:0\n")
        self.rpc.supervisor.getAllProcessInfo.return_value = []
        self.stdin.write(header)
        self.stdin.seek(0)
        self.listener._handle_event()
        self.assertEqual(self.listener.tick_count, 1)
        self.stdin.seek(0)

        self.listener._handle_event()

        self.rpc.supervisor.getAllProcessInfo.assert_called_once_with()