Esempio n. 1
0
    def test_provision_veth_error(self, m_print, m_ns, m_os):
        # Mock
        endpoint = MagicMock(spec=Endpoint)
        endpoint.name = "cali12345"
        m_os.path.abspath.return_value = "/path/to/netns"
        endpoint.provision_veth.side_effect = CalledProcessError(1, "cmd", 3)

        # Mock out cleanup methods.
        self.plugin._remove_workload = MagicMock(
            spec=self.plugin._remove_workload)
        self.plugin._release_ip = MagicMock(spec=self.plugin._release_ip)

        # Call method
        with assert_raises(SystemExit) as err:
            self.plugin._provision_veth(endpoint)
        e = err.exception
        assert_equal(e.code, ERR_CODE_GENERIC)

        # Assert.
        m_ns.assert_called_once_with("/path/to/netns")
        endpoint.provision_veth.assert_called_once_with(
            m_ns("/path/to/netns"), "eth0")
        assert_false(self.plugin._client.set_endpoint.called)
        self.plugin._remove_workload.assert_called_once_with()
        self.plugin._release_ip.assert_called_once_with(ANY)
    def test_join_veth_fail(self, m_del_veth, m_create_veth, m_set_veth_mac,
                            m_next_hops):
        """
        Test the join() processing when create_veth fails.
        """
        m_create_veth.side_effect = CalledProcessError(2, "testcmd")

        m_next_hops.return_value = {4: IPAddress("1.2.3.4"), 6: None}

        # Actually make the request to the plugin.
        rv = self.app.post('/NetworkDriver.Join',
                           data='{"EndpointID": "%s", "NetworkID": "%s"}' %
                           (TEST_ENDPOINT_ID, TEST_NETWORK_ID))

        # Expect a 500 response.
        self.assertDictEqual(
            json.loads(rv.data),
            {u'Err': u"Command 'testcmd' returned non-zero exit status 2"})

        # Check that create veth is called with the expected endpoint, and
        # that set_endpoint is not (since create_veth is raising an exception).
        host_interface_name = generate_cali_interface_name(
            IF_PREFIX, TEST_ENDPOINT_ID)
        temp_interface_name = generate_cali_interface_name(
            "tmp", TEST_ENDPOINT_ID)

        m_create_veth.assert_called_once_with(host_interface_name,
                                              temp_interface_name)

        # Check that we delete the veth.
        m_del_veth.assert_called_once_with(host_interface_name)
Esempio n. 3
0
def invoke(cmd, cmd_input=None, use_except=False, **environ):
    """Runs command and return return code and output.

    Allows passing some input and/or setting all keyword arguments as environ
    variables.

    :param cmd:
        Command to run
    :type cmd:
        ``list``
    :param cmd_input:
        *optional* Provide some input to be passed to the command's STDIN
    :type cmd_input:
        ``str``
    :param environ:
        Environ variable to set prior to running the command
    :type environ:
        ``dict``
    :returns:
        (``(int, str)``) -- Return code and output from executed process
    :raises:
        :class:`CalledProcessError`
    """
    _LOGGER.debug('invoke: %r', cmd)
    args = _alias_command(cmd)

    # Setup a copy of the environ with the provided overrides
    cmd_environ = dict(os.environ.items())
    cmd_environ.update(**environ)

    # Encode any input from unicode
    if cmd_input is not None:
        cmd_input = cmd_input.encode()

    try:
        proc = subprocess.Popen(args,
                                close_fds=_CLOSE_FDS, shell=False,
                                stdin=subprocess.PIPE,
                                stdout=subprocess.PIPE,
                                stderr=subprocess.STDOUT,
                                env=cmd_environ)
        (out, _err) = proc.communicate(cmd_input)
        retcode = proc.returncode

    except Exception:
        _LOGGER.exception('Error invoking %r', args)
        raise

    # Decode output back into unicode
    out = out.decode()

    if retcode != 0 and use_except:
        _LOGGER.error('Command failed: rc:%d: %s', retcode, out)
        raise CalledProcessError(cmd=args,
                                 returncode=retcode,
                                 output=out)

    return (retcode, out)
Esempio n. 4
0
    def test_remove_veth_error(self, m_netns):
        """
        Make sure we handle errors gracefully - don't re-raise.
        """
        # Mock
        endpoint = MagicMock(spec=Endpoint)
        endpoint.name = "cali12345"
        m_netns.remove_veth.side_effect = CalledProcessError(1, "cmd2", 3)

        # Call
        self.plugin._remove_veth(endpoint)
Esempio n. 5
0
def crawl_and_yield(crawlCommand):
    ps = Popen(crawlCommand,
               stdout=PIPE,
               stderr=PIPE,
               cwd=os.path.dirname(os.path.abspath(__file__)))
    for stdout_line in iter(ps.stdout.readline, ""):
        print stdout_line
    ps.stdout.close()
    return_code = ps.wait()
    if return_code:
        raise CalledProcessError(return_code, crawlCommand)
Esempio n. 6
0
    def test_join_veth_fail(self, m_set, m_del_veth, m_veth, m_read, m_next_hops):
        """
        Test the join() processing when create_veth fails.
        """
        m_veth.side_effect = CalledProcessError(2, "testcmd")

        endpoint_json = {"Interfaces":
                          [
                            {"Address": "1.2.3.4",
                             "ID": 0,
                             "MacAddress": "EE:EE:EE:EE:EE:EE"}
                          ]
                        }
        m_read.return_value = endpoint_json

        m_next_hops.return_value = {4: IPAddress("1.2.3.4"),
                                    6: None}

        # Actually make the request to the plugin.
        rv = self.app.post('/NetworkDriver.Join',
                           data='{"EndpointID": "%s", "NetworkID": "%s"}' %
                                (TEST_ENDPOINT_ID, TEST_NETWORK_ID))
        m_read.assert_called_once_with(TEST_ENDPOINT_ID)

        # Check that the create_veth is called with this
        # endpoint.
        endpoint = Endpoint("hostname",
                            "docker",
                            "libnetwork",
                            TEST_ENDPOINT_ID,
                            "active",
                            "EE:EE:EE:EE:EE:EE")
        endpoint.ipv4_gateway = IPAddress("1.2.3.4")
        endpoint.ipv4_nets.add(IPNetwork("1.2.3.4/32"))
        endpoint.profile_ids.append(TEST_NETWORK_ID)

        # Check that create veth is called with the expected endpoint, and
        # that set_endpoint is not (since create_veth is raising an exception).
        m_veth.assert_called_once_with(endpoint)
        assert_equal(m_set.call_count, 0)

        # Check that we delete the veth.
        m_del_veth.assert_called_once_with(endpoint)

        # Expect a 500 response.
        self.assertDictEqual(json.loads(rv.data), ERROR_RESPONSE_500)
