# Copyright 2017 The Forseti Security Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Tests for google.cloud.forseti.enforcer.gce_firewall_enforcer."""
import copy
import json
import threading
import unittest
import unittest.mock as mock

from googleapiclient import errors
import parameterized

from google.cloud.forseti.common.gcp_api import compute
from google.cloud.forseti.common.gcp_api import errors as api_errors
from google.cloud.forseti.common.gcp_api import repository_mixins
from google.cloud.forseti.enforcer import gce_firewall_enforcer as fe
from tests.enforcer import testing_constants as constants
from tests.unittest_utils import ForsetiTestCase

class HelperFunctionTest(ForsetiTestCase):
    """Unit tests for helper functions."""

    def test_get_network_name_from_url(self):
        """Verify that we can get the network name given a network url."""
        url = ('https://www.googleapis.com/compute/{}/projects/'
               'example.com:testing/global/networks/'
               'expected-network').format(fe.API_VERSION)
        self.assertEqual('expected-network',
                         fe.get_network_name_from_url(url))

    def test_build_network_url(self):
        """Verify that we can get a url from project and network name."""
        self.assertEqual('https://www.googleapis.com/compute/{}/projects/'
                         'example.com:testing/global/networks/'
                         'mytestnet'.format(fe.API_VERSION),
                         fe.build_network_url('example.com:testing',
                                              'mytestnet'))

    def test_is_successful(self):
        """_is_successful should know about bad responses and OK responses."""
        self.assertTrue(
            fe._is_successful({
                'kind': 'compute#operation'
            }))
        self.assertFalse(
            fe._is_successful({
                'error': {
                    'errors': [{
                        'code': 'NOT_FOUND'
                    }]
                }
            }))

        # We ignore the following errors:
        # RESOURCE_ALREADY_EXISTS: Because another program somewhere else
        #     could have already added the rule.
        # INVALID_FIELD_VALUE: Because the network probably disappeared
        #     out from under us.
        self.assertTrue(
            fe._is_successful({
                'error': {
                    'errors': [{
                        'code': 'RESOURCE_ALREADY_EXISTS'
                    }]
                }
            }))

        self.assertTrue(
            fe._is_successful({
                'error': {
                    'errors': [{
                        'code': 'INVALID_FIELD_VALUE'
                    }]
                }
            }))


