def setUp(self):
     self.shell = MockShell()
     self.app = SaboteurWebApp(self.shell)
class TestCommands(unittest.TestCase):
    def setUp(self):
        self.shell = MockShell()
        self.app = SaboteurWebApp(self.shell)

    def test_isolate_webserver(self):
        params = {
            'name': 'isolate-web-server',
            'type': 'NETWORK_FAILURE',
            'direction': 'IN',
            'to_port': 80,
            'protocol': 'TCP'
        }
        response = self.app.handle(post_request(params))
        self.assertEqual(response['status'], 200)
        self.assertEqual(self.shell.last_command, 'sudo /sbin/iptables -A INPUT -p TCP -j DROP --dport 80')

    def test_reset(self):
        self.shell.next_result = 'eth1'

        response = self.app.handle(delete_request())
        self.assertEqual(response['status'], 200)
        self.assertEqual(self.shell.commands, [
            'sudo /sbin/iptables -F',
            "netstat -i | tail -n+3 | cut -f1 -d ' '",
            'sudo /sbin/tc qdisc del dev eth1 root'])

    def test_isolate_udp_server(self):
        params = {
            'name': "isolate-streaming-server",
            'type': "NETWORK_FAILURE",
            'direction': "IN",
            'to_port': 8111,
            'protocol': "UDP"
        }
        response = self.app.handle(post_request(params))
        self.assertEqual(response['status'], 200)
        self.assertEqual(self.shell.last_command, 'sudo /sbin/iptables -A INPUT -p UDP -j DROP --dport 8111')


    def test_webserver_shut_down(self):
        params = {
            'name': "web-server-down",
            'type': "SERVICE_FAILURE",
            'direction': "IN",
            'to_port': 8080,
            'protocol': "TCP"
        }
        response = self.app.handle(post_request(params))
        self.assertEqual(response['status'], 200)
        self.assertEqual(self.shell.last_command,
                         'sudo /sbin/iptables -A INPUT -p TCP -j REJECT --reject-with tcp-reset --dport 8080')


    def test_client_dependency_unreachable(self):
        params = {
            'name': "connectivity-to-dependency-down",
            'type': "NETWORK_FAILURE",
            'direction': "OUT",
            'to': 'my.dest.host.com',
            'to_port': 443,
            'protocol': "TCP"
        }
        response = self.app.handle(post_request(params))
        self.assertEqual(response['status'], 200)
        self.assertEqual(self.shell.last_command,
                         'sudo /sbin/iptables -A OUTPUT -p TCP -j DROP -d my.dest.host.com --dport 443')


    def test_specifying_source(self):
        params = {
            'name': "network-failure-by-source-host",
            'type': "NETWORK_FAILURE",
            'direction': "IN",
            'from': 'my.source.host.com',
            'protocol': "TCP"
        }
        response = self.app.handle(post_request(params))
        self.assertEqual(response['status'], 200)
        self.assertEqual(self.shell.last_command, 'sudo /sbin/iptables -A INPUT -p TCP -j DROP -s my.source.host.com')

    def test_firewall_timeout(self):
        params = {
            'name': "network-failure-by-source-host",
            'type': "FIREWALL_TIMEOUT",
            'direction': "IN",
            'to_port': 3000,
            'protocol': "TCP",
            'timeout': 101
        }
        response = self.app.handle(post_request(params))
        self.assertEqual(response['status'], 200)
        self.assertEqual(self.shell.commands, [
            'sudo /sbin/iptables -A INPUT -p TCP -j ACCEPT --dport 3000 -m conntrack --ctstate NEW,ESTABLISHED',
            'sudo /sbin/iptables -A INPUT -p TCP -j DROP --dport 3000',
            'echo 0 | sudo tee /proc/sys/net/netfilter/nf_conntrack_tcp_loose',
            'echo 101 | sudo tee /proc/sys/net/netfilter/nf_conntrack_tcp_timeout_established'])


    def test_normally_distributed_delay(self):
        params = {
            'name': "normally-distributed-delay",
            'type': "DELAY",
            'direction': "IN",
            'to_port': 4411,
            'delay': 160,
            'variance': 12,
            'distribution': 'normal'}
        self.shell.next_result = 'eth0\nvmnet8'
        response = self.app.handle(post_request(params))
        self.assertEqual(response['status'], 200)
        self.assertEqual(self.shell.commands, [
            "netstat -i | tail -n+3 | cut -f1 -d ' '",
            'sudo /sbin/tc qdisc add dev eth0 root handle 1: prio',
            'sudo /sbin/tc qdisc add dev eth0 parent 1:3 handle 11: netem delay 160ms 12ms distribution normal',
            'sudo /sbin/tc filter add dev eth0 protocol ip parent 1:0 prio 3 u32 match ip sport 4411 0xffff flowid 1:3',
            'sudo /sbin/tc qdisc add dev vmnet8 root handle 1: prio',
            'sudo /sbin/tc qdisc add dev vmnet8 parent 1:3 handle 11: netem delay 160ms 12ms distribution normal',
            'sudo /sbin/tc filter add dev vmnet8 protocol ip parent 1:0 prio 3 u32 match ip sport 4411 0xffff flowid 1:3'])

    def test_pareto_distributed_delay(self):
        params = {
            'name': "pareto-distributed-delay",
            'type': "DELAY",
            'direction': "IN",
            'to_port': 8822,
            'delay': 350,
            'variance': 50,
            'distribution': 'pareto'}
        self.shell.next_result = 'eth0'
        response = self.app.handle(post_request(params))
        self.assertEqual(response['status'], 200)
        self.assertEqual(self.shell.commands, [
            "netstat -i | tail -n+3 | cut -f1 -d ' '",
            'sudo /sbin/tc qdisc add dev eth0 root handle 1: prio',
            'sudo /sbin/tc qdisc add dev eth0 parent 1:3 handle 11: netem delay 350ms 50ms distribution pareto',
            'sudo /sbin/tc filter add dev eth0 protocol ip parent 1:0 prio 3 u32 match ip sport 8822 0xffff flowid 1:3'])

    def test_uniformly_distributed_delay(self):
        params = {
            'name': "uniformly-distributed-delay",
            'type': "DELAY",
            'direction': "IN",
            'to_port': 8822,
            'delay': 120,
            'variance': 5,
            'correlation': 25}
        self.shell.next_result = 'eth0'
        response = self.app.handle(post_request(params))
        self.assertEqual(response['status'], 200)
        self.assertEqual(self.shell.commands, [
            "netstat -i | tail -n+3 | cut -f1 -d ' '",
            'sudo /sbin/tc qdisc add dev eth0 root handle 1: prio',
            'sudo /sbin/tc qdisc add dev eth0 parent 1:3 handle 11: netem delay 120ms 5ms 25%',
            'sudo /sbin/tc filter add dev eth0 protocol ip parent 1:0 prio 3 u32 match ip sport 8822 0xffff flowid 1:3'])


    def test_outbound_delay(self):
        params = {
            'name': "outbound-delay",
            'type': "DELAY",
            'direction': "OUT",
            'to_port': 8822,
            'delay': 350}
        self.shell.next_result = 'eth0'
        response = self.app.handle(post_request(params))
        self.assertEqual(response['status'], 200)
        self.assertEqual(self.shell.commands, [
            "netstat -i | tail -n+3 | cut -f1 -d ' '",
            'sudo /sbin/tc qdisc add dev eth0 root handle 1: prio',
            'sudo /sbin/tc qdisc add dev eth0 parent 1:3 handle 11: netem delay 350ms',
            'sudo /sbin/tc filter add dev eth0 protocol ip parent 1:0 prio 3 u32 match ip dport 8822 0xffff flowid 1:3'])

    def test_packet_loss(self):
        params = {
            'name': "packet-loss",
            'type': "PACKET_LOSS",
            'direction': "IN",
            'to_port': 9191,
            'probability': 0.3}
        self.shell.next_result = 'eth0'
        response = self.app.handle(post_request(params))
        self.assertEqual(response['status'], 200)
        self.assertEqual(self.shell.commands, [
            "netstat -i | tail -n+3 | cut -f1 -d ' '",
            'sudo /sbin/tc qdisc add dev eth0 root handle 1: prio',
            'sudo /sbin/tc qdisc add dev eth0 parent 1:3 handle 11: netem loss 0.3%',
            'sudo /sbin/tc filter add dev eth0 protocol ip parent 1:0 prio 3 u32 match ip sport 9191 0xffff flowid 1:3'])

    def test_packet_loss_with_correlation(self):
        params = {
            'name': "packet-loss-with-correlation",
            'type': "PACKET_LOSS",
            'direction': "IN",
            'to_port': 9191,
            'probability': 0.2,
            'correlation': 21}
        self.shell.next_result = 'eth0'
        response = self.app.handle(post_request(params))
        self.assertEqual(response['status'], 200)
        self.assertEqual(self.shell.commands, [
            "netstat -i | tail -n+3 | cut -f1 -d ' '",
            'sudo /sbin/tc qdisc add dev eth0 root handle 1: prio',
            'sudo /sbin/tc qdisc add dev eth0 parent 1:3 handle 11: netem loss 0.2% 21%',
            'sudo /sbin/tc filter add dev eth0 protocol ip parent 1:0 prio 3 u32 match ip sport 9191 0xffff flowid 1:3'])

    def test_invalid_parameter_key(self):
        params = {'bad_field': 5}
        response = self.app.handle(post_request(params))
        response_data = json.loads(response['body'])

        self.assertEqual(response['status'], 400)
        self.assertEqual(response_data['message'], "'bad_field' is not a valid parameter")

    def test_missing_required_general_parameter(self):
        params = {'name': 'whatever', 'type': 'PACKET_LOSS'}
        response = self.app.handle(post_request(params))
        response_data = json.loads(response['body'])

        self.assertEqual(response['status'], 400)
        self.assertEqual(response_data['message'], "'direction' is a required parameter")

    def test_invalid_fault_type(self):
        params = {
            'name': "no-chance",
            'type': "ATOMIC_BOMB",
            'direction': "IN"}
        response = self.app.handle(post_request(params))
        response_data = json.loads(response['body'])

        self.assertEqual(response['status'], 400)
        self.assertEqual(response_data['message'], "'ATOMIC_BOMB' is not a valid fault type")