Example #1
0
    def setUp(self):
        """
        Per-test setup method.
        """
        self.container_id = "ff3afbd1-17ad-499d-b514-72438c009e81"
        self.network_config = {
            "name": "ut-network",
            "type": "calico",
            "ipam": {
                "type": "calico-ipam",
                ASSIGN_IPV4_KEY: "true",
                ASSIGN_IPV6_KEY: "true"
            }
        }
        self.env = {
                CNI_CONTAINERID_ENV: self.container_id,
                CNI_IFNAME_ENV: "eth0",
                CNI_ARGS_ENV: "",
                CNI_COMMAND_ENV: CNI_CMD_ADD, 
                CNI_PATH_ENV: "/usr/bin/rkt/",
                CNI_NETNS_ENV: "netns",
        }

        # Create the CniPlugin to test.
        self.plugin = IpamPlugin(self.env, self.network_config["ipam"])

        # Mock out the datastore client.
        self.m_datastore_client = MagicMock(spec=IPAMClient)
        self.plugin.datastore_client = self.m_datastore_client

        # Set expected values.
        self.expected_handle = self.container_id
Example #2
0
    def setUp(self):
        """
        Per-test setup method.
        """
        self.container_id = "ff3afbd1-17ad-499d-b514-72438c009e81"
        self.network_config = {
            "name": "ut-network",
            "type": "calico",
            "ipam": {
                "type": "calico-ipam",
                "subnet": "10.22.0.0/16",
                "routes": [{"dst": "0.0.0.0/0"}],
                "range-start": "",
                "range-end": ""
            }
        }
        self.env = {
                CNI_CONTAINERID_ENV: self.container_id,
                CNI_IFNAME_ENV: "eth0",
                CNI_ARGS_ENV: "",
                CNI_COMMAND_ENV: CNI_CMD_ADD, 
                CNI_PATH_ENV: "/usr/bin/rkt/",
                CNI_NETNS_ENV: "netns",
        }

        # Create the CniPlugin to test.
        self.plugin = IpamPlugin(self.env)

        # Mock out the datastore client.
        self.m_datastore_client = MagicMock(spec=IPAMClient)
        self.plugin.datastore_client = self.m_datastore_client
Example #3
0
    def _call_ipam_plugin(self, env):
        """
        Executes a CNI IPAM plugin.  If `calico-ipam` is the provided IPAM
        type, then calls directly into ipam.py as a performance optimization.

        For all other types of IPAM, searches the CNI_PATH for the
        correct binary and executes it.

        :return: Tuple of return code, response from the IPAM plugin.
        """
        if self.ipam_type == "calico-ipam":
            _log.info("Using Calico IPAM")
            try:
                response = IpamPlugin(env,
                                      self.network_config["ipam"]).execute()
                code = 0
            except CniError as e:
                # We hit a CNI error - return the appropriate CNI formatted
                # error dictionary.
                response = json.dumps({
                    "code": e.code,
                    "msg": e.msg,
                    "details": e.details
                })
                code = e.code
        elif self.ipam_type == "host-local":
            # We've been told to use the "host-local" IPAM plugin.
            # Check if we need to use the Kubernetes podCidr for this node, and
            # if so replace the subnet field with the correct value.
            if self.network_config["ipam"].get("subnet") == "usePodCidr":
                if not self.running_under_k8s:
                    print_cni_error(
                        ERR_CODE_GENERIC, "Invalid network config",
                        "Must be running under Kubernetes to use 'subnet: usePodCidr'"
                    )
                    sys.exit(ERR_CODE_GENERIC)
                _log.info("Using Kubernetes podCIDR for node: %s",
                          self.k8s_node_name)
                pod_cidr = self._get_kubernetes_pod_cidr()
                self.network_config["ipam"]["subnet"] = str(pod_cidr)

            # Call the IPAM plugin.
            _log.debug("Calling host-local IPAM plugin")
            code, response = self._call_binary_ipam_plugin(env)
        else:
            # Using some other IPAM plugin - call it.
            _log.debug("Using binary plugin")
            code, response = self._call_binary_ipam_plugin(env)

        # Return the IPAM return code and output.
        _log.debug("IPAM response (rc=%s): %s", code, response)
        return code, response
