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)
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)
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)
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)
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)
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)