class FirewallRulesTest(ForsetiTestCase):
    """Tests for the FirewallRules class."""

    def setUp(self):
        """Set up."""
        self.firewall_rules = fe.FirewallRules(constants.TEST_PROJECT)
        self.test_rule = copy.deepcopy(
            constants.EXPECTED_FIREWALL_RULES['test-network-allow-internal-1'])


    def test_add_rule_for_an_invalid_rule_type(self):
        """Validate that invalid rules raises an exception.

        Setup:
          * Knowing that add_rule is expecting a dict

        Expected Results:
          * add_rules should raise InvalidFirewallRuleError
        """
        rule_types_to_try = [[], '', 1]

        for rule in rule_types_to_try:
          with self.assertRaises(fe.InvalidFirewallRuleError) as r:
              self.firewall_rules.add_rule(
                  rule, network_name=constants.TEST_NETWORK)

    def test_add_rules_from_api(self):
        """Validate that add_rules_from_api adds appropriate rules.

        Setup:
          * Break the mock current firewall rules into two pages to validate
            nextPageToken works as expected.
          * Set compute.firewalls().list() to return to two pages of data.

        Expected Results:
          * Imported rules were successfully added to the rules dictionary.
        """
        mock_compute_client = mock.Mock(spec=compute.ComputeClient)

        mock_compute_client.get_firewall_rules.return_value = (
            constants.EXPECTED_FIREWALL_API_RESPONSE)

        self.firewall_rules.add_rules_from_api(mock_compute_client)

        self.assertSameStructure(constants.EXPECTED_FIREWALL_RULES,
                                 self.firewall_rules.rules)

    def test_add_rules_from_api_add_rule_false(self):
        """Validate function adds no rules when callback returns false.

        Setup:
          * Break the mock current firewall rules into two pages to validate
            nextPageToken works as expected
          * Set compute.firewalls().list() to return to two pages of data
          * Set _add_rule_callback to return False

        Expected Results:
          * No rules were added to the rules dictionary
        """
        mock_compute_client = mock.Mock(spec=compute.ComputeClient)
        mock_compute_client.get_firewall_rules.return_value = (
            constants.EXPECTED_FIREWALL_API_RESPONSE)

        self.firewall_rules._add_rule_callback = lambda _: False
        self.firewall_rules.add_rules_from_api(mock_compute_client)
        self.assertEqual({}, self.firewall_rules.rules)

    def test_add_rules_from_api_add_rule(self):
        """Validate that add_rules_from_api adds appropriate rules.

        Setup:
          * Break the mock current firewall rules into two pages to validate
            nextPageToken works as expected
          * Set compute.firewalls().list() to return to two pages of data
          * Set _add_rule_callback to only return True for specific rules.

        Expected Results:
          * Imported rules were successfully added to the rules dictionary
        """
        mock_compute_client = mock.Mock(spec=compute.ComputeClient)
        mock_compute_client.get_firewall_rules.return_value = (
            constants.EXPECTED_FIREWALL_API_RESPONSE)

        callback = lambda rule: (rule['name'] ==
                                 'test-network-allow-internal-1')

        self.firewall_rules._add_rule_callback = callback
        self.firewall_rules.add_rules_from_api(mock_compute_client)
        expected = {'test-network-allow-internal-1':
                    constants.EXPECTED_FIREWALL_RULES[
                        'test-network-allow-internal-1']}
        self.assertSameStructure(
            expected,
            self.firewall_rules.rules)

    def test_add_rules_for_network(self):
        """Validate adding rules for a specific network.

        Setup:
          * Load a raw policy from a JSON string.
          * Import the raw policy for a specific network.

        Expected Results:
          * Imported rules have the correct names and the correct network
            assigned.
        """
        test_rules = json.loads(constants.RAW_EXPECTED_JSON_POLICY)

        self.firewall_rules.add_rules(
            test_rules, network_name=constants.TEST_NETWORK)
        self.assertSameStructure(constants.EXPECTED_FIREWALL_RULES,
                                 self.firewall_rules.rules)

    def test_add_rules_for_network_short_form(self):
        """Validate adding rules for network using the short network name.

        Setup:
          * Create a sample rule with a network short name
          * Import the rule into a FirewallRules object

        Expected Results:
          * Imported rules have the correct names and the correct network
            assigned.
        """
        # The rule name should not be changed since the rule includes a
        # network already.
        test_rule_name = 'test-rule'
        test_rule = _GenerateTestRule(test_rule_name)

        # Short network name, value under test
        test_rule['network'] = 'global/networks/default'


        self.firewall_rules.add_rules(
            [test_rule], network_name='default')

        # Full network name used by API, given project name of test-project
        expected_network = (
            'https://www.googleapis.com/compute/v1/projects/test-project/'
            'global/networks/default')

        self.assertEqual(expected_network,
                         self.firewall_rules.rules[test_rule_name]['network'])

    def test_add_rules_for_network_long_name(self):
        """Validate adding rules for a specific network with a long name.

        Setup:
          * Load a sample firewall policy.
          * Set the test network name to a 63 character string.
          * Import the policy for the test network.

        Expected Results:
          * Imported rules have the correct name, with the network name
            truncated.
        """
        test_rules = json.loads(constants.RAW_EXPECTED_JSON_POLICY)
        test_network = 'x' * 63

        self.firewall_rules.add_rules(test_rules, network_name=test_network)
        expected_rule_names = [
            'x' * (62 - len(rule['name'])) + '-' + rule['name']
            for rule in test_rules
        ]
        self.assertCountEqual(expected_rule_names,
                              list(self.firewall_rules.rules.keys()))

    def test_add_rules_for_network_long_name_duplicate_rule(self):
        """Validate adding rules for two networks with similar long names.

        Setup:
          * Load a sample firewall policy.
          * Create three test networks with long names differing in the last
            character.
          * Import the policy for all test networks.

        Expected Results:
          * The second rule and third rules, which would have a duplicate name,
            have their names changed to a hash of the original network name.
        """
        test_rules = json.loads(constants.RAW_EXPECTED_JSON_POLICY)
        test_rules = test_rules[:1]

        rule_name = test_rules[0]['name']
        test_networks = ['x' * 62 + str(i) for i in range(3)]

        for network in test_networks:
            self.firewall_rules.add_rules(test_rules, network_name=network)

        expected_rule_names = []

        # test_networks[0], no hash
        expected_rule_names.append('x' * (62 - len(rule_name)) + '-' +
                                   rule_name)

        # test_networks[1], use hashed name
        expected_rule_names.append('hn-' + fe.hashlib.md5(test_networks[1].
                                                          encode())
                                   .hexdigest() + '-' + rule_name)

        # test_networks[2], use hashed name
        expected_rule_names.append('hn-' + fe.hashlib.md5(test_networks[2].
                                                          encode())
                                   .hexdigest() + '-' + rule_name)

        self.assertCountEqual(expected_rule_names,
                              list(self.firewall_rules.rules.keys()))

    def test_add_rules_for_network_is_idempotent(self):
        """Adding rules for a specific network doesn't modify the original.

        Setup:
          * Load a raw policy from a JSON string.
          * Make a deep copy of the raw policy object.
          * Import the raw policy for a specific network.

        Expected Results:
          * raw policy object is still equal to its deep copy.
        """
        test_rules = json.loads(constants.RAW_EXPECTED_JSON_POLICY)

        copy_of_test_rules = copy.deepcopy(test_rules)

        self.firewall_rules.add_rules(
            test_rules, network_name=constants.TEST_NETWORK)
        self.assertSameStructure(test_rules, copy_of_test_rules)

    def test_add_rules_for_network_negative_match(self):
        """Adding rules for a specific network skips rules on other networks.

        Setup:
          * Create a copy of rules for network 'test-network'.
          * Import the rules with a restriction of matching network 'default'.

        Expected Results:
          * No rules are added.
        """
        test_rules = list(constants.EXPECTED_FIREWALL_RULES.values())
        test_network = 'default'

        self.firewall_rules.add_rules(test_rules, network_name=test_network)
        self.assertEqual({}, self.firewall_rules.rules)

    def test_get_rules_for_network(self):
        """Validate get_rules_for_network returns a valid FirewallRules object.

        Setup:
          * Load a raw policy from a JSON string.
          * Import the raw policy for two different networks.
          * Run get_rules_for_network to return a new object with just the rules
            for one of the networks.

        Expected Results:
          * New FirewallRules object will have the correct rules.
        """
        test_rules = json.loads(constants.RAW_EXPECTED_JSON_POLICY)
        test_networks = ['default', constants.TEST_NETWORK]

        for network in test_networks:
            self.firewall_rules.add_rules(test_rules, network_name=network)

        expected_firewall_rules = fe.FirewallRules(constants.TEST_PROJECT)
        expected_firewall_rules.add_rules(
            test_rules, network_name=constants.TEST_NETWORK)

        # Validate that the current rules do not equal the expected rules
        self.assertNotEqual(expected_firewall_rules, self.firewall_rules)

        new_firewall_rules = self.firewall_rules.filtered_by_networks(
            [constants.TEST_NETWORK])
        self.assertEqual(expected_firewall_rules.rules, new_firewall_rules)

    def test_export_and_import_of_rules(self):
        """Validate that exported and imported rules match the original rules.

        Setup:
          * Add EXPECTED_FIREWALL_RULES to a FirewallRules object.
          * Export the rules to a JSON string.
          * Import the string into a new FirewallRules object.

        Expected Results:
          * The two FirewallRules objects are equal.
        """
        test_rules = list(constants.EXPECTED_FIREWALL_RULES.values())
        self.firewall_rules.add_rules(test_rules)

        json_rules = self.firewall_rules.as_json()
        new_firewall_rules = fe.FirewallRules(constants.TEST_PROJECT)
        new_firewall_rules.add_rules_from_json(json_rules)

        self.assertEqual(self.firewall_rules, new_firewall_rules)

    def test_add_rule_duplicate_rules(self):
        """Validate that attempting to add a duplicate rule raises exception.

        Setup:
          * Add 'test-rule' with add_rule
          * Create new rule with same name as 'test-rule'
          * Attempt to add new rule

        Expected Results:
          * add_rule should raise DuplicateFirewallRuleNameError

        """
        # First addition should work.
        self.firewall_rules.add_rule(self.test_rule)
        new_rule = copy.deepcopy(
            constants.EXPECTED_FIREWALL_RULES['test-network-allow-internal-0'])
        new_rule['name'] = self.test_rule['name']
        # Adding the rule
        with self.assertRaises(fe.DuplicateFirewallRuleNameError):
          self.firewall_rules.add_rule(new_rule)