Example #4
0
    def setUp(self):
        """
        Per-test setup method.
        """
        CniIpamTest.setUp(self)
        self.container_id = "ff3afbd1-17ad-499d-b514-72438c009e81"
        self.env = {
                CNI_CONTAINERID_ENV: self.container_id,
                CNI_IFNAME_ENV: "eth0",
                CNI_ARGS_ENV: "K8S_POD_NAME=podname;K8S_POD_NAMESPACE=k8sns",
                CNI_COMMAND_ENV: CNI_CMD_ADD, 
                CNI_PATH_ENV: "/usr/bin/rkt/",
                CNI_NETNS_ENV: "netns",
        }

        # Create the CniPlugin to test.
        self.plugin = IpamPlugin(self.env, self.network_config["ipam"])

        # Mock out the datastore client.
        self.m_datastore_client = MagicMock(spec=IPAMClient)
        self.plugin.datastore_client = self.m_datastore_client

        # Set expected values.
        self.expected_handle = "k8sns.podname" 
Example #5
0
class CniIpamTest(unittest.TestCase):
    """
    Test class for IPAM plugin.
    """
    def setUp(self):
        """
        Per-test setup method.
        """
        self.container_id = "ff3afbd1-17ad-499d-b514-72438c009e81"
        self.network_config = {
            "name": "ut-network",
            "type": "calico",
            "ipam": {
                "type": "calico-ipam",
                "subnet": "10.22.0.0/16",
                "routes": [{"dst": "0.0.0.0/0"}],
                "range-start": "",
                "range-end": ""
            }
        }
        self.env = {
                CNI_CONTAINERID_ENV: self.container_id,
                CNI_IFNAME_ENV: "eth0",
                CNI_ARGS_ENV: "",
                CNI_COMMAND_ENV: CNI_CMD_ADD, 
                CNI_PATH_ENV: "/usr/bin/rkt/",
                CNI_NETNS_ENV: "netns",
        }

        # Create the CniPlugin to test.
        self.plugin = IpamPlugin(self.env)

        # Mock out the datastore client.
        self.m_datastore_client = MagicMock(spec=IPAMClient)
        self.plugin.datastore_client = self.m_datastore_client

    @patch('sys.stdout', new_callable=StringIO)
    def test_execute_add_mainline(self, m_stdout):
        # Mock
        self.plugin.command = CNI_CMD_ADD
        ip4 = IPNetwork("1.2.3.4/32")
        ip6 = IPNetwork("ba:ad::be:ef/128")
        self.plugin._assign_address = MagicMock(spec=self.plugin._assign_address)
        self.plugin._assign_address.return_value = ip4, ip6

        # Call 
        ret = self.plugin.execute()

        # Assert
        expected = json.dumps({"ip4": {"ip": "1.2.3.4/32"}, 
                               "ip6": {"ip": "ba:ad::be:ef/128"}})
        assert_equal(ret, expected)

    @patch('sys.stdout', new_callable=StringIO)
    def test_execute_del_mainline(self, m_stdout):
        # Mock
        self.plugin.command = CNI_CMD_DELETE

        # Call 
        self.plugin.execute()

        # Assert
        expected = ''
        assert_equal(m_stdout.getvalue().strip(), expected)
        self.plugin.datastore_client.release_ip_by_handle.assert_called_once_with(handle_id=self.plugin.container_id)

    @patch('sys.stdout', new_callable=StringIO)
    def test_execute_del_not_assigned(self, m_stdout):
        # Mock
        self.plugin.command = CNI_CMD_DELETE
        self.plugin.datastore_client.release_ip_by_handle.side_effect = KeyError

        # Call 
        self.plugin.execute()

        # Assert
        expected = ''
        assert_equal(m_stdout.getvalue().strip(), expected)

    def test_assign_address_mainline(self):
        # Mock
        ip4 = IPNetwork("1.2.3.4/32")
        ip6 = IPNetwork("ba:ad::be:ef/128")
        self.plugin.datastore_client.auto_assign_ips = MagicMock(spec=self.plugin._assign_address)
        self.plugin.datastore_client.auto_assign_ips.return_value = [ip4], [ip6]

        # Args
        handle_id = "abcdef12345"

        # Call
        ret_ip4, ret_ip6 = self.plugin._assign_address(handle_id)

        # Assert
        assert_equal(ip4, ret_ip4)
        assert_equal(ip6, ret_ip6)

    def test_assign_address_runtime_err(self):
        # Mock
        self.plugin.datastore_client.auto_assign_ips = MagicMock(spec=self.plugin._assign_address)
        self.plugin.datastore_client.auto_assign_ips.side_effect = RuntimeError

        # Args
        handle_id = "abcdef12345"

        # Call
        with assert_raises(CniError) as err:
            self.plugin._assign_address(handle_id)
        e = err.exception
        assert_equal(e.code, ERR_CODE_GENERIC)

    @patch("ipam._exit_on_error", autospec=True)
    def test_assign_address_no_ipv4(self, m_exit):
        # Mock
        ip6 = IPNetwork("ba:ad::be:ef/128")
        self.plugin.datastore_client.auto_assign_ips = MagicMock(spec=self.plugin._assign_address)
        self.plugin.datastore_client.auto_assign_ips.return_value = [], [ip6]

        # Args
        handle_id = "abcdef12345"

        # Call
        with assert_raises(CniError) as err:
            self.plugin._assign_address(handle_id)
        e = err.exception

        # Assert
        assert_equal(e.code, ERR_CODE_GENERIC)

    @patch("ipam._exit_on_error", autospec=True)
    def test_assign_address_no_ipv6(self, m_exit):
        # Mock
        ip4 = IPNetwork("1.2.3.4/32")
        self.plugin.datastore_client.auto_assign_ips = MagicMock(spec=self.plugin._assign_address)
        self.plugin.datastore_client.auto_assign_ips.return_value = [ip4], []

        # Args
        handle_id = "abcdef12345"

        # Call
        with assert_raises(CniError) as err:
            self.plugin._assign_address(handle_id)
        e = err.exception

        # Assert
        assert_equal(e.code, ERR_CODE_GENERIC)

    def test_parse_environment_no_command(self):
        # Delete command.
        del self.env[CNI_COMMAND_ENV]

        # Call
        with assert_raises(CniError) as err:
            self.plugin._parse_environment(self.env)
        e = err.exception
        assert_equal(e.code, ERR_CODE_GENERIC)

    def test_parse_environment_invalid_command(self):
        # Change command.
        self.env[CNI_COMMAND_ENV] = "invalid"

        # Call
        with assert_raises(CniError) as err:
            self.plugin._parse_environment(self.env)
        e = err.exception
        assert_equal(e.code, ERR_CODE_GENERIC)

    def test_parse_environment_invalid_container_id(self):
        # Delete container ID.
        del self.env[CNI_CONTAINERID_ENV] 

        # Call
        with assert_raises(CniError) as err:
            self.plugin._parse_environment(self.env)
        e = err.exception
        assert_equal(e.code, ERR_CODE_GENERIC)

    def test_exit_on_error(self):
        with assert_raises(SystemExit) as err:
            _exit_on_error(1, "message", "details")
        e = err.exception
        assert_equal(e.code, 1)

    @patch("ipam.os", autospec=True)
    @patch("ipam.sys", autospec=True)
    @patch("ipam.IpamPlugin", autospec=True)
    @patch("ipam.configure_logging", autospec=True)
    def test_main(self, m_conf_log, m_plugin, m_sys, m_os):
        # Mock
        m_os.environ = self.env
        m_sys.stdin.readlines.return_value = json.dumps(self.network_config)
        m_plugin.reset_mock()

        # Call
        main()

        # Assert
        m_plugin.assert_called_once_with(self.env)
        m_plugin(self.env).execute.assert_called_once_with()

    @patch("ipam.os", autospec=True)
    @patch("ipam.sys", autospec=True)
    @patch("ipam.IpamPlugin", autospec=True)
    @patch("ipam.configure_logging", autospec=True)
    @patch("ipam._exit_on_error", autospec=True)
    def test_main_execute_cni_error(self, m_exit, m_conf_log, m_plugin, m_sys, m_os):
        # Mock
        m_os.environ = self.env
        m_sys.stdin.readlines.return_value = json.dumps(self.network_config)
        m_plugin.reset_mock()
        m_plugin(self.env).execute.side_effect = CniError(50, "Message", "Details") 

        # Call
        main()

        # Assert
        m_exit.assert_called_once_with(50, "Message", "Details")

    @patch("ipam.os", autospec=True)
    @patch("ipam.sys", autospec=True)
    @patch("ipam.IpamPlugin", autospec=True)
    @patch("ipam.configure_logging", autospec=True)
    @patch("ipam._exit_on_error", autospec=True)
    def test_main_execute_unhandled_error(self, m_exit, m_conf_log, m_plugin, m_sys, m_os):
        # Mock
        m_os.environ = self.env
        m_sys.stdin.readlines.return_value = json.dumps(self.network_config)
        m_plugin.reset_mock()
        m_plugin(self.env).execute.side_effect = Exception

        # Call
        main()

        # Assert
        m_exit.assert_called_once_with(ERR_CODE_GENERIC, message=ANY, details=ANY)
