diff options
author | Thomas Haller <thaller@redhat.com> | 2023-05-12 12:45:16 +0200 |
---|---|---|
committer | Thomas Haller <thaller@redhat.com> | 2023-05-12 12:45:16 +0200 |
commit | fe5dfe97f970cd358cbd06edb82f33d8ca207c59 (patch) | |
tree | dc8cfb99c31ac38b5263a7f332000e04f1c01804 | |
parent | a206042eda16abdb4df71a510be78480036048b2 (diff) | |
parent | c72e085f5cb1c10a380aed0969c1503f374667c5 (diff) |
cloud-setup: merge branch 'lr/more-cloud-setup-tests'
https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/1606
-rw-r--r-- | src/libnm-core-aux-intern/nm-libnm-core-utils.h | 7 | ||||
-rw-r--r-- | src/nm-cloud-setup/main.c | 9 | ||||
-rw-r--r-- | src/nm-cloud-setup/nmcs-provider-azure.c | 24 | ||||
-rw-r--r-- | src/nm-cloud-setup/nmcs-provider-gcp.c | 47 | ||||
-rw-r--r-- | src/nm-cloud-setup/nmcs-provider.c | 2 | ||||
-rw-r--r-- | src/nm-cloud-setup/nmcs-provider.h | 7 | ||||
-rwxr-xr-x | src/tests/client/test-client.py | 308 | ||||
-rwxr-xr-x | tools/test-cloud-meta-mock.py | 122 |
8 files changed, 477 insertions, 49 deletions
diff --git a/src/libnm-core-aux-intern/nm-libnm-core-utils.h b/src/libnm-core-aux-intern/nm-libnm-core-utils.h index 589291e5a9..b1336731be 100644 --- a/src/libnm-core-aux-intern/nm-libnm-core-utils.h +++ b/src/libnm-core-aux-intern/nm-libnm-core-utils.h @@ -21,6 +21,13 @@ #define nm_auto_unref_ip_address nm_auto(_nm_ip_address_unref) NM_AUTO_DEFINE_FCN0(NMIPAddress *, _nm_ip_address_unref, nm_ip_address_unref); +static inline NMIPRoute * +_nm_ip_route_ref(NMIPRoute *route) +{ + nm_ip_route_ref(route); + return route; +} + #define nm_auto_unref_ip_route nm_auto(_nm_auto_unref_ip_route) NM_AUTO_DEFINE_FCN0(NMIPRoute *, _nm_auto_unref_ip_route, nm_ip_route_unref); diff --git a/src/nm-cloud-setup/main.c b/src/nm-cloud-setup/main.c index d6802634bd..5efa87bd3d 100644 --- a/src/nm-cloud-setup/main.c +++ b/src/nm-cloud-setup/main.c @@ -315,8 +315,9 @@ _nmc_mangle_connection(NMDevice *device, addrs_new = g_ptr_array_new_full(config_data->ipv4s_len, (GDestroyNotify) nm_ip_address_unref); rules_new = g_ptr_array_new_full(config_data->ipv4s_len, (GDestroyNotify) nm_ip_routing_rule_unref); - routes_new = g_ptr_array_new_full(config_data->iproutes_len + !!config_data->ipv4s_len, - (GDestroyNotify) nm_ip_route_unref); + routes_new = + g_ptr_array_new_full(nm_g_ptr_array_len(config_data->iproutes) + !!config_data->ipv4s_len, + (GDestroyNotify) nm_ip_route_unref); if (remote_s_ip) { guint len; @@ -422,8 +423,8 @@ _nmc_mangle_connection(NMDevice *device, } } - for (i = 0; i < config_data->iproutes_len; ++i) - g_ptr_array_add(routes_new, config_data->iproutes_arr[i]); + for (i = 0; i < nm_g_ptr_array_len(config_data->iproutes); i++) + g_ptr_array_add(routes_new, _nm_ip_route_ref(config_data->iproutes->pdata[i])); addrs_changed = nmcs_setting_ip_replace_ipv4_addresses(s_ip, (NMIPAddress **) addrs_new->pdata, diff --git a/src/nm-cloud-setup/nmcs-provider-azure.c b/src/nm-cloud-setup/nmcs-provider-azure.c index 69946f5c53..418d380a5d 100644 --- a/src/nm-cloud-setup/nmcs-provider-azure.c +++ b/src/nm-cloud-setup/nmcs-provider-azure.c @@ -17,8 +17,30 @@ #define NM_AZURE_METADATA_URL_BASE /* $NM_AZURE_BASE/$NM_AZURE_API_VERSION */ \ "/metadata/instance/network/interface/" +static const char * +_azure_base(void) +{ + static const char *base_cached = NULL; + const char *base; + +again: + base = g_atomic_pointer_get(&base_cached); + if (G_UNLIKELY(!base)) { + /* The base URI can be set via environment variable. + * This is mainly for testing, it's not usually supposed to be configured. + * Consider this private API! */ + base = g_getenv(NMCS_ENV_VARIABLE("NM_CLOUD_SETUP_AZURE_HOST")); + base = nmcs_utils_uri_complete_interned(base) ?: ("" NM_AZURE_BASE); + + if (!g_atomic_pointer_compare_and_exchange(&base_cached, NULL, base)) + goto again; + } + + return base; +} + #define _azure_uri_concat(...) \ - nmcs_utils_uri_build_concat(NM_AZURE_BASE, __VA_ARGS__, NM_AZURE_API_VERSION) + nmcs_utils_uri_build_concat(_azure_base(), __VA_ARGS__, NM_AZURE_API_VERSION) #define _azure_uri_interfaces(...) _azure_uri_concat(NM_AZURE_METADATA_URL_BASE, ##__VA_ARGS__) /*****************************************************************************/ diff --git a/src/nm-cloud-setup/nmcs-provider-gcp.c b/src/nm-cloud-setup/nmcs-provider-gcp.c index ca354865dd..33d0fe08f9 100644 --- a/src/nm-cloud-setup/nmcs-provider-gcp.c +++ b/src/nm-cloud-setup/nmcs-provider-gcp.c @@ -13,15 +13,39 @@ #define HTTP_POLL_TIMEOUT_MS 10000 #define HTTP_RATE_LIMIT_MS 1000 -#define NM_GCP_HOST "metadata.google.internal" -#define NM_GCP_BASE "http://" NM_GCP_HOST -#define NM_GCP_API_VERSION "/v1" -#define NM_GCP_METADATA_URL_BASE NM_GCP_BASE "/computeMetadata" NM_GCP_API_VERSION "/instance" -#define NM_GCP_METADATA_URL_NET "/network-interfaces/" +#define NM_GCP_HOST "metadata.google.internal" +#define NM_GCP_BASE "http://" NM_GCP_HOST +#define NM_GCP_API_VERSION "/v1" +#define NM_GCP_METADATA_URL_NET "/network-interfaces/" #define NM_GCP_METADATA_HEADER "Metadata-Flavor: Google" -#define _gcp_uri_concat(...) nmcs_utils_uri_build_concat(NM_GCP_METADATA_URL_BASE, __VA_ARGS__) +static const char * +_gcp_base(void) +{ + static const char *base_cached = NULL; + const char *base; + +again: + base = g_atomic_pointer_get(&base_cached); + if (G_UNLIKELY(!base)) { + /* The base URI can be set via environment variable. + * This is mainly for testing, it's not usually supposed to be configured. + * Consider this private API! */ + base = g_getenv(NMCS_ENV_VARIABLE("NM_CLOUD_SETUP_GCP_HOST")); + base = nmcs_utils_uri_complete_interned(base) ?: ("" NM_GCP_BASE); + + if (!g_atomic_pointer_compare_and_exchange(&base_cached, NULL, base)) + goto again; + } + + return base; +} + +#define _gcp_uri_concat(...) \ + nmcs_utils_uri_build_concat(_gcp_base(), \ + "/computeMetadata" NM_GCP_API_VERSION "/instance", \ + __VA_ARGS__) #define _gcp_uri_interfaces(...) _gcp_uri_concat(NM_GCP_METADATA_URL_NET, ##__VA_ARGS__) /*****************************************************************************/ @@ -73,7 +97,7 @@ detect(NMCSProvider *provider, GTask *task) http_client = nmcs_provider_get_http_client(provider); nm_http_client_poll_req(http_client, - (uri = _gcp_uri_concat("id")), + (uri = _gcp_uri_concat("/id")), HTTP_TIMEOUT_MS, 256 * 1024, 7000, @@ -112,7 +136,6 @@ _get_config_fip_cb(GObject *source, GAsyncResult *result, gpointer user_data) GCPIfaceData *iface_data = user_data; gs_free_error GError *error = NULL; gs_free char *ipaddr = NULL; - NMIPRoute **routes_arr; NMIPRoute *route_new; nm_http_client_poll_req_finish(NM_HTTP_CLIENT(source), result, NULL, &response, &error); @@ -137,15 +160,14 @@ _get_config_fip_cb(GObject *source, GAsyncResult *result, gpointer user_data) ipaddr); iface_get_config = iface_data->iface_get_config; - routes_arr = iface_get_config->iproutes_arr; route_new = nm_ip_route_new(AF_INET, ipaddr, 32, NULL, 100, &error); if (error) goto out_done; nm_ip_route_set_attribute(route_new, NM_IP_ROUTE_ATTRIBUTE_TYPE, g_variant_new_string("local")); - routes_arr[iface_get_config->iproutes_len] = route_new; - ++iface_get_config->iproutes_len; + + g_ptr_array_add(iface_get_config->iproutes, route_new); out_done: if (!error) { @@ -215,7 +237,8 @@ _get_config_ips_list_cb(GObject *source, GAsyncResult *result, gpointer user_dat goto out_error; } - iface_data->iface_get_config->iproutes_arr = g_new(NMIPRoute *, iface_data->n_fips_pending); + iface_data->iface_get_config->iproutes = + g_ptr_array_new_full(iface_data->n_fips_pending, (GDestroyNotify) nm_ip_route_unref); for (i = 0; i < uri_arr->len; ++i) { const char *str = uri_arr->pdata[i]; diff --git a/src/nm-cloud-setup/nmcs-provider.c b/src/nm-cloud-setup/nmcs-provider.c index fd9a61b813..d785fd23cf 100644 --- a/src/nm-cloud-setup/nmcs-provider.c +++ b/src/nm-cloud-setup/nmcs-provider.c @@ -216,7 +216,7 @@ _iface_data_free(gpointer data) NMCSProviderGetConfigIfaceData *iface_data = data; g_free(iface_data->ipv4s_arr); - g_free(iface_data->iproutes_arr); + nm_g_ptr_array_unref(iface_data->iproutes); g_free((char *) iface_data->hwaddr); nm_g_slice_free(iface_data); diff --git a/src/nm-cloud-setup/nmcs-provider.h b/src/nm-cloud-setup/nmcs-provider.h index 09cdb4143d..9e5eeebe69 100644 --- a/src/nm-cloud-setup/nmcs-provider.h +++ b/src/nm-cloud-setup/nmcs-provider.h @@ -34,8 +34,8 @@ typedef struct { bool has_cidr : 1; bool has_gateway : 1; - NMIPRoute **iproutes_arr; - gsize iproutes_len; + /* Array of NMIPRoute (must own/free the entries). */ + GPtrArray *iproutes; /* TRUE, if the configuration was requested via hwaddrs argument to * nmcs_provider_get_config(). */ @@ -59,7 +59,8 @@ static inline gboolean nmcs_provider_get_config_iface_data_is_valid(const NMCSProviderGetConfigIfaceData *config_data) { return config_data && config_data->iface_idx >= 0 - && ((config_data->has_ipv4s && config_data->has_cidr) || config_data->iproutes_len); + && ((config_data->has_ipv4s && config_data->has_cidr) + || nm_g_ptr_array_len(config_data->iproutes) > 0); } /*****************************************************************************/ diff --git a/src/tests/client/test-client.py b/src/tests/client/test-client.py index b6f7cf00a4..dc9efd8553 100755 --- a/src/tests/client/test-client.py +++ b/src/tests/client/test-client.py @@ -147,6 +147,7 @@ except ImportError: try: from http.server import HTTPServer from http.server import BaseHTTPRequestHandler + from http.client import HTTPConnection, HTTPResponse except ImportError: HTTPServer = None @@ -1009,6 +1010,8 @@ class TestNmClient(unittest.TestCase): pexp = pexpect.spawn(argv[0], argv[1:], timeout=10, env=env) + pexp.str_last_chars = 100000 + typ = collections.namedtuple("CallPexpect", ["pexp", "valgrind_log"]) return typ(pexp, valgrind_log) @@ -2137,6 +2140,13 @@ class TestNmcli(TestNmClient): class TestNmCloudSetup(TestNmClient): + + _mac1 = "9e:c0:3e:92:24:2d" + _mac2 = "53:e9:7e:52:8d:a8" + + _ip1 = "172.31.26.249" + _ip2 = "172.31.176.249" + def cloud_setup_test(func): """ Runs the mock NetworkManager along with a mock cloud metadata service. @@ -2160,34 +2170,47 @@ class TestNmCloudSetup(TestNmClient): # hallucinogenic substances. s.listen(5) + def pass_socket(): + os.dup2(s.fileno(), 3) + service_path = PathConfiguration.test_cloud_meta_mock_path() env = os.environ.copy() - env["LISTEN_FD"] = str(s.fileno()) + env["LISTEN_FDS"] = "1" p = subprocess.Popen( - [sys.executable, service_path], + [sys.executable, service_path, "--empty"], stdin=subprocess.PIPE, env=env, - pass_fds=(s.fileno(),), + pass_fds=(3,), + preexec_fn=pass_socket, ) - self.md_url = "http://%s:%d" % s.getsockname() + (hostaddr, port) = s.getsockname() + self.md_conn = HTTPConnection(hostaddr, port=port) + self.md_url = "http://%s:%d" % (hostaddr, port) s.close() + error = None + self.srv_start() - func(self) + try: + func(self) + except Exception as e: + error = e self._nm_test_post() + self.md_conn.close() p.stdin.close() p.terminate() p.wait() - return f + if error: + raise error - @cloud_setup_test - def test_ec2(self): + return f + def _mock_devices(self): # Add a device with an active connection that has IPv4 configured - self.srv.op_AddObj("WiredDevice", iface="eth0") + self.srv.op_AddObj("WiredDevice", iface="eth0", mac="9e:c0:3e:92:24:2d") self.srv.addAndActivateConnection( { "connection": {"type": "802-3-ethernet", "id": "con-eth0"}, @@ -2198,7 +2221,7 @@ class TestNmCloudSetup(TestNmClient): ) # The second connection has no IPv4 - self.srv.op_AddObj("WiredDevice", iface="eth1") + self.srv.op_AddObj("WiredDevice", iface="eth1", mac="53:e9:7e:52:8d:a8") self.srv.addAndActivateConnection( {"connection": {"type": "802-3-ethernet", "id": "con-eth1"}}, "/org/freedesktop/NetworkManager/Devices/2", @@ -2206,6 +2229,210 @@ class TestNmCloudSetup(TestNmClient): delay=0, ) + def _mock_path(self, path, body): + self.md_conn.request("PUT", path, body=body) + self.md_conn.getresponse().read() + + @cloud_setup_test + def test_aliyun(self): + self._mock_devices() + + _aliyun_meta = "/2016-01-01/meta-data/" + _aliyun_macs = _aliyun_meta + "network/interfaces/macs/" + self._mock_path(_aliyun_meta, "ami-id\n") + self._mock_path( + _aliyun_macs, TestNmCloudSetup._mac2 + "\n" + TestNmCloudSetup._mac1 + ) + self._mock_path( + _aliyun_macs + TestNmCloudSetup._mac2 + "/vpc-cidr-block", "172.31.16.0/20" + ) + self._mock_path( + _aliyun_macs + TestNmCloudSetup._mac2 + "/private-ipv4s", + TestNmCloudSetup._ip1, + ) + self._mock_path( + _aliyun_macs + TestNmCloudSetup._mac2 + "/primary-ip-address", + TestNmCloudSetup._ip1, + ) + self._mock_path( + _aliyun_macs + TestNmCloudSetup._mac2 + "/netmask", "255.255.255.0" + ) + self._mock_path( + _aliyun_macs + TestNmCloudSetup._mac2 + "/gateway", "172.31.26.2" + ) + self._mock_path( + _aliyun_macs + TestNmCloudSetup._mac1 + "/vpc-cidr-block", "172.31.166.0/20" + ) + self._mock_path( + _aliyun_macs + TestNmCloudSetup._mac1 + "/private-ipv4s", + TestNmCloudSetup._ip2, + ) + self._mock_path( + _aliyun_macs + TestNmCloudSetup._mac1 + "/primary-ip-address", + TestNmCloudSetup._ip2, + ) + self._mock_path( + _aliyun_macs + TestNmCloudSetup._mac1 + "/netmask", "255.255.255.0" + ) + self._mock_path( + _aliyun_macs + TestNmCloudSetup._mac1 + "/gateway", "172.31.176.2" + ) + + # Run nm-cloud-setup for the first time + nmc = self.call_pexpect( + ENV_NM_TEST_CLIENT_CLOUD_SETUP_PATH, + [], + { + "NM_CLOUD_SETUP_ALIYUN_HOST": self.md_url, + "NM_CLOUD_SETUP_LOG": "trace", + "NM_CLOUD_SETUP_ALIYUN": "yes", + }, + ) + + nmc.pexp.expect("provider aliyun detected") + nmc.pexp.expect("found interfaces: 9E:C0:3E:92:24:2D, 53:E9:7E:52:8D:A8") + nmc.pexp.expect("get-config: start fetching meta data") + nmc.pexp.expect("get-config: success") + nmc.pexp.expect("meta data received") + # One of the devices has no IPv4 configuration to be modified + nmc.pexp.expect("device has no suitable applied connection. Skip") + # The other one was lacking an address set it up. + nmc.pexp.expect("some changes were applied for provider aliyun") + nmc.pexp.expect(pexpect.EOF) + + # Run nm-cloud-setup for the second time + nmc = self.call_pexpect( + ENV_NM_TEST_CLIENT_CLOUD_SETUP_PATH, + [], + { + "NM_CLOUD_SETUP_ALIYUN_HOST": self.md_url, + "NM_CLOUD_SETUP_LOG": "trace", + "NM_CLOUD_SETUP_ALIYUN": "yes", + }, + ) + + nmc.pexp.expect("provider aliyun detected") + nmc.pexp.expect("found interfaces: 9E:C0:3E:92:24:2D, 53:E9:7E:52:8D:A8") + nmc.pexp.expect("get-config: starting") + nmc.pexp.expect("get-config: success") + nmc.pexp.expect("meta data received") + # No changes this time + nmc.pexp.expect('device needs no update to applied connection "con-eth0"') + nmc.pexp.expect("no changes were applied for provider aliyun") + nmc.pexp.expect(pexpect.EOF) + + Util.valgrind_check_log(nmc.valgrind_log, "test_aliyun") + + @cloud_setup_test + def test_azure(self): + self._mock_devices() + + _azure_meta = "/metadata/instance" + _azure_iface = _azure_meta + "/network/interface/" + _azure_query = "?format=text&api-version=2017-04-02" + self._mock_path(_azure_meta + _azure_query, "") + self._mock_path(_azure_iface + _azure_query, "0\n1\n") + self._mock_path( + _azure_iface + "0/macAddress" + _azure_query, TestNmCloudSetup._mac1 + ) + self._mock_path( + _azure_iface + "1/macAddress" + _azure_query, TestNmCloudSetup._mac2 + ) + self._mock_path(_azure_iface + "0/ipv4/ipAddress/" + _azure_query, "0\n") + self._mock_path(_azure_iface + "1/ipv4/ipAddress/" + _azure_query, "0\n") + self._mock_path( + _azure_iface + "0/ipv4/ipAddress/0/privateIpAddress" + _azure_query, + TestNmCloudSetup._ip1, + ) + self._mock_path( + _azure_iface + "1/ipv4/ipAddress/0/privateIpAddress" + _azure_query, + TestNmCloudSetup._ip2, + ) + self._mock_path( + _azure_iface + "0/ipv4/subnet/0/address/" + _azure_query, "172.31.16.0" + ) + self._mock_path( + _azure_iface + "1/ipv4/subnet/0/address/" + _azure_query, "172.31.166.0" + ) + self._mock_path(_azure_iface + "0/ipv4/subnet/0/prefix/" + _azure_query, "20") + self._mock_path(_azure_iface + "1/ipv4/subnet/0/prefix/" + _azure_query, "20") + + # Run nm-cloud-setup for the first time + nmc = self.call_pexpect( + ENV_NM_TEST_CLIENT_CLOUD_SETUP_PATH, + [], + { + "NM_CLOUD_SETUP_AZURE_HOST": self.md_url, + "NM_CLOUD_SETUP_LOG": "trace", + "NM_CLOUD_SETUP_AZURE": "yes", + }, + ) + + nmc.pexp.expect("provider azure detected") + nmc.pexp.expect("found interfaces: 9E:C0:3E:92:24:2D, 53:E9:7E:52:8D:A8") + nmc.pexp.expect("found azure interfaces: 2") + nmc.pexp.expect("interface\[0]: found a matching device with hwaddr") + nmc.pexp.expect( + "interface\[0]: (received subnet address|received subnet prefix 20)" + ) + nmc.pexp.expect( + "interface\[0]: (received subnet address|received subnet prefix 20)" + ) + nmc.pexp.expect("get-config: success") + nmc.pexp.expect("meta data received") + # One of the devices has no IPv4 configuration to be modified + nmc.pexp.expect("device has no suitable applied connection. Skip") + # The other one was lacking an address set it up. + nmc.pexp.expect("some changes were applied for provider azure") + nmc.pexp.expect(pexpect.EOF) + + # Run nm-cloud-setup for the second time + nmc = self.call_pexpect( + ENV_NM_TEST_CLIENT_CLOUD_SETUP_PATH, + [], + { + "NM_CLOUD_SETUP_AZURE_HOST": self.md_url, + "NM_CLOUD_SETUP_LOG": "trace", + "NM_CLOUD_SETUP_AZURE": "yes", + }, + ) + + nmc.pexp.expect("provider azure detected") + nmc.pexp.expect("found interfaces: 9E:C0:3E:92:24:2D, 53:E9:7E:52:8D:A8") + nmc.pexp.expect("get-config: starting") + nmc.pexp.expect("get-config: success") + nmc.pexp.expect("meta data received") + # No changes this time + nmc.pexp.expect('device needs no update to applied connection "con-eth0"') + nmc.pexp.expect("no changes were applied for provider azure") + nmc.pexp.expect(pexpect.EOF) + + Util.valgrind_check_log(nmc.valgrind_log, "test_azure") + + @cloud_setup_test + def test_ec2(self): + self._mock_devices() + + _ec2_macs = "/2018-09-24/meta-data/network/interfaces/macs/" + self._mock_path("/latest/meta-data/", "ami-id\n") + self._mock_path( + _ec2_macs, TestNmCloudSetup._mac2 + "\n" + TestNmCloudSetup._mac1 + ) + self._mock_path( + _ec2_macs + TestNmCloudSetup._mac2 + "/subnet-ipv4-cidr-block", + "172.31.16.0/20", + ) + self._mock_path( + _ec2_macs + TestNmCloudSetup._mac2 + "/local-ipv4s", TestNmCloudSetup._ip1 + ) + self._mock_path( + _ec2_macs + TestNmCloudSetup._mac1 + "/subnet-ipv4-cidr-block", + "172.31.166.0/20", + ) + self._mock_path( + _ec2_macs + TestNmCloudSetup._mac1 + "/local-ipv4s", TestNmCloudSetup._ip2 + ) + # Run nm-cloud-setup for the first time nmc = self.call_pexpect( ENV_NM_TEST_CLIENT_CLOUD_SETUP_PATH, @@ -2251,6 +2478,67 @@ class TestNmCloudSetup(TestNmClient): Util.valgrind_check_log(nmc.valgrind_log, "test_ec2") + @cloud_setup_test + def test_gcp(self): + self._mock_devices() + + gcp_meta = "/computeMetadata/v1/instance/" + gcp_iface = gcp_meta + "network-interfaces/" + self._mock_path(gcp_meta + "id", "") + self._mock_path(gcp_iface, "0\n1\n") + self._mock_path(gcp_iface + "0/mac", TestNmCloudSetup._mac1) + self._mock_path(gcp_iface + "1/mac", TestNmCloudSetup._mac2) + self._mock_path(gcp_iface + "0/forwarded-ips/", "0\n") + self._mock_path(gcp_iface + "0/forwarded-ips/0", TestNmCloudSetup._ip1) + self._mock_path(gcp_iface + "1/forwarded-ips/", "0\n") + self._mock_path(gcp_iface + "1/forwarded-ips/0", TestNmCloudSetup._ip2) + + # Run nm-cloud-setup for the first time + nmc = self.call_pexpect( + ENV_NM_TEST_CLIENT_CLOUD_SETUP_PATH, + [], + { + "NM_CLOUD_SETUP_GCP_HOST": self.md_url, + "NM_CLOUD_SETUP_LOG": "trace", + "NM_CLOUD_SETUP_GCP": "yes", + }, + ) + + nmc.pexp.expect("provider GCP detected") + nmc.pexp.expect("found interfaces: 9E:C0:3E:92:24:2D, 53:E9:7E:52:8D:A8") + nmc.pexp.expect("found GCP interfaces: 2") + nmc.pexp.expect("GCP interface\[0]: found a requested device with hwaddr") + nmc.pexp.expect("get-config: success") + nmc.pexp.expect("meta data received") + # One of the devices has no IPv4 configuration to be modified + nmc.pexp.expect("device has no suitable applied connection. Skip") + # The other one was lacking an address set it up. + nmc.pexp.expect("some changes were applied for provider GCP") + nmc.pexp.expect(pexpect.EOF) + + # Run nm-cloud-setup for the second time + nmc = self.call_pexpect( + ENV_NM_TEST_CLIENT_CLOUD_SETUP_PATH, + [], + { + "NM_CLOUD_SETUP_GCP_HOST": self.md_url, + "NM_CLOUD_SETUP_LOG": "trace", + "NM_CLOUD_SETUP_GCP": "yes", + }, + ) + + nmc.pexp.expect("provider GCP detected") + nmc.pexp.expect("found interfaces: 9E:C0:3E:92:24:2D, 53:E9:7E:52:8D:A8") + nmc.pexp.expect("get-config: starting") + nmc.pexp.expect("get-config: success") + nmc.pexp.expect("meta data received") + # No changes this time + nmc.pexp.expect('device needs no update to applied connection "con-eth0"') + nmc.pexp.expect("no changes were applied for provider GCP") + nmc.pexp.expect(pexpect.EOF) + + Util.valgrind_check_log(nmc.valgrind_log, "test_gcp") + ############################################################################### diff --git a/tools/test-cloud-meta-mock.py b/tools/test-cloud-meta-mock.py index 392955b8ad..ab3630addf 100755 --- a/tools/test-cloud-meta-mock.py +++ b/tools/test-cloud-meta-mock.py @@ -1,13 +1,23 @@ #!/usr/bin/env python +# A service that mocks up various metadata providers. Used for testing, +# can also be used standalone as a development aid. +# +# To run standalone: +# # run: $ systemd-socket-activate -l 8000 python tools/test-cloud-meta-mock.py & # $ NM_CLOUD_SETUP_EC2_HOST=http://localhost:8000 \ # NM_CLOUD_SETUP_LOG=trace \ # NM_CLOUD_SETUP_EC2=yes src/nm-cloud-setup/nm-cloud-setup # or just: $ python tools/test-cloud-meta-mock.py +# +# By default, the utility will server some resources for each known cloud +# providers, for convenience. The tests start this with "--empty" argument, +# which starts with no resources. import os import socket +from sys import argv from http.server import HTTPServer from http.server import BaseHTTPRequestHandler @@ -20,36 +30,40 @@ class MockCloudMDRequestHandler(BaseHTTPRequestHandler): Currently implements a fairly minimal subset of AWS EC2 API. """ - _ec2_macs = "/2018-09-24/meta-data/network/interfaces/macs/" - _meta_resources = { - "/latest/meta-data/": b"ami-id\n", - _ec2_macs: b"9e:c0:3e:92:24:2d\n53:e9:7e:52:8d:a8", - _ec2_macs + "9e:c0:3e:92:24:2d/subnet-ipv4-cidr-block": b"172.31.16.0/20", - _ec2_macs + "9e:c0:3e:92:24:2d/local-ipv4s": b"172.31.26.249", - _ec2_macs + "53:e9:7e:52:8d:a8/subnet-ipv4-cidr-block": b"172.31.166.0/20", - _ec2_macs + "53:e9:7e:52:8d:a8/local-ipv4s": b"172.31.176.249", - } - def log_message(self, format, *args): pass def do_GET(self): - if self.path in self._meta_resources: + path = self.path.encode("ascii") + if path in self.server._resources: self.send_response(200) self.end_headers() - self.wfile.write(self._meta_resources[self.path]) + self.wfile.write(self.server._resources[path]) else: self.send_response(404) self.end_headers() def do_PUT(self): - if self.path == "/latest/api/token": + path = self.path.encode("ascii") + if path == b"/latest/api/token": self.send_response(200) self.end_headers() self.wfile.write( b"AQAAALH-k7i18JMkK-ORLZQfAa7nkNjQbKwpQPExNHqzk1oL_7eh-A==" ) else: + length = int(self.headers["content-length"]) + self.server._resources[path] = self.rfile.read(length) + self.send_response(201) + self.end_headers() + + def do_DELETE(self): + path = self.path.encode("ascii") + if path in self.server._resources: + del self.server._resources[path] + self.send_response(204) + self.end_headers() + else: self.send_response(404) self.end_headers() @@ -61,16 +75,89 @@ class SocketHTTPServer(HTTPServer): fron the test runner. """ - def __init__(self, server_address, RequestHandlerClass, socket): + def __init__(self, server_address, RequestHandlerClass, socket, resources): BaseServer.__init__(self, server_address, RequestHandlerClass) self.socket = socket self.server_address = self.socket.getsockname() + self._resources = resources + + +def default_resources(): + ec2_macs = b"/2018-09-24/meta-data/network/interfaces/macs/" + + aliyun_meta = b"/2016-01-01/meta-data/" + aliyun_macs = aliyun_meta + b"network/interfaces/macs/" + + azure_meta = b"/metadata/instance" + azure_iface = azure_meta + b"/network/interface/" + azure_query = b"?format=text&api-version=2017-04-02" + + gcp_meta = b"/computeMetadata/v1/instance/" + gcp_iface = gcp_meta + b"network-interfaces/" + + mac1 = b"9e:c0:3e:92:24:2d" + mac2 = b"53:e9:7e:52:8d:a8" + + ip1 = b"172.31.26.249" + ip2 = b"172.31.176.249" + + return { + b"/latest/meta-data/": b"ami-id\n", + ec2_macs: mac2 + b"\n" + mac1, + ec2_macs + mac2 + b"/subnet-ipv4-cidr-block": b"172.31.16.0/20", + ec2_macs + mac2 + b"/local-ipv4s": ip1, + ec2_macs + mac1 + b"/subnet-ipv4-cidr-block": b"172.31.166.0/20", + ec2_macs + mac1 + b"/local-ipv4s": ip2, + aliyun_meta: b"ami-id\n", + aliyun_macs: mac2 + b"\n" + mac1, + aliyun_macs + mac2 + b"/vpc-cidr-block": b"172.31.16.0/20", + aliyun_macs + mac2 + b"/private-ipv4s": ip1, + aliyun_macs + mac2 + b"/primary-ip-address": ip1, + aliyun_macs + mac2 + b"/netmask": b"255.255.255.0", + aliyun_macs + mac2 + b"/gateway": b"172.31.26.2", + aliyun_macs + mac1 + b"/vpc-cidr-block": b"172.31.166.0/20", + aliyun_macs + mac1 + b"/private-ipv4s": ip2, + aliyun_macs + mac1 + b"/primary-ip-address": ip2, + aliyun_macs + mac1 + b"/netmask": b"255.255.255.0", + aliyun_macs + mac1 + b"/gateway": b"172.31.176.2", + azure_meta + azure_query: b"", + azure_iface + azure_query: b"0\n1\n", + azure_iface + b"0/macAddress" + azure_query: mac1, + azure_iface + b"1/macAddress" + azure_query: mac2, + azure_iface + b"0/ipv4/ipAddress/" + azure_query: b"0\n", + azure_iface + b"1/ipv4/ipAddress/" + azure_query: b"0\n", + azure_iface + b"0/ipv4/ipAddress/0/privateIpAddress" + azure_query: ip1, + azure_iface + b"1/ipv4/ipAddress/0/privateIpAddress" + azure_query: ip2, + azure_iface + b"0/ipv4/subnet/0/address/" + azure_query: b"172.31.16.0", + azure_iface + b"1/ipv4/subnet/0/address/" + azure_query: b"172.31.166.0", + azure_iface + b"0/ipv4/subnet/0/prefix/" + azure_query: b"20", + azure_iface + b"1/ipv4/subnet/0/prefix/" + azure_query: b"20", + gcp_meta + b"id": b"", + gcp_iface: b"0\n1\n", + gcp_iface + b"0/mac": mac1, + gcp_iface + b"1/mac": mac2, + gcp_iface + b"0/forwarded-ips/": b"0\n", + gcp_iface + b"0/forwarded-ips/0": ip1, + gcp_iface + b"1/forwarded-ips/": b"0\n", + gcp_iface + b"1/forwarded-ips/0": ip2, + } +resources = None +try: + if argv[1] == "--empty": + resources = {} +except IndexError: + pass +if resources is None: + resources = default_resources() + # See sd_listen_fds(3) -fileno = os.getenv("LISTEN_FD") +fileno = os.getenv("LISTEN_FDS") if fileno is not None: - s = socket.socket(fileno=int(fileno)) + if fileno != "1": + raise Exception("Bad LISTEN_FDS") + s = socket.socket(fileno=3) else: addr = ("localhost", 0) s = socket.socket() @@ -78,8 +165,7 @@ else: s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) s.bind(addr) - -httpd = SocketHTTPServer(None, MockCloudMDRequestHandler, socket=s) +httpd = SocketHTTPServer(None, MockCloudMDRequestHandler, socket=s, resources=resources) print("Listening on http://%s:%d" % (httpd.server_address[0], httpd.server_address[1])) httpd.server_activate() |