class FirewallEnforcerTest(constants.EnforcerTestCase):
    """Tests for the FirewallEnforcer class."""

    def setUp(self):
        """Set up.

        Creates a FirewallEnforcer object with current and expected rules set to
        an empty FirewallRules object.
        """
        super(FirewallEnforcerTest, self).setUp()

        self.expected_rules = fe.FirewallRules(constants.TEST_PROJECT)
        self.current_rules = fe.FirewallRules(constants.TEST_PROJECT)

        self.project_sema = threading.BoundedSemaphore(value=1)

        self.enforcer = fe.FirewallEnforcer(
            constants.TEST_PROJECT, self.gce_api_client, self.expected_rules,
            self.current_rules, self.project_sema, None)

    def test_apply_firewall_no_changes(self):
        """No changes when current and expected rules match."""
        self.expected_rules.rules = constants.EXPECTED_FIREWALL_RULES
        self.current_rules.rules = constants.EXPECTED_FIREWALL_RULES

        changed_count = self.enforcer.apply_firewall()
        self.assertEqual(0, changed_count)

    def test_apply_firewall_no_rules(self):
        """Raises exception if no expected_rules defined."""
        with self.assertRaises(fe.EmptyProposedFirewallRuleSetError):
            self.enforcer.apply_firewall()

    def test_apply_firewall_allow_empty_ruleset(self):
        """Deletes all current rules if allow_empty_ruleset is true.

        Setup:
          * Set current_rules to EXPECTED_FIREWALL_RULES.
          * Leave expected_rules with no rules defined.
          * Run self.enforcer.apply_firewall with allow_empty_ruleset set to
            true.

        Expected Results:
          * The current rules are deleted, no rules inserted or updated.
        """
        self.current_rules.add_rules(list(constants.EXPECTED_FIREWALL_RULES.values()))

        changed_count = self.enforcer.apply_firewall(allow_empty_ruleset=True)

        self.assertEqual(len(self.current_rules.rules), changed_count)
        self.assertSameStructure(
            sorted(self.enforcer.current_rules.rules.values(), key=sorted),
            sorted(self.enforcer.get_deleted_rules(), key=sorted))
        self.assertEqual([], self.enforcer.get_inserted_rules())
        self.assertEqual([], self.enforcer.get_updated_rules())

    def test_apply_firewall_all_rules_differ_single_network(self):
        """Validate apply_firewall works end to end with no errors.

        Setup:
          * Set expected_policy to RAW_EXPECTED_JSON_POLICY.
          * Set expected_rules to expected_policy for network TEST_NETWORK.
          * Set compute.firewalls().list() to return
            DEFAULT_FIREWALL_API_RESPONSE.
          * Set current_rules to None so ApplyFirewall will query the compute
            API.
          * Run FirewallEnforcer.apply_firewall() with networks set to
            TEST_NETWORKS.

        Expected Results:
          * apply_firewall will return a changed_count of 7 rules
            (3 added, 4 removed).
          * get_deleted_rules will return a list containing the rule from
            current_rules.
          * get_inserted_rules will return a list containing all the rules from
            expected_rules.
          * get_updated_rules will return an empty list.
        """
        expected_policy = json.loads(constants.RAW_EXPECTED_JSON_POLICY)
        self.expected_rules.add_rules(
            expected_policy, network_name=constants.TEST_NETWORK)

        self.gce_api_client.get_firewall_rules.return_value = (
            constants.DEFAULT_FIREWALL_API_RESPONSE)

        self.enforcer.current_rules = None

        changed_count = self.enforcer.apply_firewall(
            networks=[constants.TEST_NETWORK])

        self.assertEqual(7, changed_count)

        self.assertSameStructure(
            sorted(self.enforcer.current_rules.rules.values(), key=sorted),
            sorted(self.enforcer.get_deleted_rules(), key=sorted))

        self.assertSameStructure(
            sorted(self.expected_rules.rules.values(), key=sorted),
            sorted(self.enforcer.get_inserted_rules(), key=sorted))

        self.assertEqual([], self.enforcer.get_updated_rules())

    def test_apply_firewall_multiple_changes(self):
        """Validate apply_firewall works with multiple different rule changes.

        Setup:
          Set EXPECTED_FIREWALL_RULES as the enforced policy.

          Modify the mock current firewall rules as follows:
            1. No change to test-network-allow-internal-1.
            2. Add a new source range to test-network-allow-internal-0.
            3. Remove test-network-allow-public-0.
            4. Add a new fake rule unknown-rule-doesnt-match.

        Expected Results:
          * apply_firewall will return a changed_count of 3 rules.
          * get_deleted_rules will return unknown-rule-doesnt-match.
          * get_inserted_rules will return test-network-allow-public-0.
          * get_updated_rules will return test-network-allow-internal-0.
        """
        self.expected_rules.rules = constants.EXPECTED_FIREWALL_RULES

        # All four of these cases should be tested:
        # Rule zero, we'll leave alone and allow it to match.  It should
        # neither be removed nor added.
        rule_zero = copy.deepcopy(
            constants.EXPECTED_FIREWALL_RULES['test-network-allow-internal-1'])
        self.current_rules.add_rule(rule_zero)

        # Rule one, we'll modify so it needs to be updated.
        rule_one = copy.deepcopy(
            constants.EXPECTED_FIREWALL_RULES['test-network-allow-internal-0'])
        rule_one['sourceRanges'].append('11.0.0.0/8')
        self.current_rules.add_rule(rule_one)

        # Rule two, we won't insert so that it gets re-added.
        rule_two = copy.deepcopy(
            constants.EXPECTED_FIREWALL_RULES['test-network-allow-public-0'])

        # Rule three isn't part of EXPECTED_FIREWALL_RULES.It should be removed.
        rule_three = {
            'allowed': [{
                'IPProtocol': u'icmp'
            }, {
                'IPProtocol': u'udp',
                'ports': [u'1-65535']
            }],
            'description':
                u'Allow communication between instances.',
            'name':
                u'unknown-rule-doesnt-match',
            'network': (u'https://www.googleapis.com/compute/{}/projects/'
                        'example.com:testing/global/networks/'
                        'test-net').format(fe.API_VERSION),
            'sourceRanges': [u'10.2.3.4/32'],
            'logConfig': {'enable': False},
            'disabled': False,
            'priority': 1000,
            'direction': u'INGRESS'
        }
        self.current_rules.add_rule(rule_three)

        changed_count = self.enforcer.apply_firewall()

        self.assertEqual(3, changed_count)

        self.assertSameStructure([rule_three],
                                 self.enforcer.get_deleted_rules())

        self.assertSameStructure([rule_two], self.enforcer.get_inserted_rules())

        expected_updated_rule = (
            constants.EXPECTED_FIREWALL_RULES['test-network-allow-internal-0'])
        self.assertSameStructure([expected_updated_rule],
                                 self.enforcer.get_updated_rules())

    def test_apply_firewall_prechange_callback_false(self):
        """Prechange callback that returns False stops changes from being made.

        Setup:
          Set EXPECTED_FIREWALL_RULES as the enforced policy.

          Modify the mock current firewall rules as follows:
            1. Add a new source range to test-network-allow-internal-0.

          Create a callback function that always returns False.

        Expected Results:
          * apply_firewall will return a changed_count of 0 rules.
          * get_updated_rules will return an empty list.
          * The internal attribute _rules_to_update contains
            test-network-allow-internal-0.
        """
        self.expected_rules.rules = constants.EXPECTED_FIREWALL_RULES

        # Add rules to current_rules
        rules = ['test-network-allow-internal-1', 'test-network-allow-public-0']
        for rule in rules:
            new_rule = copy.deepcopy(constants.EXPECTED_FIREWALL_RULES[rule])
            self.current_rules.add_rule(new_rule)

        # Rule test-net-allow-corp-internal-0 is modified, it needs to be
        # updated.
        rule_one = copy.deepcopy(
            constants.EXPECTED_FIREWALL_RULES['test-network-allow-internal-0'])
        rule_one['sourceRanges'].append('11.0.0.0/8')
        self.current_rules.add_rule(rule_one)

        prechange_callback_func = lambda *args: False

        changed_count = self.enforcer.apply_firewall(
            prechange_callback=prechange_callback_func)
        self.assertEqual(0, changed_count)
        self.assertEqual([], self.enforcer.get_updated_rules())
        self.assertEqual(['test-network-allow-internal-0'],
                         self.enforcer._rules_to_update)

    def test_apply_firewall_prechange_callback_true(self):
        """A prechange callback that returns True allows changes to be made.

        Setup:
          Set EXPECTED_FIREWALL_RULES as the enforced policy.

          Modify the mock current firewall rules as follows:
            1. Add a new source range to test-network-allow-internal-0.

          Create a callback function that always returns True.

        Expected Results:
          * apply_firewall will return a changed_count of 1 rules.
          * get_updated_rules will return test-network-allow-internal-0.
        """
        self.expected_rules.rules = constants.EXPECTED_FIREWALL_RULES

        # Add rules to current_rules
        rules = ['test-network-allow-internal-1', 'test-network-allow-public-0']
        for rule in rules:
            new_rule = copy.deepcopy(constants.EXPECTED_FIREWALL_RULES[rule])
            self.current_rules.add_rule(new_rule)

        # Rule test-net-allow-corp-internal-0 is modified, it needs to be
        # updated.
        rule_one = copy.deepcopy(
            constants.EXPECTED_FIREWALL_RULES['test-network-allow-internal-0'])
        rule_one['sourceRanges'].append('11.0.0.0/8')
        self.current_rules.add_rule(rule_one)

        prechange_callback_func = lambda *args: True

        changed_count = self.enforcer.apply_firewall(
            prechange_callback=prechange_callback_func)
        self.assertEqual(1, changed_count)

        self.assertSameStructure(
            self.enforcer.get_updated_rules(),
            [constants.EXPECTED_FIREWALL_RULES['test-network-allow-internal-0']]
        )

    def test_build_change_set_all_rules_differ_single_network(self):
        """Build a change set for a single network when all rules differ.

        Setup:
          * Set expected_rules to RAW_EXPECTED_JSON_POLICY on 'test-network' and
            'default' networks.
          * Set current_rules to RAW_DEFAULT_JSON_POLICY on 'test-network' and
            'default' networks.
          * Execute FirewallEnforcer._build_change_set(['test-network']).

        Expected Results:
          * All rules in current_rules for 'test-network' are in
            self.enforcer._rules_to_delete.
          * All rules in expected_rules for 'test-network' are in
            self.enforcer._rules_to_insert.
          * No rules in self.enforcer._rules_to_update.
        """
        expected_policy = json.loads(constants.RAW_EXPECTED_JSON_POLICY)
        self.expected_rules.add_rules(
            expected_policy, network_name=constants.TEST_NETWORK)
        self.expected_rules.add_rules(expected_policy, network_name='default')

        current_policy = json.loads(constants.RAW_DEFAULT_JSON_POLICY)
        self.current_rules.add_rules(
            current_policy, network_name=constants.TEST_NETWORK)
        self.current_rules.add_rules(current_policy, network_name='default')

        self.enforcer._build_change_set([constants.TEST_NETWORK])

        expected_deleted_rules = sorted(constants.DEFAULT_FIREWALL_RULES.keys())

        expected_inserted_rules = sorted(
            constants.EXPECTED_FIREWALL_RULES.keys())

        self.assertListEqual(expected_deleted_rules,
                             sorted(self.enforcer._rules_to_delete))

        self.assertListEqual(expected_inserted_rules,
                             sorted(self.enforcer._rules_to_insert))

        self.assertListEqual([], self.enforcer._rules_to_update)

    def test_build_change_set(self):
        """Verify output of FirewallEnforcer._build_change_set.

        Setup:
          Set EXPECTED_FIREWALL_RULES as the enforced policy.

          Modify the mock current firewall rules as follows:
            1. No change to test-network-allow-internal-0.
            2. Add a new source range to test-network-allow-internal-1.
            3. Remove test-network-allow-public-0.
            4. Add a new fake rule unknown-rule-doesnt-match.

        Expected results:
          * test-network-allow-public-0 in _rules_to_add list.
          * test-network-allow-internal-1 in _rules_to_update list.
          * unknown-rule-doesnt-match in _rules_to_delete list.
        """
        self.expected_rules.rules = constants.EXPECTED_FIREWALL_RULES

        # All four of these cases should be tested:
        # Rule zero, we'll leave alone and allow it to match.  It should
        # neither be removed nor added.
        rule_zero = copy.deepcopy(
            constants.EXPECTED_FIREWALL_RULES['test-network-allow-internal-0'])
        self.current_rules.add_rule(rule_zero)

        # Rule one, we'll modify so it needs to be updated.
        rule_one = copy.deepcopy(
            constants.EXPECTED_FIREWALL_RULES['test-network-allow-internal-1'])
        rule_one['sourceRanges'].append('11.0.0.0/8')
        self.current_rules.add_rule(rule_one)

        # Rule two isn't part of EXPECTED_FIREWALL_RULES.  It should be
        # removed.
        rule_two = {
            'allowed': [{
                'IPProtocol': u'udp',
                'ports': [u'1-65535']
            }, {
                'IPProtocol': u'icmp'
            }],
            'description':
                u'Allow communication between instances.',
            'name':
                u'unknown-rule-doesnt-match',
            'network': (u'https://www.googleapis.com/compute/beta/projects/'
                        'forseti-system-test/global/networks/test-net'),
            'sourceRanges': [u'10.2.3.4/32'],
        }
        self.current_rules.add_rule(rule_two)

        self.enforcer._build_change_set()

        self.assertListEqual(['test-network-allow-public-0'],
                             self.enforcer._rules_to_insert)

        self.assertListEqual(['test-network-allow-internal-1'],
                             self.enforcer._rules_to_update)

        self.assertListEqual(['unknown-rule-doesnt-match'],
                             self.enforcer._rules_to_delete)

    def test_validate_change_set_insert_rule_exists(self):
        """Ensure validate_change_set raises exceptions on duplicate rules.

        Setup:
          * Set current_rules to EXPECTED_FIREWALL_RULES.
          * Set expected_rules to EXPECTED_FIREWALL_RULES.
          * Mock a _rules_to_insert list with a rule in EXPECTED_FIREWALL_RULES.
          * Run _validate_change_set with networks set to ['test-network'].

        Expected Results:
          A FirewallEnforcementFailedError exception will be raised
        """
        self.current_rules.rules = constants.EXPECTED_FIREWALL_RULES
        self.expected_rules.rules = constants.EXPECTED_FIREWALL_RULES

        # Insert a rule that is already defined in current_rules and not deleted
        self.enforcer._rules_to_insert.append('test-network-allow-internal-0')
        with self.assertRaises(fe.FirewallRuleValidationError):
            # ValidateChangeSet only checks rules if networks is not None
            self.enforcer._validate_change_set(
                networks=[constants.TEST_NETWORK])

    def test_validate_change_update_rule_modifies_wrong_network(self):
        """Raises exceptions when wrong network impacted.

        Setup:
          * Set current_rules to RAW_EXPECTED_JSON_POLICY on 'test-network' and
            'default' networks.
          * Set expected_rules to RAW_EXPECTED_JSON_POLICY on 'test-network' and
            'default' networks.
          * Mock a _rules_to_update list with a rule on the 'default' network.
          * Run _validate_change_set with networks set to ['test-network'].

        Expected Results:
          A FirewallEnforcementFailedError exception will be raised.
        """
        default_policy = json.loads(constants.RAW_EXPECTED_JSON_POLICY)
        self.expected_rules.add_rules(
            default_policy, network_name=constants.TEST_NETWORK)
        self.expected_rules.add_rules(default_policy, network_name='default')

        self.current_rules.add_rules(
            default_policy, network_name=constants.TEST_NETWORK)
        self.current_rules.add_rules(default_policy, network_name='default')

        # Update a rule that is defined in current_rules on a different network
        self.enforcer._rules_to_update.append('default-allow-internal-0')
        with self.assertRaises(fe.NetworkImpactValidationError):
            self.enforcer._validate_change_set(
                networks=[constants.TEST_NETWORK])

    def test_validate_change_update_rule_without_networks(self):
        """Do not raise exception when networks=None.

        Setup:
          * Set current_rules to RAW_EXPECTED_JSON_POLICY on 'test-network'
            network.
          * Set expected_rules to RAW_EXPECTED_JSON_POLICY on 'test-network'
            network.
          * Mock a _rules_to_update list with a rule on the 'test-network'
            network.
          * Run _validate_change_set.

        Expected Results:
          Method should return None with no exceptions.
        """
        default_policy = json.loads(constants.RAW_EXPECTED_JSON_POLICY)
        self.expected_rules.add_rules(
            default_policy, network_name=constants.TEST_NETWORK)

        self.current_rules.add_rules(
            default_policy, network_name=constants.TEST_NETWORK)

        # Update a rule that is defined in current_rules on a different network
        self.enforcer._rules_to_update.append('test-network-allow-internal-0')

        self.assertEqual(None, self.enforcer._validate_change_set())

    def test_apply_change(self):
        """Validate apply_change works with no errors."""
        delete_function = self.gce_api_client.delete_firewall_rule
        insert_function = self.gce_api_client.insert_firewall_rule
        update_function = self.gce_api_client.patch_firewall_rule

        test_rules = [
            copy.deepcopy(constants.EXPECTED_FIREWALL_RULES[
                'test-network-allow-internal-0'])
        ]

        for function in [delete_function, insert_function, update_function]:
            (successes, failures, change_errors) = self.enforcer._apply_change(
                function, test_rules)
            self.assertSameStructure(test_rules, successes)
            self.assertListEqual([], failures)
            self.assertListEqual([], change_errors)

    def test_apply_change_no_rules(self):
        """Running apply_change with no rules returns empty lists."""
        delete_function = self.gce_api_client.delete_firewall_rule
        (successes, failures, change_errors) = self.enforcer._apply_change(
            delete_function, [])
        self.assertListEqual([], successes)
        self.assertListEqual([], failures)
        self.assertListEqual([], change_errors)

    @mock.patch('google.cloud.forseti.enforcer.gce_firewall_enforcer.LOGGER', autospec=True)
    def test_apply_change_insert_http_error(self, mock_logger):
        """Adds the rule to failures on HttpError exception.

        Setup:
          * Create a status 409 HttpError object.
          * Set insert_function to raise HttpError.
          * Run apply_change with fake insert_function.

        Expected Results:
          * Passed in rule ends up in failures list.
        """
        response = fe.httplib2.Response({
            'status': '409',
            'content-type': 'application/json'
        })
        response.reason = 'Duplicate Rule'
        error_409 = errors.HttpError(response, ''.encode(), uri='')
        err = api_errors.ApiExecutionError(self.project, error_409)

        insert_function = mock.Mock(side_effect=err)

        test_rules = [
            copy.deepcopy(constants.EXPECTED_FIREWALL_RULES[
                'test-network-allow-internal-0'])
        ]

        (successes, failures, change_errors) = self.enforcer._apply_change(
            insert_function, test_rules)
        self.assertSameStructure(test_rules, failures)
        self.assertListEqual([], successes)
        error_str = 'Rule: %s\nError: %s' % (
            test_rules[0].get('name', ''),
            err)
        self.assertListEqual([error_str], change_errors)
        self.assertTrue(mock_logger.exception.called)

    @mock.patch('google.cloud.forseti.enforcer.gce_firewall_enforcer.LOGGER', autospec=True)
    def test_apply_change_operation_status_error(self, mock_logger):
        """Adds the rule to failures on HttpError exception.

        Setup:
          * Create a mock _create_dry_run_response method.
          * Set _create_dry_run_response to return an error response for all
            operations.
          * Run apply_change.

        Expected Results:
          * Passed in rule ends up in failures list.
        """
        insert_function = self.gce_api_client.insert_firewall_rule

        test_rules = [
            copy.deepcopy(constants.EXPECTED_FIREWALL_RULES[
                'test-network-allow-internal-0'])
        ]
        with mock.patch.object(
                repository_mixins, '_create_fake_operation') as mock_dry_run:

            mock_dry_run.return_value = {
                'status': 'DONE',
                'name': 'test',
                'error': {
                    'errors': [{
                        'code': 'ERROR'
                    }]
                }
            }
            (successes, failures, change_errors) = self.enforcer._apply_change(
                insert_function, test_rules)

        self.assertSameStructure(test_rules, failures)
        self.assertListEqual([], successes)
        self.assertListEqual([], change_errors)
        self.assertTrue(mock_logger.error.called)

    @mock.patch('google.cloud.forseti.enforcer.gce_firewall_enforcer.LOGGER', autospec=True)
    def test_apply_change_operation_timeout_error(self, mock_logger):
        """Adds the rule to failures on Operation Timeout exception.

        Setup:
          * Mock insert_firewall_rule to return OperationTimeoutError
          * Run apply_change.

        Expected Results:
          * Passed in rule ends up in failures list.
        """
        test_rules = [
            copy.deepcopy(constants.EXPECTED_FIREWALL_RULES[
                'test-network-allow-internal-0'])
        ]
        with mock.patch.object(
                self.gce_api_client, 'insert_firewall_rule') as mock_insert:

            mock_operation = {
                'kind': 'compute#operation',
                'id': '1234',
                'name': 'operation-1234',
                'operationType': 'insert',
                'targetLink': ('https://www.googleapis.com/compute/v1/projects/'
                               'test-project/global/firewalls/'
                               'test-network-allow-internal-0'),
                'targetId': '123456',
                'status': 'PENDING',
                'user': 'mock_data@example.com',
                'progress': 0,
                'insertTime': '2018-08-02T06:49:34.713-07:00',
                'selfLink': ('https://www.googleapis.com/compute/v1/projects/'
                             'test-project/global/operations/operation-1234')
            }
            err = api_errors.OperationTimeoutError(self.project, mock_operation)
            mock_insert.side_effect = err
            (successes, failures, change_errors) = self.enforcer._apply_change(
                mock_insert, test_rules)

        self.assertSameStructure(test_rules, failures)
        self.assertListEqual([], successes)
        error_str = 'Rule: %s\nError: %s' % (
            test_rules[0].get('name', ''),
            err)
        self.assertListEqual([error_str], change_errors)
        self.assertTrue(mock_logger.exception.called)

    def test_apply_changes(self):
        """Validate _apply_change_set works with no errors.

        Setup:
          * Set current and expected rules to EXPECTED_FIREWALL_RULES.
          * Add one rule each to rules_to_(delete|insert|update).
          * Run _apply_change_set with network set to None

        Expected Results:
          * _apply_change_set will return 3 for the number of rules changed.
          * The methods get_(deleted|inserted|updated)_rules() will each return
            a list containing the rules that were (deleted|inserted|updated) by
            _apply_changes.
        """
        self.current_rules.rules = constants.EXPECTED_FIREWALL_RULES
        self.expected_rules.rules = constants.EXPECTED_FIREWALL_RULES

        self.enforcer._rules_to_delete = ['test-network-allow-internal-1']
        self.enforcer._rules_to_insert = ['test-network-allow-internal-0']
        self.enforcer._rules_to_update = ['test-network-allow-public-0']

        delete_before_insert = False

        changed_count = self.enforcer._apply_change_set(delete_before_insert,
                                                        None)

        self.assertSameStructure([
            constants.EXPECTED_FIREWALL_RULES['test-network-allow-internal-1']
        ], self.enforcer.get_deleted_rules())

        self.assertSameStructure([
            constants.EXPECTED_FIREWALL_RULES['test-network-allow-internal-0']
        ], self.enforcer.get_inserted_rules())

        self.assertSameStructure(
            [constants.EXPECTED_FIREWALL_RULES['test-network-allow-public-0']],
            self.enforcer.get_updated_rules())

    def test_apply_changes_single_network(self):
        """Validate _apply_change_set works on a single network.

        Setup:
          * Set expected_policy to RAW_EXPECTED_JSON_POLICY.
          * Set expected_rules to expected_policy for networks 'default' and
            TEST_NETWORK.
          * Set current_rules to expected_policy for networks 'default' and
            TEST_NETWORK.
          * Add one rule for each network to each of
            rules_to_(delete|insert|update).
          * Run _apply_change_set with network set to 'default'

        Expected Results:
          * Only rules on network 'default' are in the results for
            get_(deleted|inserted|updated)_rules().
        """
        expected_policy = json.loads(constants.RAW_EXPECTED_JSON_POLICY)
        self.expected_rules.add_rules(
            expected_policy, network_name=constants.TEST_NETWORK)
        self.expected_rules.add_rules(
            expected_policy, network_name='default')
        self.current_rules.add_rules(
            expected_policy, network_name=constants.TEST_NETWORK)
        self.current_rules.add_rules(
            expected_policy, network_name='default')

        self.enforcer._rules_to_delete = [
            'test-network-allow-internal-1', 'default-allow-internal-1']
        self.enforcer._rules_to_insert = [
            'test-network-allow-internal-0', 'default-allow-internal-0']
        self.enforcer._rules_to_update = [
            'test-network-allow-public-0', 'default-allow-public-0']

        delete_before_insert = False

        changed_count = self.enforcer._apply_change_set(delete_before_insert,
                                                        'default')
        expected_changed_count = 3

        self.assertEqual(expected_changed_count, changed_count)

        self.assertSameStructure(
            [self.expected_rules.rules['default-allow-internal-1']],
            self.enforcer.get_deleted_rules())

        self.assertSameStructure(
            [self.expected_rules.rules['default-allow-internal-0']],
            self.enforcer.get_inserted_rules())

        self.assertSameStructure(
            [self.expected_rules.rules['default-allow-public-0']],
            self.enforcer.get_updated_rules())

    @mock.patch('google.cloud.forseti.enforcer.gce_firewall_enforcer.LOGGER', autospec=True)
    def test_apply_changes_operation_status_error(self, mock_logger):
        """Validate that an error on a change raises the expected exception.

        Setup:
          * Set current and expected rules to EXPECTED_FIREWALL_RULES.
          * Create a mock _create_dry_run_response method.
          * Set _create_dry_run_response to return an error response for all
            operations.
          * Run _apply_changes three times, once to delete, once to insert and
            once to update, with network set to None.

        Expected Results:
          * Each time it is run, _apply_changes should raise a
            FirewallEnforcementFailedError exception.
          * get_(deleted|inserted|updated)_rules() should return an empty list.
        """
        self.current_rules.rules = constants.EXPECTED_FIREWALL_RULES
        self.expected_rules.rules = constants.EXPECTED_FIREWALL_RULES

        delete_before_insert = False

        self.enforcer._rules_to_delete = ['test-network-allow-internal-0']
        with mock.patch.object(
                repository_mixins, '_create_fake_operation') as mock_dry_run:

            mock_dry_run.return_value = {
                'status': 'DONE',
                'name': 'test',
                'error': {
                    'errors': [{
                        'code': 'NOT_FOUND'
                    }]
                }
            }
            with self.assertRaises(fe.FirewallEnforcementFailedError):
                self.enforcer._apply_change_set(delete_before_insert, None)

            self.assertEqual([], self.enforcer.get_deleted_rules())
            self.enforcer._rules_to_delete = []

            self.enforcer._rules_to_insert = ['test-network-allow-internal-0']
            with self.assertRaises(fe.FirewallEnforcementFailedError):
                self.enforcer._apply_change_set(delete_before_insert, None)
            self.assertEqual([], self.enforcer.get_inserted_rules())
            self.enforcer._rules_to_insert = []

            self.enforcer._rules_to_update = ['test-network-allow-internal-0']
            with self.assertRaises(fe.FirewallEnforcementFailedError):
                self.enforcer._apply_change_set(delete_before_insert, None)
            self.assertEqual([], self.enforcer.get_updated_rules())
            self.enforcer._rules_to_update = []

        self.assertTrue(mock_logger.error.called)

    def test_apply_changes_delete_first(self):
      """Validate _apply_change_set works with no errors.

      Setup:
        * Set current and expected rules to EXPECTED_CORP_FIREWALL_RULES
        * Add one rule each to rules_to_(delete|insert|update)
        * Set _delete_first to True
        * Run _apply_change_set with network set to None

      Expected Results:
        * _apply_change_set will return 3 for the number of rules changed
        * The methods Get(Deleted|Inserted|Updated)Rules() will each return a
          list containing the rules that were (deleted|inserted|updated) by
          _ApplyChanges.
      """

      self.current_rules.rules = constants.EXPECTED_FIREWALL_RULES
      self.expected_rules.rules = constants.EXPECTED_FIREWALL_RULES

      self.enforcer._rules_to_delete = ['test-network-allow-internal-1']
      self.enforcer._rules_to_insert = ['test-network-allow-internal-0']
      self.enforcer._rules_to_update = ['test-network-allow-public-0']

      delete_before_insert = True
      changed_count = self.enforcer._apply_change_set(delete_before_insert,
                                                      None)

      self.assertEqual(3, changed_count)

      self.assertSameStructure(self.enforcer.get_deleted_rules(),
                               [constants.EXPECTED_FIREWALL_RULES[
                                   'test-network-allow-internal-1']])

      self.assertSameStructure(self.enforcer.get_inserted_rules(),
                               [constants.EXPECTED_FIREWALL_RULES[
                                   'test-network-allow-internal-0']])

      self.assertSameStructure(self.enforcer.get_updated_rules(),
                               [constants.EXPECTED_FIREWALL_RULES[
                                   'test-network-allow-public-0']])

    @parameterized.parameterized.expand([
        ('no_quota', 0, 0, 1, 0, True, False),
        ('low_quota_1', 1, 1, 1, 0, True, False),
        ('low_quota_2', 10, 8, 4, 1, True, False),
        ('low_quota_3', 10, 8, 4, 2, False, True),
        ('high_quota_1', 100, 6, 10, 6, False, False),
        ('high_quota_2', 100, 85, 30, 50, False, True),
        ('unknown_quota', None, None, 1, 0, False, True)])
    @mock.patch('google.cloud.forseti.enforcer.gce_firewall_enforcer.LOGGER', autospec=True)
    @mock.patch('google.cloud.forseti.common.gcp_api.compute.LOGGER', autospec=True)
    def test_check_change_operation_order(self, name, quota, usage,
                                          insert_rule_count, delete_rule_count,
                                          expect_exception,
                                          expect_delete_before_insert,
                                          mock_logger_enforcer,
                                          mock_logger_compute):

        """Validate CheckChangeOperationOrder has expected behavior.

        Args:
          quota: The mocked firewall quota limit.
          usage: The mocked current firewall quota usage.
          insert_rule_count: The number of rules that would be inserted.
          delete_rule_count: The number of rules that would be deleted.
          expect_exception: True if an exception should be raised.
          expect_delete_before_insert: The expected return value from the check.

        Setup:
          * Mock project.get() to return a FIREWALLS quota with specific limits.
          * Mock the number of rules that would be inserted and/or deleted.

        Expected Results:
          * When mock project does not have enough quota, an exception is
            raised.
          * When mock project does not have enough quota to insert first and
            then
            delete, the method returns True.
          * When mock project does have enough quota, the method returns False.
        """
        if quota is not None:
          self.gce_api_client.get_project.return_value = {
              'quotas': [{'metric': 'FIREWALLS',
                          'limit': quota,
                          'usage': usage}]}
        else:
          self.gce_api_client.get_project.return_value = {
              'quotas': []}

        if expect_exception:
          with self.assertRaises(fe.FirewallQuotaExceededError):
            self.enforcer._check_change_operation_order(
                insert_rule_count, delete_rule_count)
        else:
          delete_before_insert = self.enforcer._check_change_operation_order(
              insert_rule_count, delete_rule_count)

          self.assertEqual(expect_delete_before_insert, delete_before_insert)