Example #6
0
class CniIpamTest(unittest.TestCase):
    """
    Test class for IPAM plugin.
    """
    def setUp(self):
        """
        Per-test setup method.
        """
        self.container_id = "ff3afbd1-17ad-499d-b514-72438c009e81"
        self.network_config = {
            "name": "ut-network",
            "type": "calico",
            "ipam": {
                "type": "calico-ipam",
                ASSIGN_IPV4_KEY: "true",
                ASSIGN_IPV6_KEY: "true"
            }
        }
        self.env = {
                CNI_CONTAINERID_ENV: self.container_id,
                CNI_IFNAME_ENV: "eth0",
                CNI_ARGS_ENV: "",
                CNI_COMMAND_ENV: CNI_CMD_ADD, 
                CNI_PATH_ENV: "/usr/bin/rkt/",
                CNI_NETNS_ENV: "netns",
        }

        # Create the CniPlugin to test.
        self.plugin = IpamPlugin(self.env, self.network_config["ipam"])

        # Mock out the datastore client.
        self.m_datastore_client = MagicMock(spec=IPAMClient)
        self.plugin.datastore_client = self.m_datastore_client

        # Set expected values.
        self.expected_handle = self.container_id

    @patch('sys.stdout', new_callable=StringIO)
    def test_execute_add_mainline(self, m_stdout):
        # Mock
        self.plugin.command = CNI_CMD_ADD
        ip4 = IPNetwork("1.2.3.4/32")
        ip6 = IPNetwork("ba:ad::be:ef/128")
        self.plugin._assign_address = MagicMock(spec=self.plugin._assign_address)
        self.plugin._assign_address.return_value = ip4, ip6

        # Call 
        ret = self.plugin.execute()

        # Assert
        expected = json.dumps({"ip4": {"ip": "1.2.3.4/32"}, 
                               "ip6": {"ip": "ba:ad::be:ef/128"}})
        assert_equal(ret, expected)

    @patch('sys.stdout', new_callable=StringIO)
    def test_execute_del_mainline(self, m_stdout):
        # Mock
        self.plugin.command = CNI_CMD_DELETE

        # Call 
        self.plugin.execute()

        # Assert
        expected = ''
        assert_equal(m_stdout.getvalue().strip(), expected)
        self.plugin.datastore_client.release_ip_by_handle.assert_called_once_with(handle_id=self.expected_handle)

    @patch('sys.stdout', new_callable=StringIO)
    def test_execute_del_not_assigned(self, m_stdout):
        # Mock
        self.plugin.command = CNI_CMD_DELETE
        self.plugin.datastore_client.release_ip_by_handle.side_effect = KeyError

        # Call 
        self.plugin.execute()

        # Assert
        expected = ''
        assert_equal(m_stdout.getvalue().strip(), expected)

    @patch('sys.stdout', new_callable=StringIO)
    def test_execute_add_user_supplied(self, m_stdout):
        # Mock
        self.plugin.command = CNI_CMD_ADD
        self.plugin.ip = "1.2.3.4"
        ip4 = IPNetwork("1.2.3.4/32")
        self.plugin._assign_existing_address = MagicMock(spec=self.plugin._assign_existing_address)
        self.plugin._assign_existing_address.return_value = ip4

        # Call
        ret = self.plugin.execute()

        # Assert
        expected = json.dumps({"ip4": {"ip": "1.2.3.4/32"}})
        assert_equal(ret, expected)

    def test_assign_address_mainline(self):
        # Mock
        ip4 = IPNetwork("1.2.3.4/32")
        ip6 = IPNetwork("ba:ad::be:ef/128")
        self.plugin.datastore_client.auto_assign_ips = MagicMock(spec=self.plugin._assign_address)
        self.plugin.datastore_client.auto_assign_ips.return_value = [ip4], [ip6]

        # Args
        handle_id = "abcdef12345"

        # Call
        ret_ip4, ret_ip6 = self.plugin._assign_address(handle_id)

        # Assert
        assert_equal(ip4, ret_ip4)
        assert_equal(ip6, ret_ip6)

    def test_assign_address_runtime_err(self):
        # Mock
        self.plugin.datastore_client.auto_assign_ips = MagicMock(spec=self.plugin._assign_address)
        self.plugin.datastore_client.auto_assign_ips.side_effect = RuntimeError

        # Args
        handle_id = "abcdef12345"

        # Call
        with assert_raises(CniError) as err:
            self.plugin._assign_address(handle_id)
        e = err.exception
        assert_equal(e.code, ERR_CODE_GENERIC)

    @patch("ipam._exit_on_error", autospec=True)
    def test_assign_address_no_ipv4(self, m_exit):
        # Mock
        ip6 = IPNetwork("ba:ad::be:ef/128")
        self.plugin.datastore_client.auto_assign_ips = MagicMock(spec=self.plugin._assign_address)
        self.plugin.datastore_client.auto_assign_ips.return_value = [], [ip6]

        # Args
        handle_id = "abcdef12345"

        # Call
        with assert_raises(CniError) as err:
            self.plugin._assign_address(handle_id)
        e = err.exception

        # Assert
        assert_equal(e.code, ERR_CODE_GENERIC)

    @patch("ipam._exit_on_error", autospec=True)
    def test_assign_address_no_ipv6(self, m_exit):
        # Mock
        ip4 = IPNetwork("1.2.3.4/32")
        self.plugin.datastore_client.auto_assign_ips = MagicMock(spec=self.plugin._assign_address)
        self.plugin.datastore_client.auto_assign_ips.return_value = [ip4], []

        # Args
        handle_id = "abcdef12345"

        # Call
        with assert_raises(CniError) as err:
            self.plugin._assign_address(handle_id)
        e = err.exception

        # Assert
        assert_equal(e.code, ERR_CODE_GENERIC)

    def test_assign_address_user_supplied_mainline(self):
        # Mock
        input_address = "192.123.123.123"
        expected = IPNetwork(input_address)

        self.plugin.ip = input_address
        self.plugin.datastore_client.assign_ip = Mock(spec=self.plugin._assign_existing_address())

        # Call
        ipv4 = self.plugin._assign_existing_address()

        # Assert
        assert_equal(ipv4, expected)

    def test_assign_address_user_supplied_malformed(self):
        # Mock
        input_address = "192.123.123.321"

        self.plugin.ip = input_address

        # Call
        with assert_raises(CniError) as err:
            self.plugin._assign_existing_address()
        e = err.exception
        assert_equal(e.code, ERR_CODE_GENERIC)

    def test_assign_address_user_supplied_already_assigned(self):
        # Mock
        input_address = "192.123.123.123"

        self.plugin.ip = input_address
        self.plugin.datastore_client.assign_ip = Mock(spec=self.plugin._assign_existing_address())
        self.plugin.datastore_client.assign_ip.side_effect = AlreadyAssignedError()

        with assert_raises(CniError) as err:
            self.plugin._assign_existing_address()
        e = err.exception
        assert_equal(e.code, ERR_CODE_GENERIC)

    def test_assign_address_user_supplied_runtime_error(self):
        # Mock
        input_address = "192.123.123.123"

        self.plugin.ip = input_address
        self.plugin.datastore_client.assign_ip = Mock(spec=self.plugin._assign_existing_address())
        self.plugin.datastore_client.assign_ip.side_effect = RuntimeError()

        with assert_raises(CniError) as err:
            self.plugin._assign_existing_address()
        e = err.exception
        assert_equal(e.code, ERR_CODE_GENERIC)

    def test_parse_environment_no_command(self):
        # Delete command.
        del self.env[CNI_COMMAND_ENV]

        # Call
        with assert_raises(CniError) as err:
            self.plugin._parse_environment(self.env)
        e = err.exception
        assert_equal(e.code, ERR_CODE_GENERIC)

    def test_parse_environment_invalid_command(self):
        # Change command.
        self.env[CNI_COMMAND_ENV] = "invalid"

        # Call
        with assert_raises(CniError) as err:
            self.plugin._parse_environment(self.env)
        e = err.exception
        assert_equal(e.code, ERR_CODE_GENERIC)

    def test_parse_environment_invalid_container_id(self):
        # Delete container ID.
        del self.env[CNI_CONTAINERID_ENV] 

        # Call
        with assert_raises(CniError) as err:
            self.plugin._parse_environment(self.env)
        e = err.exception
        assert_equal(e.code, ERR_CODE_GENERIC)

    def test_exit_on_error(self):
        with assert_raises(SystemExit) as err:
            _exit_on_error(1, "message", "details")
        e = err.exception
        assert_equal(e.code, 1)

    @patch("ipam.os", autospec=True)
    @patch("ipam.sys", autospec=True)
    @patch("ipam.IpamPlugin", autospec=True)
    @patch("ipam.configure_logging", autospec=True)
    def test_main(self, m_conf_log, m_plugin, m_sys, m_os):
        # Mock
        m_os.environ = self.env
        m_sys.stdin.readlines.return_value = json.dumps(self.network_config)
        m_plugin.reset_mock()

        # Call
        main()

        # Assert
        m_plugin.assert_called_once_with(self.env, self.network_config["ipam"])
        m_plugin(self.env, self.network_config["ipam"]).execute.assert_called_once_with()

    @patch("ipam.os", autospec=True)
    @patch("ipam.sys", autospec=True)
    @patch("ipam.IpamPlugin", autospec=True)
    @patch("ipam.configure_logging", autospec=True)
    @patch("ipam._exit_on_error", autospec=True)
    def test_main_execute_cni_error(self, m_exit, m_conf_log, m_plugin, m_sys, m_os):
        # Mock
        m_os.environ = self.env
        m_sys.stdin.readlines.return_value = json.dumps(self.network_config)
        m_plugin.reset_mock()
        m_plugin(self.env, self.network_config["ipam"]).execute.side_effect = CniError(50, "Message", "Details") 

        # Call
        main()

        # Assert
        m_exit.assert_called_once_with(50, "Message", "Details")

    @patch("ipam.os", autospec=True)
    @patch("ipam.sys", autospec=True)
    @patch("ipam.IpamPlugin", autospec=True)
    @patch("ipam.configure_logging", autospec=True)
    @patch("ipam._exit_on_error", autospec=True)
    def test_main_execute_unhandled_error(self, m_exit, m_conf_log, m_plugin, m_sys, m_os):
        # Mock
        m_os.environ = self.env
        m_sys.stdin.readlines.return_value = json.dumps(self.network_config)
        m_plugin.reset_mock()
        m_plugin(self.env, self.network_config["ipam"]).execute.side_effect = Exception

        # Call
        main()

        # Assert
        m_exit.assert_called_once_with(ERR_CODE_GENERIC, message=ANY, details=ANY)