class TestPlugin(unittest.TestCase):
    def setUp(self):
        self.app = driver_plugin.app.test_client()

    def tearDown(self):
        pass

    def test_404(self):
        rv = self.app.post('/')
        assert_equal(rv.status_code, 404)

    def test_activate(self):
        rv = self.app.post('/Plugin.Activate')
        activate_response = {"Implements": ["NetworkDriver", "IpamDriver"]}
        self.assertDictEqual(json.loads(rv.data), activate_response)

    def test_get_default_address_spaces(self):
        """
        Test get_default_address_spaces returns the fixed values.
        """
        rv = self.app.post('/IpamDriver.GetDefaultAddressSpaces')
        response_data = {
            "LocalDefaultAddressSpace": "CalicoLocalAddressSpace",
            "GlobalDefaultAddressSpace": "CalicoGlobalAddressSpace"
        }
        self.assertDictEqual(json.loads(rv.data), response_data)

    def test_request_pool_v4(self):
        """
        Test request_pool returns the correct fixed values for IPv4.
        """
        request_data = {"Pool": "", "SubPool": "", "V6": False}
        rv = self.app.post('/IpamDriver.RequestPool',
                           data=json.dumps(request_data))
        response_data = {
            "PoolID": "CalicoPoolIPv4",
            "Pool": "0.0.0.0/0",
            "Data": {
                "com.docker.network.gateway": "0.0.0.0/0"
            }
        }
        self.assertDictEqual(json.loads(rv.data), response_data)

    def test_request_pool_v6(self):
        """
        Test request_pool returns the correct fixed values for IPv6.
        """
        request_data = {"Pool": "", "SubPool": "", "V6": True}
        rv = self.app.post('/IpamDriver.RequestPool',
                           data=json.dumps(request_data))
        response_data = {
            "PoolID": "CalicoPoolIPv6",
            "Pool": "::/0",
            "Data": {
                "com.docker.network.gateway": "::/0"
            }
        }
        self.assertDictEqual(json.loads(rv.data), response_data)

    @patch("libnetwork.driver_plugin.client.get_ip_pools", autospec=True)
    def test_request_pool_valid_ipv4_pool_defined(self, m_get_pools):
        """
        Test request_pool errors if a valid IPv4 pool is requested.
        """
        request_data = {"Pool": "1.2.3.4/26", "SubPool": "", "V6": False}
        m_get_pools.return_value = [IPPool("1.2.3.4/26")]
        rv = self.app.post('/IpamDriver.RequestPool',
                           data=json.dumps(request_data))
        response_data = {
            "PoolID": "1.2.3.4/26",
            "Pool": "0.0.0.0/0",
            "Data": {
                "com.docker.network.gateway": "0.0.0.0/0"
            }
        }
        self.assertDictEqual(json.loads(rv.data), response_data)

    @patch("libnetwork.driver_plugin.client.get_ip_pools", autospec=True)
    def test_request_pool_valid_ipv6_pool_defined(self, m_get_pools):
        """
        Test request_pool errors if a valid IPv6 pool is requested.
        """
        request_data = {"Pool": "11:22::3300/120", "SubPool": "", "V6": True}
        m_get_pools.return_value = [IPPool("11:22::3300/120")]
        rv = self.app.post('/IpamDriver.RequestPool',
                           data=json.dumps(request_data))
        response_data = {
            "PoolID": "11:22::3300/120",
            "Pool": "::/0",
            "Data": {
                "com.docker.network.gateway": "::/0"
            }
        }
        self.assertDictEqual(json.loads(rv.data), response_data)

    @patch("libnetwork.driver_plugin.client.get_ip_pools", autospec=True)
    def test_request_pool_invalid_pool_defined(self, m_get_pools):
        """
        Test request_pool errors if an invalid pool is requested.
        """
        request_data = {"Pool": "1.2.3.0/24", "SubPool": "", "V6": False}
        m_get_pools.return_value = [IPPool("1.2.4.0/24")]
        rv = self.app.post('/IpamDriver.RequestPool',
                           data=json.dumps(request_data))
        self.assertTrue("Err" in json.loads(rv.data))

    def test_request_pool_subpool_defined(self):
        """
        Test request_pool errors if a specific sub-pool is requested.
        """
        request_data = {"Pool": "", "SubPool": "1.2.3.4/5", "V6": False}
        rv = self.app.post('/IpamDriver.RequestPool',
                           data=json.dumps(request_data))
        self.assertTrue("Err" in json.loads(rv.data))

    def test_release_pool(self):
        """
        Test release_pool.
        """
        request_data = {
            "PoolID": "TestPoolID",
        }
        rv = self.app.post('/IpamDriver.ReleasePool',
                           data=json.dumps(request_data))
        self.assertDictEqual(json.loads(rv.data), {})

    @patch("libnetwork.driver_plugin.client.auto_assign_ips", autospec=True)
    def test_request_address_auto_assign_ipv4(self, m_auto_assign):
        """
        Test request_address when IPv4 address is auto-assigned.
        """
        request_data = {"PoolID": "CalicoPoolIPv4", "Address": ""}
        ip = IPAddress("1.2.3.4")
        m_auto_assign.return_value = ([], [ip])
        rv = self.app.post('/IpamDriver.RequestAddress',
                           data=json.dumps(request_data))
        response_data = {"Address": str(IPNetwork(ip)), "Data": {}}
        self.assertDictEqual(json.loads(rv.data), response_data)

    @patch("libnetwork.driver_plugin.client.auto_assign_ips", autospec=True)
    def test_request_address_auto_assign_ipv6(self, m_auto_assign):
        """
        Test request_address when IPv6 address is auto-assigned.
        """
        request_data = {"PoolID": "CalicoPoolIPv6", "Address": ""}
        ip = IPAddress("aa::ff")
        m_auto_assign.return_value = ([], [ip])
        rv = self.app.post('/IpamDriver.RequestAddress',
                           data=json.dumps(request_data))
        response_data = {"Address": str(IPNetwork(ip)), "Data": {}}
        self.assertDictEqual(json.loads(rv.data), response_data)

    @patch("libnetwork.driver_plugin.client.get_ip_pools", autospec=True)
    @patch("libnetwork.driver_plugin.client.auto_assign_ips", autospec=True)
    def test_request_address_assign_ipv4_from_subnet(self, m_auto_assign,
                                                     m_get_pools):
        """
        Test request_address when IPv4 address is auto-assigned from a valid
        subnet.
        """
        request_data = {"PoolID": "1.2.3.0/24", "Address": ""}
        ip = IPAddress("1.2.3.4")
        m_auto_assign.return_value = ([], [ip])
        m_get_pools.return_value = [IPPool("1.2.3.0/24")]
        rv = self.app.post('/IpamDriver.RequestAddress',
                           data=json.dumps(request_data))
        response_data = {"Address": str(IPNetwork(ip)), "Data": {}}
        self.assertDictEqual(json.loads(rv.data), response_data)

    @patch("libnetwork.driver_plugin.client.get_ip_pools", autospec=True)
    @patch("libnetwork.driver_plugin.client.auto_assign_ips", autospec=True)
    def test_request_address_assign_ipv4_from_invalid_subnet(
            self, m_auto_assign, m_get_pools):
        """
        Test request_address when IPv4 address is auto-assigned from an invalid
        subnet.
        """
        request_data = {"PoolID": "1.2.3.0/24", "Address": ""}
        ip = IPAddress("1.2.3.4")
        m_auto_assign.return_value = ([], [ip])
        m_get_pools.return_value = [IPPool("1.2.5.0/24")]
        rv = self.app.post('/IpamDriver.RequestAddress',
                           data=json.dumps(request_data))
        self.assertTrue("Err" in json.loads(rv.data))

    @patch("libnetwork.driver_plugin.client.auto_assign_ips", autospec=True)
    def test_request_address_auto_assign_no_ips(self, m_auto_assign):
        """
        Test request_address when there are no auto assigned IPs.
        """
        request_data = {"PoolID": "CalicoPoolIPv6", "Address": ""}
        m_auto_assign.return_value = ([], [])
        rv = self.app.post('/IpamDriver.RequestAddress',
                           data=json.dumps(request_data))
        self.assertTrue("Err" in json.loads(rv.data))

    @patch("libnetwork.driver_plugin.client.assign_ip", autospec=True)
    def test_request_address_ip_supplied(self, m_assign):
        """
        Test request_address when address is supplied.
        """
        ip = IPAddress("1.2.3.4")
        request_data = {"PoolID": "CalicoPoolIPv4", "Address": str(ip)}
        rv = self.app.post('/IpamDriver.RequestAddress',
                           data=json.dumps(request_data))
        response_data = {"Address": str(IPNetwork(ip)), "Data": {}}
        self.assertDictEqual(json.loads(rv.data), response_data)

    @patch("libnetwork.driver_plugin.client.assign_ip", autospec=True)
    def test_request_address_ip_supplied_in_use(self, m_assign):
        """
        Test request_address when the supplied address is in use.
        """
        ip = IPAddress("1.2.3.4")
        request_data = {"PoolID": "CalicoPoolIPv4", "Address": str(ip)}
        m_assign.side_effect = AlreadyAssignedError()
        rv = self.app.post('/IpamDriver.RequestAddress',
                           data=json.dumps(request_data))
        self.assertTrue("Err" in json.loads(rv.data))

    @patch("libnetwork.driver_plugin.client.assign_ip", autospec=True)
    def test_request_address_ip_supplied_no_pool(self, m_assign):
        """
        Test request_address when the supplied address is not in a pool.
        """
        ip = IPAddress("1.2.3.4")
        request_data = {"PoolID": "CalicoPoolIPv4", "Address": str(ip)}
        m_assign.side_effect = PoolNotFound(ip)
        rv = self.app.post('/IpamDriver.RequestAddress',
                           data=json.dumps(request_data))
        self.assertTrue("Err" in json.loads(rv.data))

    @patch("libnetwork.driver_plugin.client.release_ips", autospec=True)
    def test_release_address(self, m_release):
        """
        Test request_address when address is supplied.
        """
        ip = IPAddress("1.2.3.4")
        request_data = {"Address": str(ip)}
        rv = self.app.post('/IpamDriver.ReleaseAddress',
                           data=json.dumps(request_data))
        self.assertDictEqual(json.loads(rv.data), {})
        m_release.assert_called_once_with({ip})

    def test_capabilities(self):
        rv = self.app.post('/NetworkDriver.GetCapabilities')
        capabilities_response = {"Scope": "global"}
        self.assertDictEqual(json.loads(rv.data), capabilities_response)

    @patch("libnetwork.driver_plugin.client.create_profile", autospec=True)
    @patch("libnetwork.driver_plugin.client.write_network", autospec=True)
    @patch("libnetwork.driver_plugin.client.add_ip_pool", autospec=True)
    def test_create_network(self, m_add_ip_pool, m_write_network, m_create):
        """
        Test create_network
        """
        request_data = {
            "NetworkID": TEST_NETWORK_ID,
            "IPv4Data": [{
                "Gateway": "10.0.0.0/8",
                "Pool": "6.5.4.3/21"
            }],
            "IPv6Data": [],
            "Options": {
                "com.docker.network.generic": {}
            }
        }
        rv = self.app.post('/NetworkDriver.CreateNetwork',
                           data=json.dumps(request_data))
        m_create.assert_called_once_with(TEST_NETWORK_ID)
        m_add_ip_pool.assert_called_once_with(4,
                                              IPPool("6.5.4.3/21", ipam=False))
        m_write_network.assert_called_once_with(TEST_NETWORK_ID, request_data)

        self.assertDictEqual(json.loads(rv.data), {})

    @patch("libnetwork.driver_plugin.client.remove_network", autospec=True)
    @patch("libnetwork.driver_plugin.client.remove_ip_pool", autospec=True)
    @patch("libnetwork.driver_plugin.client.get_network", autospec=True)
    @patch("libnetwork.driver_plugin.client.remove_profile", autospec=True)
    def test_delete_network_default_ipam(self, m_remove_profile, m_get_network,
                                         m_remove_pool, m_remove_network):
        """
        Test the delete_network behavior for default IPAM.
        """
        m_get_network.return_value = {
            "NetworkID": TEST_NETWORK_ID,
            "IPv4Data": [{
                "Gateway": "6.5.4.3/21",
                "Pool": "6.5.4.3/21"
            }],
            "IPv6Data": [{
                "Gateway": "aa::ff/10",
                "Pool": "aa::fe/10"
            }]
        }

        request_data = {"NetworkID": TEST_NETWORK_ID}

        rv = self.app.post('/NetworkDriver.DeleteNetwork',
                           data=json.dumps(request_data))
        m_remove_profile.assert_called_once_with(TEST_NETWORK_ID)
        m_remove_network.assert_called_once_with(TEST_NETWORK_ID)
        m_remove_pool.assert_has_calls([
            call(4, IPNetwork("6.5.4.3/21")),
            call(6, IPNetwork("aa::fe/10"))
        ])
        self.assertDictEqual(json.loads(rv.data), {})

    @patch("libnetwork.driver_plugin.client.remove_network", autospec=True)
    @patch("libnetwork.driver_plugin.client.remove_ip_pool", autospec=True)
    @patch("libnetwork.driver_plugin.client.get_network",
           autospec=True,
           return_value=None)
    @patch("libnetwork.driver_plugin.client.remove_profile", autospec=True)
    def test_delete_network_calico_ipam(self, m_remove_profile, m_get_network,
                                        m_remove_pool, m_remove_network):
        """
        Test the delete_network behavior for Calico IPAM.
        """
        m_get_network.return_value = {
            "NetworkID": TEST_NETWORK_ID,
            "IPv4Data": [{
                "Gateway": "0.0.0.0/0",
                "Pool": "0.0.0.0/0"
            }],
            "IPv6Data": [{
                "Gateway": "00::00/0",
                "Pool": "00::00/0"
            }]
        }

        request_data = {"NetworkID": TEST_NETWORK_ID}

        rv = self.app.post('/NetworkDriver.DeleteNetwork',
                           data=json.dumps(request_data))
        m_remove_profile.assert_called_once_with(TEST_NETWORK_ID)
        m_remove_network.assert_called_once_with(TEST_NETWORK_ID)
        self.assertEquals(m_remove_pool.call_count, 0)
        self.assertDictEqual(json.loads(rv.data), {})

    @patch("libnetwork.driver_plugin.client.remove_profile", autospec=True)
    def test_delete_network_no_profile(self, m_remove):
        """
        Test the delete_network hook correctly removes the etcd data and
        returns the correct response.
        """
        m_remove.side_effect = KeyError
        request_data = {"NetworkID": TEST_NETWORK_ID}
        rv = self.app.post('/NetworkDriver.DeleteNetwork',
                           data=json.dumps(request_data))
        m_remove.assert_called_once_with(TEST_NETWORK_ID)
        self.assertDictEqual(json.loads(rv.data), {u'Err': u''})

    def test_oper_info(self):
        """
        Test oper_info returns the correct data.
        """
        request_data = {"EndpointID": TEST_ENDPOINT_ID}
        rv = self.app.post('/NetworkDriver.EndpointOperInfo',
                           data=json.dumps(request_data))
        self.assertDictEqual(json.loads(rv.data), {"Value": {}})

    @patch("libnetwork.driver_plugin.client.get_network", autospec=True)
    @patch("pycalico.netns.set_veth_mac", autospec=True)
    @patch("pycalico.netns.create_veth", autospec=True)
    def test_join_default_ipam(self, m_create_veth, m_set_mac, m_get_network):
        """
        Test the join() processing with default IPAM.
        """
        request_data = {
            "EndpointID": TEST_ENDPOINT_ID,
            "NetworkID": TEST_NETWORK_ID
        }

        m_get_network.return_value = {
            "NetworkID": TEST_NETWORK_ID,
            "IPv4Data": [{
                "Gateway": "6.5.4.3/21",
                "Pool": "6.5.4.3/21"
            }],
            "IPv6Data": []
        }

        # Actually make the request to the plugin.
        rv = self.app.post('/NetworkDriver.Join',
                           data=json.dumps(request_data))

        # Check the expected response.
        response_data = {
            "Gateway": "",
            "GatewayIPv6": "",
            "InterfaceName": {
                "DstPrefix": "cali",
                "SrcName": "tmpTEST_ENDPOI"
            }
        }
        self.maxDiff = None
        self.assertDictEqual(json.loads(rv.data), response_data)

        # Check appropriate netns calls.
        host_interface_name = generate_cali_interface_name(
            IF_PREFIX, TEST_ENDPOINT_ID)
        temp_interface_name = generate_cali_interface_name(
            "tmp", TEST_ENDPOINT_ID)

        m_create_veth.assert_called_once_with(host_interface_name,
                                              temp_interface_name)
        m_set_mac.assert_called_once_with(temp_interface_name,
                                          "EE:EE:EE:EE:EE:EE")

    @patch("libnetwork.driver_plugin.client.get_endpoint", autospec=True)
    @patch("libnetwork.driver_plugin.client.get_network",
           autospec=True,
           return_value=None)
    @patch("pycalico.netns.set_veth_mac", autospec=True)
    @patch("pycalico.netns.create_veth", autospec=True)
    def test_join_calico_ipam(self, m_create_veth, m_set_mac, m_get_network,
                              m_get_endpoint):
        """
        Test the join() processing with Calico IPAM.
        """
        m_get_network.return_value = {
            "NetworkID": TEST_NETWORK_ID,
            "IPv4Data": [{
                "Gateway": "0.0.0.0/0",
                "Pool": "0.0.0.0/0"
            }],
            "IPv6Data": [{
                "Gateway": "::/0",
                "Pool": "::/0"
            }]
        }
        m_get_endpoint.return_value = Endpoint(hostname, "libnetwork",
                                               "docker", TEST_ENDPOINT_ID,
                                               None, None)
        m_get_endpoint.return_value.ipv4_gateway = IPAddress("1.2.3.4")
        m_get_endpoint.return_value.ipv6_gateway = IPAddress("aa::ff")

        # Actually make the request to the plugin.
        rv = self.app.post('/NetworkDriver.Join',
                           data='{"EndpointID": "%s", "NetworkID": "%s"}' %
                           (TEST_ENDPOINT_ID, TEST_NETWORK_ID))

        host_interface_name = generate_cali_interface_name(
            IF_PREFIX, TEST_ENDPOINT_ID)
        temp_interface_name = generate_cali_interface_name(
            "tmp", TEST_ENDPOINT_ID)

        m_create_veth.assert_called_once_with(host_interface_name,
                                              temp_interface_name)
        m_set_mac.assert_called_once_with(temp_interface_name,
                                          "EE:EE:EE:EE:EE:EE")

        expected_data = {
            "Gateway":
            str(m_get_endpoint.return_value.ipv4_gateway),
            "GatewayIPv6":
            str(m_get_endpoint.return_value.ipv6_gateway),
            "InterfaceName": {
                "DstPrefix": "cali",
                "SrcName": "tmpTEST_ENDPOI"
            },
            "StaticRoutes": [{
                "Destination":
                str(m_get_endpoint.return_value.ipv4_gateway) + "/32",
                "RouteType":
                1,
                "NextHop":
                ""
            }, {
                "Destination":
                str(m_get_endpoint.return_value.ipv6_gateway) + "/128",
                "RouteType":
                1,
                "NextHop":
                ""
            }]
        }
        self.maxDiff = None
        self.assertDictEqual(json.loads(rv.data), expected_data)

    @patch("libnetwork.driver_plugin.client.get_default_next_hops",
           autospec=True)
    @patch("pycalico.netns.set_veth_mac", autospec=True)
    @patch("pycalico.netns.create_veth", autospec=True)
    @patch("libnetwork.driver_plugin.remove_veth", autospec=True)
    def test_join_veth_fail(self, m_del_veth, m_create_veth, m_set_veth_mac,
                            m_next_hops):
        """
        Test the join() processing when create_veth fails.
        """
        m_create_veth.side_effect = CalledProcessError(2, "testcmd")

        m_next_hops.return_value = {4: IPAddress("1.2.3.4"), 6: None}

        # Actually make the request to the plugin.
        rv = self.app.post('/NetworkDriver.Join',
                           data='{"EndpointID": "%s", "NetworkID": "%s"}' %
                           (TEST_ENDPOINT_ID, TEST_NETWORK_ID))

        # Expect a 500 response.
        self.assertDictEqual(
            json.loads(rv.data),
            {u'Err': u"Command 'testcmd' returned non-zero exit status 2"})

        # Check that create veth is called with the expected endpoint, and
        # that set_endpoint is not (since create_veth is raising an exception).
        host_interface_name = generate_cali_interface_name(
            IF_PREFIX, TEST_ENDPOINT_ID)
        temp_interface_name = generate_cali_interface_name(
            "tmp", TEST_ENDPOINT_ID)

        m_create_veth.assert_called_once_with(host_interface_name,
                                              temp_interface_name)

        # Check that we delete the veth.
        m_del_veth.assert_called_once_with(host_interface_name)

    @patch("libnetwork.driver_plugin.remove_veth", autospec=True)
    def test_leave(self, m_veth):
        """
        Test leave() processing removes the veth.
        """
        # Send the leave request.
        rv = self.app.post('/NetworkDriver.Leave',
                           data='{"EndpointID": "%s"}' % TEST_ENDPOINT_ID)
        self.assertDictEqual(json.loads(rv.data), {})

        m_veth.assert_called_once_with(
            generate_cali_interface_name(IF_PREFIX, TEST_ENDPOINT_ID))

    @patch("libnetwork.driver_plugin.client.remove_endpoint", autospec=True)
    def test_delete_endpoint(self, m_remove):
        """
        Test delete_endpoint() deletes the endpoint and backout IP assignment.
        """
        rv = self.app.post('/NetworkDriver.DeleteEndpoint',
                           data='{"EndpointID": "%s"}' % TEST_ENDPOINT_ID)
        m_remove.assert_called_once_with(
            Endpoint(hostname, "libnetwork", "docker", TEST_ENDPOINT_ID, None,
                     None))
        self.assertDictEqual(json.loads(rv.data), {})

    @patch("libnetwork.driver_plugin.client.remove_endpoint",
           autospec=True,
           side_effect=KeyError())
    def test_delete_endpoint_fail(self, m_remove):
        """
        Test delete_endpoint() deletes the endpoint and backout IP assignment.
        """
        rv = self.app.post('/NetworkDriver.DeleteEndpoint',
                           data='{"EndpointID": "%s"}' % TEST_ENDPOINT_ID)
        m_remove.assert_called_once_with(
            Endpoint(hostname, "libnetwork", "docker", TEST_ENDPOINT_ID, None,
                     None))
        self.assertDictEqual(json.loads(rv.data), {u'Err': u''})

    @patch("libnetwork.driver_plugin.client.get_network",
           autospec=True,
           return_value=None)
    def test_create_endpoint_missing_network(self, _):
        """
        Test the create_endpoint hook behavior when missing network data.
        """
        rv = self.app.post('/NetworkDriver.CreateEndpoint',
                           data="""
                           {"EndpointID": "%s",
                            "NetworkID":  "%s",
                            "Interface": {"MacAddress": "EE:EE:EE:EE:EE:EE",
                                          "Address": "1.2.3.4/32"
                            }}""" % (TEST_ENDPOINT_ID, TEST_NETWORK_ID))
        expected_data = {u'Err': u"Network TEST_NETWORK_ID does not exist"}
        self.assertDictEqual(json.loads(rv.data), expected_data)

    @patch("libnetwork.driver_plugin.client.get_default_next_hops",
           autospec=True)
    @patch("libnetwork.driver_plugin.client.get_network", autospec=True)
    @patch("libnetwork.driver_plugin.client.set_endpoint", autospec=True)
    def test_create_endpoint(self, m_set, m_get_network, m_get_next_hops):
        """
        Test the create_endpoint hook correctly writes the appropriate data
        to etcd based on IP assignment and pool selection.
        """

        # Iterate using various different mixtures of IP assignments and
        # gateway CIDRs.
        #
        # (IPv4 addr, IPv6 addr, IPv4 gway, IPv6 gway, calico_ipam)
        #
        # calico_ipam indicates whether the gateway indicates Calico IPAM or
        # not which changes the gateway selected in the endpoint.
        parms = [(None, "aa:bb::bb", None, "cc:dd::00/23", False),
                 ("10.20.30.40", None, "1.2.3.4/32", "aa:bb:cc::/24", False),
                 ("20.20.30.40", "ab:bb::bb", "1.2.3.4/32", "aa:bb:cc::/25",
                  False), (None, "ac:bb::bb", None, "00::00/0", True),
                 ("40.20.30.40", None, "0.0.0.0/0", "::/0", True),
                 ("50.20.30.40", "ad:bb::bb", "0.0.0.0/0", "00::/0", True)]

        next_hop_4 = IPAddress("11.22.33.44")
        next_hop_6 = IPAddress("a0:b0::f0")
        m_get_next_hops.return_value = {4: next_hop_4, 6: next_hop_6}

        # Loop through different combinations of IP availability.
        for ipv4, ipv6, gwv4, gwv6, calico_ipam in parms:
            m_get_network.return_value = {
                "NetworkID": TEST_NETWORK_ID,
                "IPv4Data": [{
                    "Gateway": gwv4,
                    "Pool": gwv4
                }],
                "IPv6Data": [{
                    "Gateway": gwv6,
                    "Pool": gwv6
                }]
            }
            ipv4_json = ',"Address": "%s"' % ipv4 if ipv4 else ""
            ipv6_json = ',"AddressIPv6": "%s"' % ipv6 if ipv6 else ""

            # Invoke create endpoint.
            rv = self.app.post(
                '/NetworkDriver.CreateEndpoint',
                data='{"EndpointID": "%s",'
                '"NetworkID":  "%s",'
                '"Interface": {"MacAddress": "EE:EE:EE:EE:EE:EE"%s%s}}' %
                (TEST_ENDPOINT_ID, TEST_NETWORK_ID, ipv4_json, ipv6_json))

            # Assert return value
            self.assertDictEqual(json.loads(
                rv.data), {"Interface": {
                    "MacAddress": "EE:EE:EE:EE:EE:EE"
                }})

            # Assert expected data is written to etcd
            ep = Endpoint(hostname, "libnetwork", "libnetwork",
                          TEST_ENDPOINT_ID, "active", "EE:EE:EE:EE:EE:EE")

            ep.profile_ids.append(TEST_NETWORK_ID)

            if ipv4:
                ep.ipv4_nets.add(IPNetwork(ipv4))
                if calico_ipam:
                    ep.ipv4_gateway = next_hop_4
                else:
                    ep.ipv4_gateway = IPNetwork(gwv4).ip

            if ipv6:
                ep.ipv6_nets.add(IPNetwork(ipv6))
                if calico_ipam:
                    ep.ipv6_gateway = next_hop_6
                else:
                    ep.ipv6_gateway = IPNetwork(gwv6).ip

            m_set.assert_called_once_with(ep)

            # Reset the Mocks before continuing.
            m_set.reset_mock()

    def test_discover_new(self):
        """
        Test discover_new returns the correct data.
        """
        rv = self.app.post('/NetworkDriver.DiscoverNew',
                           data='{"DiscoveryType": 1,'
                           '"DiscoveryData": {'
                           '"Address": "thisaddress",'
                           '"self": true'
                           '}'
                           '}')
        self.assertDictEqual(json.loads(rv.data), {})

    def test_discover_delete(self):
        """
        Test discover_delete returns the correct data.
        """
        rv = self.app.post('/NetworkDriver.DiscoverDelete',
                           data='{"DiscoveryType": 1,'
                           '"DiscoveryData": {'
                           '"Address": "thisaddress",'
                           '"self": true'
                           '}'
                           '}')
        self.assertDictEqual(json.loads(rv.data), {})

    @patch("pycalico.netns.remove_veth",
           autospec=True,
           side_effect=CalledProcessError(2, "test"))
    def test_remove_veth_fail(self, m_remove):
        """
        Test remove_veth calls through to netns to remove the veth.
        Fail with a CalledProcessError to write the log.
        """
        name = generate_cali_interface_name(IF_PREFIX, TEST_ENDPOINT_ID)

        driver_plugin.remove_veth(name)
        m_remove.assert_called_once_with(name)

    def test_get_gateway_pool_from_network_data(self):
        """
        Test get_gateway_pool_from_network_data for a variety of inputs.
        """
        tests = [((None, None), 4, {
            "IPv6Data": []
        }), ((None, None), 6, {
            "IPv6Data": []
        }), ((None, None), 6, {
            "IPv6Data": [{}]
        }), ((None, None), 4, {
            "IPv4Data": [{
                "Gateway": "1.2.3.4/40"
            }]
        }), ((None, None), 4, {
            "IPv4Data": [{
                "Pool": "1.2.3.4/40"
            }]
        }),
                 ((IPNetwork("aa::ff/120"), IPNetwork("aa::dd/121")), 6, {
                     "IPv6Data": [{
                         "Gateway": "aa::ff/120",
                         "Pool": "aa::dd/121"
                     }]
                 })]

        for result, version, network_data in tests:
            self.assertEquals(
                driver_plugin.get_gateway_pool_from_network_data(
                    network_data, version), result)

    def test_get_gateway_pool_from_network_data_multiple_datas(self):
        """
        Test get_gateway_pool_from_network_data when multiple data blocks are
        supplied.
        """
        network_data = {
            "IPv6Data": [{
                "Gateway": "aa::ff/120",
                "Pool": "aa::dd/121"
            }, {
                "Gateway": "aa::fa/120",
                "Pool": "aa::da/121"
            }]
        }
        self.assertRaises(Exception,
                          driver_plugin.get_gateway_pool_from_network_data,
                          network_data, 6)

    def test_is_using_calico_ipam(self):
        """
        Test is_using_calico_ipam using a variety of CIDRs.
        """
        for cidr, is_cipam in [(IPNetwork("1.2.3.4/20"), False),
                               (IPNetwork("0.0.0.0/20"), False),
                               (IPNetwork("::/128"), False),
                               (IPNetwork("0.0.0.0/32"), False),
                               (IPNetwork("0.0.0.0/0"), True),
                               (IPNetwork("::/0"), True)]:
            self.assertEquals(driver_plugin.is_using_calico_ipam(cidr),
                              is_cipam)