class FirewallRulesAreEqualTest(ForsetiTestCase):
    """Multiple tests for (in)equality between two firewall rules."""

    def setUp(self):
        """Start with two identical rules."""
        self.firewall_rules_1 = fe.FirewallRules(constants.TEST_PROJECT)
        self.firewall_rules_2 = fe.FirewallRules(constants.TEST_PROJECT)

        self.rule_one = {
            'network': ('https://www.googleapis.com/compute/{}/'
                        'projects/example.com:testing/global/networks/'
                        'default').format(fe.API_VERSION),
            'sourceRanges': ['10.240.0.0/16', '10.8.129.0/24'],
            'sourceTags': ['example-source-tag'],
            'allowed': [{
                'IPProtocol': 'udp',
                'ports': ['1-65535']
            }, {
                'IPProtocol': 'tcp',
                'ports': ['80', '443', '8080']
            }, {
                'IPProtocol': 'icmp'
            }],
            'description':
                'Allow communication between instances.',
            'name':
                u'test-network-allow-internal',
            'targetTags': ['example-target-tag']
        }
        self.rule_two = copy.deepcopy(self.rule_one)

    def test_equal(self):
        """Test where two rules are equal."""
        self.firewall_rules_1.add_rule(self.rule_one)
        self.firewall_rules_2.add_rule(self.rule_two)
        self.assertEqual(self.firewall_rules_1, self.firewall_rules_2)

    def test_equal_reversed_elements(self):
        """Test that source ranges are order independent."""
        self.rule_two['sourceRanges'] = [
            r for r in reversed(self.rule_two['sourceRanges'])
        ]

        self.firewall_rules_1.add_rule(self.rule_one)
        self.firewall_rules_2.add_rule(self.rule_two)
        self.assertEqual(self.firewall_rules_1, self.firewall_rules_2)

    def test_equal_reversed_allowed_rules(self):
        """Test that allows in a rule are order independent."""
        self.rule_two[
            'allowed'] = [r for r in reversed(self.rule_two['allowed'])]

        self.firewall_rules_1.add_rule(self.rule_one)
        self.firewall_rules_2.add_rule(self.rule_two)
        self.assertEqual(self.firewall_rules_1, self.firewall_rules_2)

    def test_equal_reversed_allowed_ports(self):
        """Test that ports in an allowed rules are order independent."""
        for allow in self.rule_two['allowed']:
            if 'ports' in allow:
                allow['ports'] = [r for r in reversed(allow['ports'])]

        self.firewall_rules_1.add_rule(self.rule_one)
        self.firewall_rules_2.add_rule(self.rule_two)
        self.assertEqual(self.firewall_rules_1, self.firewall_rules_2)

    def test_inequal_network(self):
        """Test that inequal networks cause inequality."""
        self.rule_two['network'] = self.rule_two['network'].replace(
            'default', 'other')
        self.firewall_rules_1.add_rule(self.rule_one)
        self.firewall_rules_2.add_rule(self.rule_two)
        self.assertNotEqual(self.firewall_rules_1, self.firewall_rules_2)

    def test_inequal_source_ranges(self):
        """Test that inequal sourceRanges cause inequality."""
        self.rule_two['sourceRanges'].append('1.2.3.4/28')
        self.firewall_rules_1.add_rule(self.rule_one)
        self.firewall_rules_2.add_rule(self.rule_two)
        self.assertNotEqual(self.firewall_rules_1, self.firewall_rules_2)

    def test_inequal_allowed(self):
        """Test that inequal 'allowed' causes inequality."""
        self.rule_two['allowed'] = self.rule_two['allowed'][0:1]
        self.firewall_rules_1.add_rule(self.rule_one)
        self.firewall_rules_2.add_rule(self.rule_two)
        self.assertNotEqual(self.firewall_rules_1, self.firewall_rules_2)

    def test_inequal_descriptions(self):
        """Test that inequal descriptions cause inequality."""
        self.rule_two['description'] = 'Other Description.'
        self.firewall_rules_1.add_rule(self.rule_one)
        self.firewall_rules_2.add_rule(self.rule_two)
        self.assertNotEqual(self.firewall_rules_1, self.firewall_rules_2)

    def test_inequal_name(self):
        """Test that inequal name cause inequality."""
        self.rule_two['name'] = 'other-name'
        self.firewall_rules_1.add_rule(self.rule_one)
        self.firewall_rules_2.add_rule(self.rule_two)
        self.assertNotEqual(self.firewall_rules_1, self.firewall_rules_2)

    def test_inequal_source_tags(self):
        """Test that inequal sourceTags cause inequality."""
        self.rule_two['sourceTags'] = []
        self.firewall_rules_1.add_rule(self.rule_one)
        self.firewall_rules_2.add_rule(self.rule_two)
        self.assertNotEqual(self.firewall_rules_1, self.firewall_rules_2)

    def test_inequal_target_tags(self):
        """Test that inequal targetTags cause inequality."""
        self.rule_two['targetTags'] = ['http-server']
        self.firewall_rules_1.add_rule(self.rule_one)
        self.firewall_rules_2.add_rule(self.rule_two)
        self.assertNotEqual(self.firewall_rules_1, self.firewall_rules_2)


def _GenerateTestRule(name):
    return {
        'name': name,
        'network': ('https://www.googleapis.com/compute/v1/projects/'
                    'test-project/global/networks/fake-network'),
        'description': 'fake rule description',
        'direction': 'INGRESS',
        'allowed': [{'IPProtocol': 'TCP'}],
        'sourceRanges': ['10.0.0.0/8']
    }


if __name__ == '__main__':
    unittest.main()