def setUp(self, m_etcd_client, m_getenv): m_getenv.return_value = "127.0.0.2:4002" self.etcd_client = Mock(spec=EtcdClient) m_etcd_client.return_value = self.etcd_client self.datastore = DatastoreClient() m_etcd_client.assert_called_once_with(host="127.0.0.2", port=4002)
class TestDatastoreClient(unittest.TestCase): @patch("calico_containers.adapter.datastore.os.getenv", autospec=True) @patch("calico_containers.adapter.datastore.etcd.Client", autospec=True) def setUp(self, m_etcd_client, m_getenv): m_getenv.return_value = "127.0.0.2:4002" self.etcd_client = Mock(spec=EtcdClient) m_etcd_client.return_value = self.etcd_client self.datastore = DatastoreClient() m_etcd_client.assert_called_once_with(host="127.0.0.2", port=4002) def test_ensure_global_config(self): """ Test ensure_global_config when it doesn't already exist. """ self.etcd_client.read.side_effect = EtcdKeyNotFound self.datastore.ensure_global_config() expected_writes = [call(CALICO_V_PATH + "/Ready", "true"), call(CONFIG_PATH + "InterfacePrefix", "cali")] self.etcd_client.write.assert_has_calls(expected_writes, any_order=True) def test_ensure_global_config_exists(self): """ Test ensure_global_config() when it already exists. """ self.datastore.ensure_global_config() self.etcd_client.read.assert_called_once_with(CONFIG_PATH) def test_ensure_global_config_exists_etcd_exc(self): """ Test ensure_global_config() when etcd raises an EtcdException. """ self.etcd_client.read.side_effect = EtcdException self.assertRaises(DataStoreError, self.datastore.ensure_global_config) self.etcd_client.read.assert_called_once_with(CONFIG_PATH) def test_get_profile(self): """ Test getting a named profile that exists. Test getting a named profile that doesn't exist raises a KeyError. """ def mock_read(path): result = Mock(spec=EtcdResult) if path == TEST_PROFILE_PATH: return result elif path == TEST_PROFILE_PATH + "tags": result.value = '["TAG1", "TAG2", "TAG3"]' return result elif path == TEST_PROFILE_PATH + "rules": result.value = """ { "id": "TEST", "inbound_rules": [ {"action": "allow", "src_net": "192.168.1.0/24", "src_ports": [200,2001]} ], "outbound_rules": [ {"action": "allow", "src_tag": "TEST", "src_ports": [200,2001]} ] } """ return result else: raise EtcdKeyNotFound() self.etcd_client.read.side_effect = mock_read profile = self.datastore.get_profile("TEST") assert_equal(profile.name, "TEST") assert_set_equal({"TAG1", "TAG2", "TAG3"}, profile.tags) assert_equal(Rule(action="allow", src_net=IPNetwork("192.168.1.0/24"), src_ports=[200, 2001]), profile.rules.inbound_rules[0]) assert_equal(Rule(action="allow", src_tag="TEST", src_ports=[200, 2001]), profile.rules.outbound_rules[0]) assert_raises(KeyError, self.datastore.get_profile, "TEST2") def test_get_profile_no_tags_or_rules(self): """ Test getting a named profile that exists, but has no tags or rules. """ def mock_read(path): result = Mock(spec=EtcdResult) if path == TEST_PROFILE_PATH: return result else: raise EtcdKeyNotFound() self.etcd_client.read.side_effect = mock_read profile = self.datastore.get_profile("TEST") assert_equal(profile.name, "TEST") assert_set_equal(set(), profile.tags) assert_equal([], profile.rules.inbound_rules) assert_equal([], profile.rules.outbound_rules) @raises(KeyError) def test_remove_profile_doesnt_exist(self): """ Remove profile when it doesn't exist. Check it throws a KeyError. :return: None """ self.etcd_client.delete.side_effect = EtcdKeyNotFound self.datastore.remove_profile(TEST_PROFILE) def test_profile_update_tags(self): """ Test updating tags on an existing profile. :return: """ profile = Profile("TEST") profile.tags = {"TAG4", "TAG5"} profile.rules = Rules(id="TEST", inbound_rules=[ Rule(action="allow", dst_ports=[12]), Rule(action="allow", protocol="udp"), Rule(action="deny") ], outbound_rules=[ Rule(action="allow", src_ports=[23]), Rule(action="deny") ]) self.datastore.profile_update_tags(profile) self.etcd_client.write.assert_called_once_with( TEST_PROFILE_PATH + "tags", '["TAG4", "TAG5"]') def test_profile_update_rules(self): """ Test updating rules on an existing profile. :return: """ profile = Profile("TEST") profile.tags = {"TAG4", "TAG5"} profile.rules = Rules(id="TEST", inbound_rules=[ Rule(action="allow", dst_ports=[12]), Rule(action="allow", protocol="udp"), Rule(action="deny") ], outbound_rules=[ Rule(action="allow", src_ports=[23]), Rule(action="deny") ]) self.datastore.profile_update_rules(profile) self.etcd_client.write.assert_called_once_with( TEST_PROFILE_PATH + "rules", profile.rules.to_json()) def test_create_host_exists(self): """ Test create_host() when the .../workload key already exists. :return: None """ def mock_read_success(path): result = Mock(spec=EtcdResult) if path == TEST_HOST_PATH + "/workload": return result else: assert False self.etcd_client.read.side_effect = mock_read_success bird_ip = "192.168.2.4" bird6_ip = "fd80::4" self.datastore.create_host(TEST_HOST, bird_ip, bird6_ip) expected_writes = [call(TEST_HOST_PATH + "/bird_ip", bird_ip), call(TEST_HOST_PATH + "/bird6_ip", bird6_ip), call(TEST_HOST_PATH + "/config/marker", "created")] self.etcd_client.write.assert_has_calls(expected_writes, any_order=True) assert_equal(self.etcd_client.write.call_count, 3) def test_create_host_mainline(self): """ Test create_host() when none of the keys exists (specifically, .../workload is checked and doesn't exist). :return: None """ def mock_read(path): if path == CALICO_V_PATH + "/host/TEST_HOST/workload": raise EtcdKeyNotFound() else: assert False self.etcd_client.read.side_effect = mock_read bird_ip = "192.168.2.4" bird6_ip = "fd80::4" self.datastore.create_host(TEST_HOST, bird_ip, bird6_ip) expected_writes = [call(TEST_HOST_PATH + "/bird_ip", bird_ip), call(TEST_HOST_PATH + "/bird6_ip", bird6_ip), call(TEST_HOST_PATH + "/config/marker", "created"), call(TEST_HOST_PATH + "/workload", None, dir=True)] self.etcd_client.write.assert_has_calls(expected_writes, any_order=True) assert_equal(self.etcd_client.write.call_count, 4) def test_remove_host_mainline(self): """ Test remove_host() when the key exists. :return: """ self.datastore.remove_host(TEST_HOST) self.etcd_client.delete.assert_called_once_with(TEST_HOST_PATH + "/", dir=True, recursive=True) def test_remove_host_doesnt_exist(self): """ Remove host when it doesn't exist. Check it doesn't throw an exception. :return: None """ self.etcd_client.delete.side_effect = EtcdKeyNotFound self.datastore.remove_host(TEST_HOST) def test_get_ip_pools(self): """ Test getting IP pools from the datastore when there are some pools. :return: None """ self.etcd_client.read.side_effect = mock_read_2_pools pools = self.datastore.get_ip_pools("v4") assert_set_equal({IPNetwork("192.168.3.0/24"), IPNetwork("192.168.5.0/24")}, set(pools)) def test_get_ip_pools_no_key(self): """ Test getting IP pools from the datastore when the key doesn't exist. :return: None """ def mock_read(path): assert_equal(path, IPV4_POOLS_PATH) raise EtcdKeyNotFound() self.etcd_client.read.side_effect = mock_read pools = self.datastore.get_ip_pools("v4") assert_list_equal([], pools) def test_get_ip_pools_no_pools(self): """ Test getting IP pools from the datastore when the key is there but has no children. :return: None """ self.etcd_client.read.side_effect = mock_read_no_pools pools = self.datastore.get_ip_pools("v4") assert_list_equal([], pools) def test_add_ip_pool(self): """ Test adding an IP pool when the directory exists, but pool doesn't. :return: None """ self.etcd_client.read.side_effect = mock_read_2_pools pool = IPNetwork("192.168.100.5/24") self.datastore.add_ip_pool("v4", pool) self.etcd_client.write.assert_called_once_with(IPV4_POOLS_PATH, "192.168.100.0/24", append=True) def test_add_ip_pool_exists(self): """ Test adding an IP pool when the pool already exists. :return: None """ self.etcd_client.read.side_effect = mock_read_2_pools pool = IPNetwork("192.168.3.5/24") self.datastore.add_ip_pool("v4", pool) assert_false(self.etcd_client.write.called) def test_del_ip_pool_exists(self): """ Test remove_ip_pool() when the pool does exist. :return: None """ self.etcd_client.read.side_effect = mock_read_2_pools pool = IPNetwork("192.168.3.1/24") self.datastore.remove_ip_pool("v4", pool) # 192.168.3.0/24 has a key .../v4/pool/0 in the ordered list. self.etcd_client.delete.assert_called_once_with(IPV4_POOLS_PATH + "0") def test_del_ip_pool_doesnt_exist(self): """ Test remove_ip_pool() when the pool does not exist. :return: None """ self.etcd_client.read.side_effect = mock_read_2_pools pool = IPNetwork("192.168.100.1/24") assert_raises(KeyError, self.datastore.remove_ip_pool, "v4", pool) assert_false(self.etcd_client.delete.called) def test_profile_exists_true(self): """ Test profile_exists() when it does. """ def mock_read(path): assert_equal(path, TEST_PROFILE_PATH) return Mock(spec=EtcdResult) self.etcd_client.read.side_effect = mock_read assert_true(self.datastore.profile_exists("TEST")) def test_profile_exists_false(self): """ Test profile_exists() when it doesn't exist. """ def mock_read(path): assert_equal(path, TEST_PROFILE_PATH) raise EtcdKeyNotFound() self.etcd_client.read.side_effect = mock_read assert_false(self.datastore.profile_exists("TEST")) def test_create_profile(self): """ Test create_profile() """ self.datastore.create_profile("TEST") rules = Rules(id="TEST", inbound_rules=[Rule(action="allow", src_tag="TEST"), Rule(action="deny")], outbound_rules=[Rule(action="allow")]) expected_calls = [call(TEST_PROFILE_PATH + "tags", '["TEST"]'), call(TEST_PROFILE_PATH + "rules", rules.to_json())] self.etcd_client.write.assert_has_calls(expected_calls, any_order=True) def test_delete_profile(self): """ Test deleting a policy profile. """ self.datastore.remove_profile("TEST") self.etcd_client.delete.assert_called_once_with(TEST_PROFILE_PATH, recursive=True, dir=True) def test_get_profile_names_2(self): """ Test get_profile_names() when there are two profiles. """ self.etcd_client.read.side_effect = mock_read_2_profiles profiles = self.datastore.get_profile_names() assert_set_equal(profiles, {"UNIT", "TEST"}) def test_get_profile_names_no_key(self): """ Test get_profile_names() when the key hasn't been set up. Should return empty set and not raise a KeyError. """ self.etcd_client.read.side_effect = mock_read_profiles_key_error profiles = self.datastore.get_profile_names() assert_set_equal(profiles, set()) def test_get_profile_names_no_profiles(self): """ Test get_profile_names() when there are no profiles. """ self.etcd_client.read.side_effect = mock_read_no_profiles profiles = self.datastore.get_profile_names() assert_set_equal(profiles, set()) def test_get_profile_members_ep_ids(self): """ Test get_profile_members_ep_ids() when there are endpoints. """ self.etcd_client.read.side_effect = mock_read_4_endpoints members = self.datastore.get_profile_members_ep_ids("TEST") assert_set_equal({"567890abcdef", "7890abcdef12"}, set(members)) members = self.datastore.get_profile_members_ep_ids("UNIT") assert_set_equal({"90abcdef1234", TEST_ENDPOINT_ID}, set(members)) members = self.datastore.get_profile_members_ep_ids("UNIT_TEST") assert_set_equal(set(), set(members)) def test_get_profile_members_ep_ids_no_key(self): """ Test get_profile_members_ep_ids() when the endpoints path has not been set up. """ self.etcd_client.read.side_effect = mock_read_endpoints_key_error members = self.datastore.get_profile_members_ep_ids("UNIT_TEST") assert_set_equal(set(), set(members)) def test_get_profile_members(self): """ Test get_profile_members() when there are endpoints. """ self.maxDiff = 1000 self.etcd_client.read.side_effect = mock_read_4_endpoints members = self.datastore.get_profile_members("TEST") assert_dict_equal({"TEST_HOST": {"docker": {"1234": {"567890abcdef": EP_56}}}, "TEST_HOST2": {"docker": {"1234": {"7890abcdef12": EP_78}}} }, members) members = self.datastore.get_profile_members("UNIT") assert_dict_equal({"TEST_HOST": {"docker": {"5678": {"90abcdef1234": EP_90}}}, "TEST_HOST2": {"docker": {"5678": {"1234567890ab": EP_12}}} }, members) members = self.datastore.get_profile_members("UNIT_TEST") assert_dict_equal({}, members) def test_get_profile_members_no_key(self): """ Test get_profile_members() when the endpoints path has not been set up. """ self.etcd_client.read.side_effect = mock_read_endpoints_key_error members = self.datastore.get_profile_members("UNIT_TEST") assert_dict_equal({}, members) def test_get_endpoint_exists(self): """ Test get_endpoint() for an endpoint that exists. """ ep = Endpoint(TEST_ENDPOINT_ID, "active", "11-22-33-44-55-66", if_name="eth1") self.etcd_client.read.side_effect = mock_read_for_endpoint(ep) ep2 = self.datastore.get_endpoint(TEST_HOST, TEST_CONT_ID, TEST_ENDPOINT_ID) assert_equal(ep.to_json(), ep2.to_json()) assert_equal(ep.ep_id, ep2.ep_id) def test_get_endpoint_doesnt_exist(self): """ Test get_endpoint() for an endpoint that doesn't exist. """ def mock_read(path): assert_equal(path, TEST_ENDPOINT_PATH) raise EtcdKeyNotFound() self.etcd_client.read.side_effect = mock_read assert_raises(KeyError, self.datastore.get_endpoint, TEST_HOST, TEST_CONT_ID, TEST_ENDPOINT_ID) def test_set_endpoint(self): """ Test set_endpoint(). """ self.datastore.set_endpoint(TEST_HOST, TEST_CONT_ID, EP_12) self.etcd_client.write.assert_called_once_with(TEST_ENDPOINT_PATH, EP_12.to_json()) def test_update_endpoint(self): """ Test update_endpoint(). """ self.datastore.update_endpoint(TEST_HOST, TEST_CONT_ID, EP_90, EP_12) self.etcd_client.write.assert_called_once_with(TEST_ENDPOINT_PATH, EP_12.to_json(), prevValue=EP_90.to_json()) def test_get_ep_id_from_cont(self): """ Test get_ep_id_from_cont() when container and endpoint exist. """ self.etcd_client.read.side_effect = mock_read_2_ep_for_cont ep_id = self.datastore.get_ep_id_from_cont(TEST_HOST, TEST_CONT_ID) assert_equal(ep_id, EP_12.ep_id) def test_get_ep_id_from_cont_no_ep(self): """ Test get_ep_id_from_cont() when the container exists, but there are no endpoints. """ self.etcd_client.read.side_effect = mock_read_0_ep_for_cont assert_raises(NoEndpointForContainer, self.datastore.get_ep_id_from_cont, TEST_HOST, TEST_CONT_ID) def test_get_ep_id_from_cont_no_cont(self): """ Test get_ep_id_from_cont() when the container doesn't exist. """ self.etcd_client.read.side_effect = EtcdKeyNotFound assert_raises(KeyError, self.datastore.get_ep_id_from_cont, TEST_HOST, TEST_CONT_ID) def test_get_endpoints_exists(self): """ Test get_endpoints() for a container that exists. """ self.etcd_client.read.side_effect = mock_read_2_ep_for_cont eps = self.datastore.get_endpoints(TEST_HOST, TEST_CONT_ID) assert_equal(len(eps), 2) assert_equal(eps[0].to_json(), EP_12.to_json()) assert_equal(eps[0].ep_id, EP_12.ep_id) assert_equal(eps[1].to_json(), EP_78.to_json()) assert_equal(eps[1].ep_id, EP_78.ep_id) def test_get_endpoints_doesnt_exist(self): """ Test get_endpoints() for a container that doesn't exist. """ def mock_read(path): assert_equal(path, TEST_CONT_ENDPOINT_PATH) raise EtcdKeyNotFound() self.etcd_client.read.side_effect = mock_read assert_raises(KeyError, self.datastore.get_endpoints, TEST_HOST, TEST_CONT_ID) def test_add_workload_to_profile(self): """ Test add_workload_to_profile() when the workload exists. """ ep = Endpoint.from_json(EP_12.ep_id, EP_12.to_json()) def mock_read(path): if path == TEST_CONT_ENDPOINT_PATH: return mock_read_2_ep_for_cont(path) elif path == TEST_CONT_ENDPOINT_PATH + ep.ep_id: return mock_read_for_endpoint(ep)(path) else: assert_true(False) self.etcd_client.read.side_effect = mock_read self.datastore.add_workload_to_profile(TEST_HOST, "UNITTEST", TEST_CONT_ID) ep.profile_id = "UNITTEST" expected_write_json = ep.to_json() self.etcd_client.write.assert_called_once_with(TEST_ENDPOINT_PATH, expected_write_json) def test_remove_workload_from_profile(self): """ Test remove_workload_from_profile() when the workload exists. """ ep = Endpoint.from_json(EP_12.ep_id, EP_12.to_json()) def mock_read(path): if path == TEST_CONT_ENDPOINT_PATH: return mock_read_2_ep_for_cont(path) elif path == TEST_CONT_ENDPOINT_PATH + ep.ep_id: return mock_read_for_endpoint(ep)(path) else: assert_true(False) self.etcd_client.read.side_effect = mock_read self.datastore.remove_workload_from_profile(TEST_HOST, TEST_CONT_ID) ep.profile_id = None expected_write_json = ep.to_json() self.etcd_client.write.assert_called_once_with(TEST_ENDPOINT_PATH, expected_write_json) def test_get_hosts(self): """ Test get_hosts with two hosts, each with two containers, each with one endpoint. """ # Reuse etcd read from test_get_profile_members_* since it's the same # query. self.etcd_client.read.side_effect = mock_read_4_endpoints hosts = self.datastore.get_hosts() assert_equal(len(hosts), 2) assert_true(TEST_HOST in hosts) assert_true("TEST_HOST2" in hosts) test_host = hosts[TEST_HOST] assert_equal(len(test_host), 1) assert_true("docker" in test_host) test_host_workloads = test_host["docker"] assert_equal(len(test_host_workloads), 2) assert_true(TEST_CONT_ID in test_host_workloads) assert_true("5678" in test_host_workloads) assert_true(EP_56.ep_id in test_host_workloads[TEST_CONT_ID]) assert_equal(len(test_host_workloads[TEST_CONT_ID]), 1) assert_true(EP_90.ep_id in test_host_workloads["5678"]) assert_equal(len(test_host_workloads["5678"]), 1) test_host2 = hosts["TEST_HOST2"] assert_equal(len(test_host2), 1) assert_true("docker" in test_host2) test_host2_workloads = test_host2["docker"] assert_equal(len(test_host2_workloads), 2) assert_true(TEST_CONT_ID in test_host2_workloads) assert_true("5678" in test_host2_workloads) assert_true(EP_78.ep_id in test_host2_workloads[TEST_CONT_ID]) assert_equal(len(test_host2_workloads[TEST_CONT_ID]), 1) assert_true(EP_12.ep_id in test_host2_workloads["5678"]) assert_equal(len(test_host2_workloads["5678"]), 1) def test_get_hosts_key_error(self): """ Test get_hosts() when the read returns a KeyError. """ self.etcd_client.read.side_effect = EtcdKeyNotFound hosts = self.datastore.get_hosts() assert_dict_equal({}, hosts) def test_get_default_next_hops(self): """ Test get_default_next_hops when both are present. """ def mock_read(path): result = Mock(spec=EtcdResult) if path == TEST_HOST_PATH + "/bird_ip": result.value = "192.168.24.1" return result if path == TEST_HOST_PATH + "/bird6_ip": result.value = "fd30:4500::1" return result assert False self.etcd_client.read.side_effect = mock_read next_hops = self.datastore.get_default_next_hops(TEST_HOST) assert_dict_equal(next_hops, {4: IPAddress("192.168.24.1"), 6: IPAddress("fd30:4500::1")}) def test_get_default_next_hops_missing(self): """ Test get_default_next_hops when both are missing. """ def mock_read(path): result = Mock(spec=EtcdResult) if path == TEST_HOST_PATH + "/bird_ip": result.value = "" return result if path == TEST_HOST_PATH + "/bird6_ip": result.value = "" return result assert False self.etcd_client.read.side_effect = mock_read next_hops = self.datastore.get_default_next_hops(TEST_HOST) assert_dict_equal(next_hops, {}) @raises(KeyError) def test_get_default_next_hops_missing_config(self): """ Test get_default_next_hops raises a KeyError when the BIRD configuration is missing from etcd. """ self.etcd_client.read.side_effect = EtcdKeyNotFound next_hops = self.datastore.get_default_next_hops(TEST_HOST) def test_remove_all_data(self): """ Test remove_all_data() when /calico does exist. """ self.datastore.remove_all_data() self.etcd_client.delete.assert_called_once_with("/calico", recursive=True, dir=True) def test_remove_all_data_key_error(self): """ Test remove_all_data() when delete() throws a KeyError. """ self.etcd_client.delete.side_effect = EtcdKeyNotFound self.datastore.remove_all_data() # should not throw exception. def test_remove_container(self): """ Test remove_container() """ self.datastore.remove_container(TEST_HOST, TEST_CONT_ID) self.etcd_client.delete.assert_called_once_with(TEST_CONT_PATH, recursive=True, dir=True) @raises(KeyError) def test_remove_container_missing(self): """ Test remove_container() raises a KeyError if the container does not exist. """ self.etcd_client.delete.side_effect = EtcdKeyNotFound self.datastore.remove_container(TEST_HOST, TEST_CONT_ID) def test_get_bgp_peer(self): """ Test getting IP peers from the datastore when there are some peers. :return: None """ self.etcd_client.read.side_effect = mock_read_2_peers peers = self.datastore.get_bgp_peers("v4") assert_set_equal({IPAddress("192.168.3.1"), IPAddress("192.168.5.1")}, set(peers)) def test_get_bgp_peer_no_key(self): """ Test getting IP peers from the datastore when the key doesn't exist. :return: None """ def mock_read(path): assert_equal(path, BGP_PEERS_PATH) raise EtcdKeyNotFound() self.etcd_client.read.side_effect = mock_read peers = self.datastore.get_bgp_peers("v4") assert_list_equal([], peers) def test_get_bgp_peer_no_peers(self): """ Test getting BGP peers from the datastore when the key is there but has no children. :return: None """ self.etcd_client.read.side_effect = mock_read_no_bgppeers peers = self.datastore.get_bgp_peers("v4") assert_list_equal([], peers) def test_add_bgp_peer(self): """ Test adding an IP peer when the directory exists, but peer doesn't. :return: None """ self.etcd_client.read.side_effect = mock_read_2_peers peer = IPAddress("192.168.100.5") self.datastore.add_bgp_peer("v4", peer) self.etcd_client.write.assert_called_once_with(BGP_PEERS_PATH, "192.168.100.5", append=True) def test_add_bgp_peer_exists(self): """ Test adding an IP peer when the peer already exists. :return: None """ self.etcd_client.read.side_effect = mock_read_2_peers peer = IPAddress("192.168.3.1") self.datastore.add_bgp_peer("v4", peer) assert_false(self.etcd_client.write.called) def test_del_bgp_peer_exists(self): """ Test del_bgp_peer() when the peer does exist. :return: None """ self.etcd_client.read.side_effect = mock_read_2_peers peer = IPAddress("192.168.3.1") self.datastore.remove_bgp_peer("v4", peer) # 192.168.3.1 has a key ...v4/0 in the ordered list. self.etcd_client.delete.assert_called_once_with(BGP_PEERS_PATH + "0") def test_del_bgp_peer_doesnt_exist(self): """ Test del_bgp_peer() when the peer does not exist. :return: None """ self.etcd_client.read.side_effect = mock_read_2_peers peer = IPAddress("192.168.100.1") assert_raises(KeyError, self.datastore.remove_bgp_peer, "v4", peer) assert_false(self.etcd_client.delete.called)