class TestPlugin(unittest.TestCase):
    def setUp(self):
        self.app = docker_plugin.app.test_client()

    def tearDown(self):
        pass

    def test_404(self):
        rv = self.app.post('/')
        assert_equal(rv.status_code, 404)

    def test_activate(self):
        rv = self.app.post('/Plugin.Activate')
        activate_response = {"Implements": ["NetworkDriver"]}
        self.assertDictEqual(json.loads(rv.data), activate_response)

    @patch("libnetwork_plugin.docker_plugin.client.profile_exists",
           autospec=True,
           return_value=False)
    @patch("libnetwork_plugin.docker_plugin.client.create_profile",
           autospec=True)
    def test_create_network(self, m_create, m_exists):
        """
        Test create_network when the profile does not exist.
        """
        rv = self.app.post('/NetworkDriver.CreateNetwork',
                           data='{"NetworkID": "%s"}' % TEST_NETWORK_ID)
        m_exists.assert_called_once_with(TEST_NETWORK_ID)
        m_create.assert_called_once_with(TEST_NETWORK_ID)
        self.assertDictEqual(json.loads(rv.data), {})

    @patch("libnetwork_plugin.docker_plugin.client.profile_exists",
           autospec=True,
           return_value=True)
    @patch("libnetwork_plugin.docker_plugin.client.create_profile",
           autospec=True)
    def test_create_network_exists(self, m_create, m_exists):
        """
        Test create_network when the profile already exists.
        """
        rv = self.app.post('/NetworkDriver.CreateNetwork',
                           data='{"NetworkID": "%s"}' % TEST_NETWORK_ID)
        m_exists.assert_called_once_with(TEST_NETWORK_ID)
        assert_equal(m_create.call_count, 0)
        self.assertDictEqual(json.loads(rv.data), {})

    @patch("libnetwork_plugin.docker_plugin.client.remove_profile",
           autospec=True)
    def test_delete_network(self, m_remove):
        """
        Test the delete_network hook correctly removes the etcd data and
        returns the correct response.
        """
        rv = self.app.post('/NetworkDriver.DeleteNetwork',
                           data='{"NetworkID": "%s"}' % TEST_NETWORK_ID)
        m_remove.assert_called_once_with(TEST_NETWORK_ID)
        self.assertDictEqual(json.loads(rv.data), {})

    @patch("libnetwork_plugin.docker_plugin.client.remove_profile",
           autospec=True)
    def test_delete_network_no_profile(self, m_remove):
        """
        Test the delete_network hook correctly removes the etcd data and
        returns the correct response.
        """
        m_remove.side_effect = KeyError
        rv = self.app.post('/NetworkDriver.DeleteNetwork',
                           data='{"NetworkID": "%s"}' % TEST_NETWORK_ID)
        m_remove.assert_called_once_with(TEST_NETWORK_ID)
        self.assertDictEqual(json.loads(rv.data), {})

    def test_oper_info(self):
        """
        Test oper_info returns the correct data.
        """
        rv = self.app.post('/NetworkDriver.EndpointOperInfo',
                           data='{"EndpointID": "%s"}' % TEST_ENDPOINT_ID)
        self.assertDictEqual(json.loads(rv.data), {"Value": {}})

    @patch("libnetwork_plugin.docker_plugin.client.get_default_next_hops",
           autospec=True)
    @patch("libnetwork_plugin.docker_plugin.client.read_cnm_endpoint",
           autospec=True)
    @patch("libnetwork_plugin.docker_plugin.create_veth", autospec=True)
    @patch("libnetwork_plugin.docker_plugin.client.set_endpoint",
           autospec=True)
    def test_join(self, m_set, m_veth, m_read, m_next_hops):
        """
        Test the join() processing correctly creates the veth and the Endpoint.
        """
        endpoint_json = {
            "Interfaces": [{
                "Address": "1.2.3.4",
                "AddressIPv6": "FE80::0202:B3FF:FE1E:8329",
                "ID": 0,
                "MacAddress": "EE:EE:EE:EE:EE:EE"
            }]
        }
        m_read.return_value = endpoint_json

        m_next_hops.return_value = {
            4: IPAddress("1.2.3.4"),
            6: IPAddress("fe80::202:b3ff:fe1e:8329")
        }

        # Actually make the request to the plugin.
        rv = self.app.post('/NetworkDriver.Join',
                           data='{"EndpointID": "%s", "NetworkID": "%s"}' %
                           (TEST_ENDPOINT_ID, TEST_NETWORK_ID))
        m_read.assert_called_once_with(TEST_ENDPOINT_ID)

        # Check that the create_veth and set_endpoint are called with this
        # endpoint.
        endpoint = Endpoint("hostname", "docker", "libnetwork",
                            TEST_ENDPOINT_ID, "active", "EE:EE:EE:EE:EE:EE")
        endpoint.ipv4_gateway = IPAddress("1.2.3.4")
        endpoint.ipv6_gateway = IPAddress("FE80::0202:B3FF:FE1E:8329")
        endpoint.ipv4_nets.add(IPNetwork("1.2.3.4/32"))
        endpoint.ipv6_nets.add(IPNetwork("FE80::0202:B3FF:FE1E:8329/128"))
        endpoint.profile_ids.append(TEST_NETWORK_ID)

        m_veth.assert_called_once_with(endpoint)
        m_set.assert_called_once_with(endpoint)

        expected_response = """{
  "Gateway": "1.2.3.4",
  "GatewayIPv6": "fe80::202:b3ff:fe1e:8329",
  "InterfaceNames": [
    {
      "DstPrefix": "cali",
      "SrcName": "tmpTEST_ENDPOI"
    }
  ],
  "StaticRoutes": [
    {
      "Destination": "1.2.3.4/32",
      "InterfaceID": 0,
      "NextHop": "",
      "RouteType": 1
    },
    {
      "Destination": "fe80::202:b3ff:fe1e:8329/128",
      "InterfaceID": 0,
      "NextHop": "",
      "RouteType": 1
    }
  ]
}"""
        self.maxDiff = None
        self.assertDictEqual(json.loads(rv.data),
                             json.loads(expected_response))

    @patch("libnetwork_plugin.docker_plugin.client.get_default_next_hops",
           autospec=True)
    @patch("libnetwork_plugin.docker_plugin.client.read_cnm_endpoint",
           autospec=True)
    @patch("libnetwork_plugin.docker_plugin.create_veth", autospec=True)
    @patch("libnetwork_plugin.docker_plugin.remove_veth", autospec=True)
    @patch("libnetwork_plugin.docker_plugin.client.set_endpoint",
           autospec=True)
    def test_join_veth_fail(self, m_set, m_del_veth, m_veth, m_read,
                            m_next_hops):
        """
        Test the join() processing when create_veth fails.
        """
        m_veth.side_effect = CalledProcessError(2, "testcmd")

        endpoint_json = {
            "Interfaces": [{
                "Address": "1.2.3.4",
                "ID": 0,
                "MacAddress": "EE:EE:EE:EE:EE:EE"
            }]
        }
        m_read.return_value = endpoint_json

        m_next_hops.return_value = {4: IPAddress("1.2.3.4"), 6: None}

        # Actually make the request to the plugin.
        rv = self.app.post('/NetworkDriver.Join',
                           data='{"EndpointID": "%s", "NetworkID": "%s"}' %
                           (TEST_ENDPOINT_ID, TEST_NETWORK_ID))
        m_read.assert_called_once_with(TEST_ENDPOINT_ID)

        # Check that the create_veth is called with this
        # endpoint.
        endpoint = Endpoint("hostname", "docker", "libnetwork",
                            TEST_ENDPOINT_ID, "active", "EE:EE:EE:EE:EE:EE")
        endpoint.ipv4_gateway = IPAddress("1.2.3.4")
        endpoint.ipv4_nets.add(IPNetwork("1.2.3.4/32"))
        endpoint.profile_ids.append(TEST_NETWORK_ID)

        # Check that create veth is called with the expected endpoint, and
        # that set_endpoint is not (since create_veth is raising an exception).
        m_veth.assert_called_once_with(endpoint)
        assert_equal(m_set.call_count, 0)

        # Check that we delete the veth.
        m_del_veth.assert_called_once_with(endpoint)

        # Expect a 500 response.
        self.assertDictEqual(json.loads(rv.data), ERROR_RESPONSE_500)

    @patch("libnetwork_plugin.docker_plugin.client.get_default_next_hops",
           autospec=True)
    @patch("libnetwork_plugin.docker_plugin.client.read_cnm_endpoint",
           autospec=True)
    @patch("libnetwork_plugin.docker_plugin.create_veth", autospec=True)
    @patch("libnetwork_plugin.docker_plugin.remove_veth", autospec=True)
    @patch("libnetwork_plugin.docker_plugin.client.set_endpoint",
           autospec=True,
           side_effect=DataStoreError)
    def test_join_set_fail(self, m_set, m_del_veth, m_veth, m_read,
                           m_next_hops):
        """
        Test the join() processing when set_endpoint fails.
        """
        endpoint_json = {
            "Interfaces": [{
                "Address": "1.2.3.4",
                "ID": 0,
                "MacAddress": "EE:EE:EE:EE:EE:EE"
            }]
        }
        m_read.return_value = endpoint_json

        m_next_hops.return_value = {4: IPAddress("1.2.3.4"), 6: None}

        # Actually make the request to the plugin.
        rv = self.app.post('/NetworkDriver.Join',
                           data='{"EndpointID": "%s", "NetworkID": "%s"}' %
                           (TEST_ENDPOINT_ID, TEST_NETWORK_ID))
        m_read.assert_called_once_with(TEST_ENDPOINT_ID)

        # Check that the create_veth is called with this
        # endpoint.
        endpoint = Endpoint("hostname", "docker", "libnetwork",
                            TEST_ENDPOINT_ID, "active", "EE:EE:EE:EE:EE:EE")
        endpoint.ipv4_gateway = IPAddress("1.2.3.4")
        endpoint.ipv4_nets.add(IPNetwork("1.2.3.4/32"))
        endpoint.profile_ids.append(TEST_NETWORK_ID)

        # Check that create veth and set_endpoint are called with the
        # endpoint.  The set throws a DataStoreError and so we clean up the
        # veth.
        m_veth.assert_called_once_with(endpoint)
        m_set.assert_called_once_with(endpoint)

        # Check that we delete the veth.
        m_del_veth.assert_called_once_with(endpoint)

        # Expect a 500 response.
        self.assertDictEqual(json.loads(rv.data), ERROR_RESPONSE_500)

    @patch("libnetwork_plugin.docker_plugin.remove_veth", autospec=True)
    @patch("libnetwork_plugin.docker_plugin.client.get_endpoint",
           autospec=True)
    @patch("libnetwork_plugin.docker_plugin.client.remove_endpoint",
           autospec=True)
    def test_leave(self, m_remove, m_get, m_veth):
        """
        Test leave() processing removes the endpoint and veth.
        """
        endpoint = Endpoint("hostname", "docker", "libnetwork",
                            TEST_ENDPOINT_ID, "active", "EE:EE:EE:EE:EE:EE")
        m_get.return_value = endpoint

        # Send the leave request.
        rv = self.app.post('/NetworkDriver.Leave',
                           data='{"EndpointID": "%s"}' % TEST_ENDPOINT_ID)
        self.assertDictEqual(json.loads(rv.data), {})

        # Check parameters
        m_get.assert_called_once_with(hostname=ANY,
                                      orchestrator_id="docker",
                                      workload_id="libnetwork",
                                      endpoint_id=TEST_ENDPOINT_ID)
        m_remove.assert_called_once_with(endpoint)
        m_veth.assert_called_once_with(endpoint)

    @patch("libnetwork_plugin.docker_plugin.remove_veth", autospec=True)
    @patch("libnetwork_plugin.docker_plugin.client.get_endpoint",
           autospec=True)
    @patch("libnetwork_plugin.docker_plugin.client.remove_endpoint",
           autospec=True)
    def test_leave_no_endpoint(self, m_remove, m_get, m_veth):
        """
        Test the leave processing when these is no endpoint.
        """
        m_get.side_effect = KeyError

        # Send the leave request.
        rv = self.app.post('/NetworkDriver.Leave',
                           data='{"EndpointID": "%s"}' % TEST_ENDPOINT_ID)
        self.assertDictEqual(json.loads(rv.data), ERROR_RESPONSE_500)

        # Check parameters
        m_get.assert_called_once_with(hostname=ANY,
                                      orchestrator_id="docker",
                                      workload_id="libnetwork",
                                      endpoint_id=TEST_ENDPOINT_ID)
        assert_equal(m_remove.call_count, 0)
        assert_equal(m_veth.call_count, 0)

    @patch("libnetwork_plugin.docker_plugin.remove_veth", autospec=True)
    @patch("libnetwork_plugin.docker_plugin.client.get_endpoint",
           autospec=True)
    @patch("libnetwork_plugin.docker_plugin.client.remove_endpoint",
           autospec=True)
    def test_leave_delete_failed(self, m_remove, m_get, m_veth):
        """
        Test the leave processing when these is no endpoint.
        """
        endpoint = Endpoint("hostname", "docker", "libnetwork",
                            TEST_ENDPOINT_ID, "active", "EE:EE:EE:EE:EE:EE")
        m_get.return_value = endpoint
        m_remove.side_effect = DataStoreError

        # Send the leave request.
        rv = self.app.post('/NetworkDriver.Leave',
                           data='{"EndpointID": "%s"}' % TEST_ENDPOINT_ID)
        self.assertDictEqual(json.loads(rv.data), {})

        # Check parameters
        m_get.assert_called_once_with(hostname=ANY,
                                      orchestrator_id="docker",
                                      workload_id="libnetwork",
                                      endpoint_id=TEST_ENDPOINT_ID)
        m_remove.assert_called_once_with(endpoint)
        m_veth.assert_called_once_with(endpoint)

    @patch("libnetwork_plugin.docker_plugin.backout_ip_assignments",
           autospec=True)
    @patch("libnetwork_plugin.docker_plugin.client.read_cnm_endpoint",
           autospec=True)
    @patch("libnetwork_plugin.docker_plugin.client.delete_cnm_endpoint",
           autospec=True)
    def test_delete_endpoint(self, m_delete, m_read, m_backout):
        """
        Test delete_endpoint() deletes the endpoint and backout IP assignment.
        """
        ep = {"test": 1}
        m_read.return_value = ep
        m_delete.return_value = True
        rv = self.app.post('/NetworkDriver.DeleteEndpoint',
                           data='{"EndpointID": "%s"}' % TEST_ENDPOINT_ID)
        m_read.assert_called_once_with(TEST_ENDPOINT_ID)
        m_delete.assert_called_once_with(TEST_ENDPOINT_ID)
        m_backout.assert_called_once_with(ep)
        self.assertDictEqual(json.loads(rv.data), {})

    @patch("libnetwork_plugin.docker_plugin.backout_ip_assignments",
           autospec=True)
    @patch("libnetwork_plugin.docker_plugin.client.read_cnm_endpoint",
           autospec=True)
    @patch("libnetwork_plugin.docker_plugin.client.delete_cnm_endpoint",
           autospec=True)
    def test_delete_endpoint_does_not_exist(self, m_delete, m_read, m_backout):
        """
        Test delete_endpoint() when the endpoint does not exist.
        """
        m_read.return_value = None
        rv = self.app.post('/NetworkDriver.DeleteEndpoint',
                           data='{"EndpointID": "%s"}' % TEST_ENDPOINT_ID)
        m_read.assert_called_once_with(TEST_ENDPOINT_ID)
        assert_equal(m_delete.call_count, 0)
        assert_equal(m_backout.call_count, 0)
        self.assertDictEqual(json.loads(rv.data), {})

    @patch("libnetwork_plugin.docker_plugin.backout_ip_assignments",
           autospec=True)
    @patch("libnetwork_plugin.docker_plugin.client.read_cnm_endpoint",
           autospec=True)
    @patch("libnetwork_plugin.docker_plugin.client.delete_cnm_endpoint",
           autospec=True)
    def test_delete_endpoint_just_deleted(self, m_delete, m_read, m_backout):
        """
        Test delete_endpoint() when the endpoint is deleted just before we
        were about to.
        """
        ep = {"test": 1}
        m_read.return_value = ep
        m_delete.return_value = False
        rv = self.app.post('/NetworkDriver.DeleteEndpoint',
                           data='{"EndpointID": "%s"}' % TEST_ENDPOINT_ID)
        m_read.assert_called_once_with(TEST_ENDPOINT_ID)
        m_delete.assert_called_once_with(TEST_ENDPOINT_ID)
        assert_equal(m_backout.call_count, 0)
        self.assertDictEqual(json.loads(rv.data), {})

    @patch("libnetwork_plugin.docker_plugin.client.cnm_endpoint_exists",
           autospec=True,
           return_value=False)
    @patch("libnetwork_plugin.docker_plugin.assign_ip", autospec=True)
    @patch("libnetwork_plugin.docker_plugin.client.write_cnm_endpoint",
           autospec=True)
    @patch("libnetwork_plugin.docker_plugin.client.get_default_next_hops",
           autospec=True)
    def test_create_endpoint(self, m_next_hops, m_write, m_assign_ip,
                             m_exists):
        """
        Test the create_endpoint hook correctly writes the appropriate data
        to etcd based on IP assignment.
        """

        # Iterate using various different mixtures of next hops and IP
        # assignments.
        #
        # (IPv4 NH, IPv4 addr, IPv6 NH, IPv6 addr)
        parms = [(IPAddress("10.20.30.40"), IPAddress("1.2.3.4"),
                  IPAddress("aa:bb::ff"), IPAddress("aa:bb::bb")),
                 (IPAddress("10.20.30.40"), None, IPAddress("aa:bb::ff"),
                  IPAddress("aa:bb::bb")),
                 (IPAddress("10.20.30.40"), IPAddress("1.2.3.4"),
                  IPAddress("aa:bb::ff"), None),
                 (IPAddress("10.20.30.40"), IPAddress("1.2.3.4"), None, None),
                 (None, None, IPAddress("aa:bb::ff"), IPAddress("aa:bb::bb"))]

        # Loop through different combinations of IP availability.
        for ipv4_nh, ipv4, ipv6_nh, ipv6 in parms:

            # Return the required next hops.
            m_next_hops.return_value = {4: ipv4_nh, 6: ipv6_nh}

            # Return the required assigned IPs.
            def assign_ip(version):
                if version == 4:
                    return ipv4
                elif version == 6:
                    return ipv6
                raise AssertionError("Unexpected version: %s" % version)

            m_assign_ip.side_effect = assign_ip

            # Invoke create endpoint.
            rv = self.app.post('/NetworkDriver.CreateEndpoint',
                               data='{"EndpointID": "%s"}' % TEST_ENDPOINT_ID)

            # Assert cnm_endpoint_exists was called.
            m_exists.assert_called_once_with(TEST_ENDPOINT_ID)

            # Construct the expected data.
            expected_data = {
                "Interfaces": [{
                    "ID": 0,
                    "MacAddress": "EE:EE:EE:EE:EE:EE"
                }]
            }
            if ipv4:
                expected_data["Interfaces"][0]["Address"] = str(ipv4)
            if ipv6:
                expected_data["Interfaces"][0]["AddressIPv6"] = str(ipv6)

            # Assert that the assign IP was called the correct number of
            # times based on whether a next hop was returned.
            expected_assign_count = 0
            if ipv4_nh:
                expected_assign_count += 1
            if ipv6_nh:
                expected_assign_count += 1
            assert_equal(m_assign_ip.call_count, expected_assign_count)

            # Assert expected data is written to etcd and returned from
            # request.
            m_write.assert_called_once_with(TEST_ENDPOINT_ID, expected_data)
            self.assertDictEqual(json.loads(rv.data), expected_data)

            # Reset the Mocks before continuing.
            m_write.reset_mock()
            m_next_hops.reset_mock()
            m_assign_ip.reset_mock()
            m_exists.reset_mock()

    @patch("libnetwork_plugin.docker_plugin.client.cnm_endpoint_exists",
           autospec=True,
           return_value=False)
    @patch("libnetwork_plugin.docker_plugin.client.write_cnm_endpoint",
           autospec=True)
    @patch("libnetwork_plugin.docker_plugin.client.get_default_next_hops",
           autospec=True)
    def test_create_endpoint_no_ip(self, m_next_hops, m_write, m_exists):
        """
        Test the create_endpoint hook writes no data and returns a 500 error
        when no IP addresses can be assigned.
        """
        m_next_hops.return_value = {4: None, 6: None}

        # Invoke create endpoint.
        rv = self.app.post('/NetworkDriver.CreateEndpoint',
                           data='{"EndpointID": "%s"}' % TEST_ENDPOINT_ID)

        # Assert cnm_endpoint_exists was called.
        m_exists.assert_called_once_with(TEST_ENDPOINT_ID)

        # Assert no data is written and returns 500 response.
        assert_equal(m_write.call_count, 0)
        self.assertDictEqual(json.loads(rv.data), ERROR_RESPONSE_500)

    @patch("libnetwork_plugin.docker_plugin.client.cnm_endpoint_exists",
           autospec=True,
           return_value=True)
    @patch("libnetwork_plugin.docker_plugin.client.write_cnm_endpoint",
           autospec=True)
    def test_create_endpoint_exists(self, m_write, m_exists):
        """
        Test the create_endpoint hook writes no data and returns a 500 error
        when no IP addresses can be assigned.
        """
        # Invoke create endpoint.
        rv = self.app.post('/NetworkDriver.CreateEndpoint',
                           data='{"EndpointID": "%s"}' % TEST_ENDPOINT_ID)

        # Assert cnm_endpoint_exists was called.
        m_exists.assert_called_once_with(TEST_ENDPOINT_ID)

        # Assert no data is written.
        assert_equal(m_write.call_count, 0)

        # Assert empty data is returned.
        self.assertDictEqual(json.loads(rv.data), {})

    @patch("libnetwork_plugin.docker_plugin.client.get_ip_pools",
           autospec=True)
    @patch("pycalico.ipam.SequentialAssignment.allocate", autospec=True)
    def test_assign_ip(self, m_allocate, m_pools):
        """
        Test assign_ip assigns an IP address.
        """
        m_pools.return_value = [
            IPNetwork("1.2.3.0/24"),
            IPNetwork("2.3.4.5/32")
        ]
        m_allocate.return_value = IPAddress("1.2.3.6")
        ip = docker_plugin.assign_ip(4)
        assert_equal(ip, IPNetwork("1.2.3.6"))
        m_pools.assert_called_once_with(4)
        m_allocate.assert_called_once_with(ANY, IPNetwork("1.2.3.0/24"))

    @patch("libnetwork_plugin.docker_plugin.client.get_ip_pools",
           autospec=True)
    @patch("pycalico.ipam.SequentialAssignment.allocate", autospec=True)
    def test_assign_ip_no_ip(self, m_allocate, m_pools):
        """
        Test assign_ip when no IP addresses can be allocated.
        """
        m_pools.return_value = [
            IPNetwork("1.2.3.0/24"),
            IPNetwork("2.3.4.5/32")
        ]
        m_allocate.return_value = None
        ip = docker_plugin.assign_ip(4)
        assert_equal(ip, None)
        m_pools.assert_called_once_with(4)

        # We should have attempted to allocate for each pool.
        m_allocate.assert_has_calls([
            call(ANY, IPNetwork("1.2.3.0/24")),
            call(ANY, IPNetwork("2.3.4.5/32"))
        ])

    @patch("libnetwork_plugin.docker_plugin.client.get_ip_pools",
           autospec=True)
    @patch("libnetwork_plugin.docker_plugin.client.unassign_address",
           autospec=True)
    def test_unassign_ip(self, m_unassign, m_pools):
        """
        Test unassign_ip unassigns an IP address.
        """
        m_pools.return_value = [
            IPNetwork("1.2.3.0/24"),
            IPNetwork("2.3.0.0/16")
        ]
        m_unassign.return_value = True
        self.assertTrue(docker_plugin.unassign_ip(IPAddress("2.3.4.5")))

        m_pools.assert_called_once_with(4)
        m_unassign.assert_called_once_with(IPNetwork("2.3.0.0/16"),
                                           IPAddress("2.3.4.5"))

    @patch("libnetwork_plugin.docker_plugin.client.get_ip_pools",
           autospec=True)
    @patch("libnetwork_plugin.docker_plugin.client.unassign_address",
           autospec=True)
    def test_unassign_ip_no_pools(self, m_unassign, m_pools):
        """
        Test unassign_ip when the IP does not fall in any configured pools.
        """
        m_pools.return_value = [
            IPNetwork("1.2.3.0/24"),
            IPNetwork("2.3.0.0/16")
        ]
        m_unassign.return_value = False
        self.assertFalse(docker_plugin.unassign_ip(IPAddress("2.30.11.11")))
        m_pools.assert_called_once_with(4)
        self.assertEquals(m_unassign.call_count, 0)

    @patch("libnetwork_plugin.docker_plugin.client.get_ip_pools",
           autospec=True)
    @patch("libnetwork_plugin.docker_plugin.client.unassign_address",
           autospec=True)
    def test_unassign_ip_not_in_pools(self, m_unassign, m_pools):
        """
        Test unassign_ip when the IP does not fall in any configured pools.
        """
        m_pools.return_value = [
            IPNetwork("1.2.3.0/24"),
            IPNetwork("2.3.0.0/16"),
            IPNetwork("1.2.0.0/16")
        ]
        m_unassign.return_value = False
        self.assertFalse(docker_plugin.unassign_ip(IPAddress("1.2.3.4")))
        m_pools.assert_called_once_with(4)
        m_unassign.assert_has_calls([
            call(IPNetwork("1.2.3.0/24"), IPAddress("1.2.3.4")),
            call(IPNetwork("1.2.0.0/16"), IPAddress("1.2.3.4"))
        ])

    @patch("libnetwork_plugin.docker_plugin.unassign_ip", autospec=True)
    def test_backout_ip_assignments(self, m_unassign):
        """
        Test backout_ip_assignment processing.
        :return:
        """
        m_unassign.return_value = True

        cnm_ep = {"Interfaces": [{"Address": "1.2.3.4"}]}
        docker_plugin.backout_ip_assignments(cnm_ep)
        m_unassign.assert_called_once_with(IPAddress("1.2.3.4"))
        m_unassign.reset_mock()

        cnm_ep = {"Interfaces": [{"AddressIPv6": "aa:bb::ff"}]}
        docker_plugin.backout_ip_assignments(cnm_ep)
        m_unassign.assert_called_once_with(IPAddress("aa:bb::ff"))
        m_unassign.reset_mock()

        cnm_ep = {
            "Interfaces": [{
                "Address": "1.2.3.4",
                "AddressIPv6": "aa:bb::ff"
            }]
        }
        docker_plugin.backout_ip_assignments(cnm_ep)
        m_unassign.assert_has_calls(
            [call(IPAddress("1.2.3.4")),
             call(IPAddress("aa:bb::ff"))])

    @patch("libnetwork_plugin.docker_plugin.unassign_ip", autospec=True)
    def test_backout_ip_assignments_failed_unassign(self, m_unassign):
        """
        Test backout_ip_assignment processing when unassignment fails.
        :return:
        """
        m_unassign.return_value = False

        cnm_ep = {"Interfaces": [{"Address": "1.2.3.4"}]}
        docker_plugin.backout_ip_assignments(cnm_ep)
        m_unassign.assert_called_once_with(IPAddress("1.2.3.4"))

    @patch("pycalico.netns.set_veth_mac", autospec=True)
    @patch("pycalico.netns.create_veth", autospec=True)
    def test_create_veth(self, m_create, m_set):
        """
        Test create_veth calls through to netns to create the veth and
        set the MAC.
        """
        endpoint = Endpoint("hostname", "docker", "libnetwork",
                            TEST_ENDPOINT_ID, "active", "EE:EE:EE:EE:EE:EE")

        docker_plugin.create_veth(endpoint)
        m_create.assert_called_once_with(endpoint.name,
                                         endpoint.temp_interface_name)
        m_set.assert_called_once_with(endpoint.temp_interface_name,
                                      endpoint.mac)

    @patch("pycalico.netns.remove_veth",
           autospec=True,
           side_effect=CalledProcessError(2, "test"))
    def test_remove_veth_fail(self, m_remove):
        """
        Test remove_veth calls through to netns to remove the veth.
        Fail with a CalledProcessError to write the log.
        """
        endpoint = Endpoint("hostname", "docker", "libnetwork",
                            TEST_ENDPOINT_ID, "active", "EE:EE:EE:EE:EE:EE")

        docker_plugin.remove_veth(endpoint)
        m_remove.assert_called_once_with(endpoint.name)
Esempio n. 9
0
class TestPlugin(unittest.TestCase):
    def setUp(self):
        self.app = driver_plugin.app.test_client()

    def tearDown(self):
        pass

    def test_404(self):
        rv = self.app.post('/')
        assert_equal(rv.status_code, 404)

    def test_activate(self):
        rv = self.app.post('/Plugin.Activate')
        activate_response = {"Implements": ["NetworkDriver"]}
        self.assertDictEqual(json.loads(rv.data), activate_response)

    def test_capabilities(self):
        rv = self.app.post('/NetworkDriver.GetCapabilities')
        capabilities_response = {"Scope": "global"}
        self.assertDictEqual(json.loads(rv.data), capabilities_response)

    @patch("libnetwork.driver_plugin.client.create_profile", autospec=True)
    @patch("libnetwork.driver_plugin.client.write_network", autospec=True)
    @patch("libnetwork.driver_plugin.client.add_ip_pool", autospec=True)
    def test_create_network(self, m_add_ip_pool, m_write_network, m_create):
        """
        Test create_network
        """
        request_json = '{"NetworkID": "%s", ' \
                       '"IPv4Data":[{"Pool": "6.5.4.3/21"}],'\
                       '"IPv6Data":[]'\
                       '}' % TEST_NETWORK_ID
        rv = self.app.post('/NetworkDriver.CreateNetwork', data=request_json)
        m_create.assert_called_once_with(TEST_NETWORK_ID)
        m_add_ip_pool.assert_called_once_with(4, IPPool("6.5.4.3/21"))
        m_write_network.assert_called_once_with(TEST_NETWORK_ID,
                                                json.loads(request_json))

        self.assertDictEqual(json.loads(rv.data), {})

    @patch("libnetwork.driver_plugin.client.remove_ip_pool", autospec=True)
    @patch("libnetwork.driver_plugin.client.get_network",
           autospec=True,
           return_value=None)
    @patch("libnetwork.driver_plugin.client.remove_profile", autospec=True)
    def test_delete_network(self, m_remove, m_get_network, m_remove_pool):
        """
        Test the delete_network hook correctly removes the etcd data and
        returns the correct response.
        """
        m_get_network.return_value = {
            "NetworkID": TEST_NETWORK_ID,
            "IPv4Data": [{
                "Pool": "6.5.4.3/21"
            }],
            "IPv6Data": []
        }

        rv = self.app.post('/NetworkDriver.DeleteNetwork',
                           data='{"NetworkID": "%s"}' % TEST_NETWORK_ID)
        m_remove.assert_called_once_with(TEST_NETWORK_ID)
        m_remove_pool.assert_called_once_with(4, IPNetwork("6.5.4.3/21"))
        self.assertDictEqual(json.loads(rv.data), {})

    @patch("libnetwork.driver_plugin.client.remove_profile", autospec=True)
    def test_delete_network_no_profile(self, m_remove):
        """
        Test the delete_network hook correctly removes the etcd data and
        returns the correct response.
        """
        m_remove.side_effect = KeyError
        rv = self.app.post('/NetworkDriver.DeleteNetwork',
                           data='{"NetworkID": "%s"}' % TEST_NETWORK_ID)
        m_remove.assert_called_once_with(TEST_NETWORK_ID)
        self.assertDictEqual(json.loads(rv.data), {u'Err': u''})

    def test_oper_info(self):
        """
        Test oper_info returns the correct data.
        """
        rv = self.app.post('/NetworkDriver.EndpointOperInfo',
                           data='{"EndpointID": "%s"}' % TEST_ENDPOINT_ID)
        self.assertDictEqual(json.loads(rv.data), {"Value": {}})

    @patch("pycalico.netns.set_veth_mac", autospec=True)
    @patch("pycalico.netns.create_veth", autospec=True)
    def test_join(self, m_create_veth, m_set_mac):
        """
        Test the join() processing correctly creates the veth.
        """
        # Actually make the request to the plugin.
        rv = self.app.post('/NetworkDriver.Join',
                           data='{"EndpointID": "%s", "NetworkID": "%s"}' %
                           (TEST_ENDPOINT_ID, TEST_NETWORK_ID))

        host_interface_name = generate_cali_interface_name(
            IF_PREFIX, TEST_ENDPOINT_ID)
        temp_interface_name = generate_cali_interface_name(
            "tmp", TEST_ENDPOINT_ID)

        m_create_veth.assert_called_once_with(host_interface_name,
                                              temp_interface_name)
        m_set_mac.assert_called_once_with(temp_interface_name,
                                          "EE:EE:EE:EE:EE:EE")

        expected_response = """
        {
          "Gateway": "",
          "InterfaceName": { "DstPrefix": "cali", "SrcName": "tmpTEST_ENDPOI" }
        }"""
        self.maxDiff = None
        self.assertDictEqual(json.loads(rv.data),
                             json.loads(expected_response))

    @patch("libnetwork.driver_plugin.client.get_default_next_hops",
           autospec=True)
    @patch("pycalico.netns.set_veth_mac", autospec=True)
    @patch("pycalico.netns.create_veth", autospec=True)
    @patch("libnetwork.driver_plugin.remove_veth", autospec=True)
    def test_join_veth_fail(self, m_del_veth, m_create_veth, m_set_veth_mac,
                            m_next_hops):
        """
        Test the join() processing when create_veth fails.
        """
        m_create_veth.side_effect = CalledProcessError(2, "testcmd")

        m_next_hops.return_value = {4: IPAddress("1.2.3.4"), 6: None}

        # Actually make the request to the plugin.
        rv = self.app.post('/NetworkDriver.Join',
                           data='{"EndpointID": "%s", "NetworkID": "%s"}' %
                           (TEST_ENDPOINT_ID, TEST_NETWORK_ID))

        # Check that create veth is called with the expected endpoint, and
        # that set_endpoint is not (since create_veth is raising an exception).
        host_interface_name = generate_cali_interface_name(
            IF_PREFIX, TEST_ENDPOINT_ID)
        temp_interface_name = generate_cali_interface_name(
            "tmp", TEST_ENDPOINT_ID)

        m_create_veth.assert_called_once_with(host_interface_name,
                                              temp_interface_name)

        # Check that we delete the veth.
        m_del_veth.assert_called_once_with(host_interface_name)

        # Expect a 500 response.
        self.assertDictEqual(
            json.loads(rv.data),
            {u'Err': u"Command 'testcmd' returned non-zero exit status 2"})

    @patch("libnetwork.driver_plugin.remove_veth", autospec=True)
    def test_leave(self, m_veth):
        """
        Test leave() processing removes the veth.
        """
        # Send the leave request.
        rv = self.app.post('/NetworkDriver.Leave',
                           data='{"EndpointID": "%s"}' % TEST_ENDPOINT_ID)
        self.assertDictEqual(json.loads(rv.data), {})

        m_veth.assert_called_once_with(
            generate_cali_interface_name(IF_PREFIX, TEST_ENDPOINT_ID))

    @patch("libnetwork.driver_plugin.client.remove_endpoint", autospec=True)
    def test_delete_endpoint(self, m_remove):
        """
        Test delete_endpoint() deletes the endpoint and backout IP assignment.
        """
        rv = self.app.post('/NetworkDriver.DeleteEndpoint',
                           data='{"EndpointID": "%s"}' % TEST_ENDPOINT_ID)
        m_remove.assert_called_once_with(
            Endpoint(hostname, "libnetwork", "docker", TEST_ENDPOINT_ID, None,
                     None))
        self.assertDictEqual(json.loads(rv.data), {})

    @patch("libnetwork.driver_plugin.client.remove_endpoint",
           autospec=True,
           side_effect=KeyError())
    def test_delete_endpoint_fail(self, m_remove):
        """
        Test delete_endpoint() deletes the endpoint and backout IP assignment.
        """
        rv = self.app.post('/NetworkDriver.DeleteEndpoint',
                           data='{"EndpointID": "%s"}' % TEST_ENDPOINT_ID)
        m_remove.assert_called_once_with(
            Endpoint(hostname, "libnetwork", "docker", TEST_ENDPOINT_ID, None,
                     None))
        self.assertDictEqual(json.loads(rv.data), {u'Err': u''})

    @patch("libnetwork.driver_plugin.client.get_network",
           autospec=True,
           return_value=None)
    def test_create_endpoint_missing_network(self, _):
        """
        Test the create_endpoint hook behavior when missing network data.
        """
        rv = self.app.post('/NetworkDriver.CreateEndpoint',
                           data="""
                           {"EndpointID": "%s",
                            "NetworkID":  "%s",
                            "Interface": {"MacAddress": "EE:EE:EE:EE:EE:EE",
                                          "Address": "1.2.3.4/32"
                            }}""" % (TEST_ENDPOINT_ID, TEST_NETWORK_ID))
        expected_data = {
            u'Err':
            u"CreateEndpoint called but network doesn't "
            u"exist Endpoint ID: TEST_ENDPOINT_ID "
            u"Network ID: TEST_NETWORK_ID"
        }
        self.assertDictEqual(json.loads(rv.data), expected_data)

    @patch("libnetwork.driver_plugin.client.get_network", autospec=True)
    @patch("libnetwork.driver_plugin.client.set_endpoint", autospec=True)
    def test_create_endpoint(self, m_set, m_get_network):
        """
        Test the create_endpoint hook correctly writes the appropriate data
        to etcd based on IP assignment.
        """

        # Iterate using various different mixtures of IP assignments.
        # (IPv4 addr, IPv6 addr)
        parms = [(None, IPAddress("aa:bb::bb")),
                 (IPAddress("10.20.30.40"), None),
                 (IPAddress("10.20.30.40"), IPAddress("aa:bb::bb"))]

        m_get_network.return_value = {
            "NetworkID": TEST_NETWORK_ID,
            "IPv4Data": [{
                "Gateway": "6.5.4.3"
            }],
            "IPv6Data": [{
                "Gateway": "aa:bb::cc"
            }]
        }

        # Loop through different combinations of IP availability.
        for ipv4, ipv6 in parms:
            ipv4_json = ',"Address": "%s"' % ipv4 if ipv4 else ""
            ipv6_json = ',"AddressIPv6": "%s"' % ipv6 if ipv6 else ""

            # Invoke create endpoint.
            rv = self.app.post(
                '/NetworkDriver.CreateEndpoint',
                data='{"EndpointID": "%s",'
                '"NetworkID":  "%s",'
                '"Interface": {"MacAddress": "EE:EE:EE:EE:EE:EE"%s%s}}' %
                (TEST_ENDPOINT_ID, TEST_NETWORK_ID, ipv4_json, ipv6_json))

            # Assert expected data is written to etcd
            ep = Endpoint(hostname, "libnetwork", "libnetwork",
                          TEST_ENDPOINT_ID, "active", "EE:EE:EE:EE:EE:EE")

            ep.profile_ids.append(TEST_NETWORK_ID)

            if ipv4:
                ep.ipv4_nets.add(IPNetwork(ipv4))
                ep.ipv4_gateway = IPAddress("6.5.4.3")

            if ipv6:
                ep.ipv6_nets.add(IPNetwork(ipv6))
                ep.ipv6_gateway = IPAddress("aa:bb::cc")

            m_set.assert_called_once_with(ep)

            # Assert return value
            self.assertDictEqual(json.loads(rv.data), {})

            # Reset the Mocks before continuing.
            m_set.reset_mock()

    @patch("pycalico.netns.remove_veth",
           autospec=True,
           side_effect=CalledProcessError(2, "test"))
    def test_remove_veth_fail(self, m_remove):
        """
        Test remove_veth calls through to netns to remove the veth.
        Fail with a CalledProcessError to write the log.
        """
        name = generate_cali_interface_name(IF_PREFIX, TEST_ENDPOINT_ID)

        driver_plugin.remove_veth(name)
        m_remove.assert_called_once_with(name)