cloud-setup: merge branch 'lr/more-cloud-setup-tests'

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/1606
This commit is contained in:
Thomas Haller
2023-05-12 12:45:16 +02:00
8 changed files with 477 additions and 49 deletions

View File

@@ -21,6 +21,13 @@
#define nm_auto_unref_ip_address nm_auto(_nm_ip_address_unref) #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); 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) #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); NM_AUTO_DEFINE_FCN0(NMIPRoute *, _nm_auto_unref_ip_route, nm_ip_route_unref);

View File

@@ -315,7 +315,8 @@ _nmc_mangle_connection(NMDevice *device,
addrs_new = g_ptr_array_new_full(config_data->ipv4s_len, (GDestroyNotify) nm_ip_address_unref); addrs_new = g_ptr_array_new_full(config_data->ipv4s_len, (GDestroyNotify) nm_ip_address_unref);
rules_new = rules_new =
g_ptr_array_new_full(config_data->ipv4s_len, (GDestroyNotify) nm_ip_routing_rule_unref); 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, routes_new =
g_ptr_array_new_full(nm_g_ptr_array_len(config_data->iproutes) + !!config_data->ipv4s_len,
(GDestroyNotify) nm_ip_route_unref); (GDestroyNotify) nm_ip_route_unref);
if (remote_s_ip) { if (remote_s_ip) {
@@ -422,8 +423,8 @@ _nmc_mangle_connection(NMDevice *device,
} }
} }
for (i = 0; i < config_data->iproutes_len; ++i) for (i = 0; i < nm_g_ptr_array_len(config_data->iproutes); i++)
g_ptr_array_add(routes_new, config_data->iproutes_arr[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, addrs_changed = nmcs_setting_ip_replace_ipv4_addresses(s_ip,
(NMIPAddress **) addrs_new->pdata, (NMIPAddress **) addrs_new->pdata,

View File

@@ -17,8 +17,30 @@
#define NM_AZURE_METADATA_URL_BASE /* $NM_AZURE_BASE/$NM_AZURE_API_VERSION */ \ #define NM_AZURE_METADATA_URL_BASE /* $NM_AZURE_BASE/$NM_AZURE_API_VERSION */ \
"/metadata/instance/network/interface/" "/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(...) \ #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__) #define _azure_uri_interfaces(...) _azure_uri_concat(NM_AZURE_METADATA_URL_BASE, ##__VA_ARGS__)
/*****************************************************************************/ /*****************************************************************************/

View File

@@ -16,12 +16,36 @@
#define NM_GCP_HOST "metadata.google.internal" #define NM_GCP_HOST "metadata.google.internal"
#define NM_GCP_BASE "http://" NM_GCP_HOST #define NM_GCP_BASE "http://" NM_GCP_HOST
#define NM_GCP_API_VERSION "/v1" #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_METADATA_URL_NET "/network-interfaces/"
#define NM_GCP_METADATA_HEADER "Metadata-Flavor: Google" #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__) #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); http_client = nmcs_provider_get_http_client(provider);
nm_http_client_poll_req(http_client, nm_http_client_poll_req(http_client,
(uri = _gcp_uri_concat("id")), (uri = _gcp_uri_concat("/id")),
HTTP_TIMEOUT_MS, HTTP_TIMEOUT_MS,
256 * 1024, 256 * 1024,
7000, 7000,
@@ -112,7 +136,6 @@ _get_config_fip_cb(GObject *source, GAsyncResult *result, gpointer user_data)
GCPIfaceData *iface_data = user_data; GCPIfaceData *iface_data = user_data;
gs_free_error GError *error = NULL; gs_free_error GError *error = NULL;
gs_free char *ipaddr = NULL; gs_free char *ipaddr = NULL;
NMIPRoute **routes_arr;
NMIPRoute *route_new; NMIPRoute *route_new;
nm_http_client_poll_req_finish(NM_HTTP_CLIENT(source), result, NULL, &response, &error); 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); ipaddr);
iface_get_config = iface_data->iface_get_config; 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); route_new = nm_ip_route_new(AF_INET, ipaddr, 32, NULL, 100, &error);
if (error) if (error)
goto out_done; goto out_done;
nm_ip_route_set_attribute(route_new, NM_IP_ROUTE_ATTRIBUTE_TYPE, g_variant_new_string("local")); 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: out_done:
if (!error) { if (!error) {
@@ -215,7 +237,8 @@ _get_config_ips_list_cb(GObject *source, GAsyncResult *result, gpointer user_dat
goto out_error; 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) { for (i = 0; i < uri_arr->len; ++i) {
const char *str = uri_arr->pdata[i]; const char *str = uri_arr->pdata[i];

View File

@@ -216,7 +216,7 @@ _iface_data_free(gpointer data)
NMCSProviderGetConfigIfaceData *iface_data = data; NMCSProviderGetConfigIfaceData *iface_data = data;
g_free(iface_data->ipv4s_arr); 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); g_free((char *) iface_data->hwaddr);
nm_g_slice_free(iface_data); nm_g_slice_free(iface_data);

View File

@@ -34,8 +34,8 @@ typedef struct {
bool has_cidr : 1; bool has_cidr : 1;
bool has_gateway : 1; bool has_gateway : 1;
NMIPRoute **iproutes_arr; /* Array of NMIPRoute (must own/free the entries). */
gsize iproutes_len; GPtrArray *iproutes;
/* TRUE, if the configuration was requested via hwaddrs argument to /* TRUE, if the configuration was requested via hwaddrs argument to
* nmcs_provider_get_config(). */ * nmcs_provider_get_config(). */
@@ -59,7 +59,8 @@ static inline gboolean
nmcs_provider_get_config_iface_data_is_valid(const NMCSProviderGetConfigIfaceData *config_data) nmcs_provider_get_config_iface_data_is_valid(const NMCSProviderGetConfigIfaceData *config_data)
{ {
return config_data && config_data->iface_idx >= 0 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);
} }
/*****************************************************************************/ /*****************************************************************************/

View File

@@ -147,6 +147,7 @@ except ImportError:
try: try:
from http.server import HTTPServer from http.server import HTTPServer
from http.server import BaseHTTPRequestHandler from http.server import BaseHTTPRequestHandler
from http.client import HTTPConnection, HTTPResponse
except ImportError: except ImportError:
HTTPServer = None HTTPServer = None
@@ -1009,6 +1010,8 @@ class TestNmClient(unittest.TestCase):
pexp = pexpect.spawn(argv[0], argv[1:], timeout=10, env=env) pexp = pexpect.spawn(argv[0], argv[1:], timeout=10, env=env)
pexp.str_last_chars = 100000
typ = collections.namedtuple("CallPexpect", ["pexp", "valgrind_log"]) typ = collections.namedtuple("CallPexpect", ["pexp", "valgrind_log"])
return typ(pexp, valgrind_log) return typ(pexp, valgrind_log)
@@ -2137,6 +2140,13 @@ class TestNmcli(TestNmClient):
class TestNmCloudSetup(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): def cloud_setup_test(func):
""" """
Runs the mock NetworkManager along with a mock cloud metadata service. Runs the mock NetworkManager along with a mock cloud metadata service.
@@ -2160,34 +2170,47 @@ class TestNmCloudSetup(TestNmClient):
# hallucinogenic substances. # hallucinogenic substances.
s.listen(5) s.listen(5)
def pass_socket():
os.dup2(s.fileno(), 3)
service_path = PathConfiguration.test_cloud_meta_mock_path() service_path = PathConfiguration.test_cloud_meta_mock_path()
env = os.environ.copy() env = os.environ.copy()
env["LISTEN_FD"] = str(s.fileno()) env["LISTEN_FDS"] = "1"
p = subprocess.Popen( p = subprocess.Popen(
[sys.executable, service_path], [sys.executable, service_path, "--empty"],
stdin=subprocess.PIPE, stdin=subprocess.PIPE,
env=env, 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() s.close()
error = None
self.srv_start() self.srv_start()
try:
func(self) func(self)
except Exception as e:
error = e
self._nm_test_post() self._nm_test_post()
self.md_conn.close()
p.stdin.close() p.stdin.close()
p.terminate() p.terminate()
p.wait() p.wait()
if error:
raise error
return f return f
@cloud_setup_test def _mock_devices(self):
def test_ec2(self):
# Add a device with an active connection that has IPv4 configured # 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( self.srv.addAndActivateConnection(
{ {
"connection": {"type": "802-3-ethernet", "id": "con-eth0"}, "connection": {"type": "802-3-ethernet", "id": "con-eth0"},
@@ -2198,7 +2221,7 @@ class TestNmCloudSetup(TestNmClient):
) )
# The second connection has no IPv4 # 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( self.srv.addAndActivateConnection(
{"connection": {"type": "802-3-ethernet", "id": "con-eth1"}}, {"connection": {"type": "802-3-ethernet", "id": "con-eth1"}},
"/org/freedesktop/NetworkManager/Devices/2", "/org/freedesktop/NetworkManager/Devices/2",
@@ -2206,6 +2229,210 @@ class TestNmCloudSetup(TestNmClient):
delay=0, 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 # Run nm-cloud-setup for the first time
nmc = self.call_pexpect( nmc = self.call_pexpect(
ENV_NM_TEST_CLIENT_CLOUD_SETUP_PATH, ENV_NM_TEST_CLIENT_CLOUD_SETUP_PATH,
@@ -2251,6 +2478,67 @@ class TestNmCloudSetup(TestNmClient):
Util.valgrind_check_log(nmc.valgrind_log, "test_ec2") 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")
############################################################################### ###############################################################################

View File

@@ -1,13 +1,23 @@
#!/usr/bin/env python #!/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 & # run: $ systemd-socket-activate -l 8000 python tools/test-cloud-meta-mock.py &
# $ NM_CLOUD_SETUP_EC2_HOST=http://localhost:8000 \ # $ NM_CLOUD_SETUP_EC2_HOST=http://localhost:8000 \
# NM_CLOUD_SETUP_LOG=trace \ # NM_CLOUD_SETUP_LOG=trace \
# NM_CLOUD_SETUP_EC2=yes src/nm-cloud-setup/nm-cloud-setup # NM_CLOUD_SETUP_EC2=yes src/nm-cloud-setup/nm-cloud-setup
# or just: $ python tools/test-cloud-meta-mock.py # 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 os
import socket import socket
from sys import argv
from http.server import HTTPServer from http.server import HTTPServer
from http.server import BaseHTTPRequestHandler from http.server import BaseHTTPRequestHandler
@@ -20,35 +30,39 @@ class MockCloudMDRequestHandler(BaseHTTPRequestHandler):
Currently implements a fairly minimal subset of AWS EC2 API. 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): def log_message(self, format, *args):
pass pass
def do_GET(self): 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.send_response(200)
self.end_headers() self.end_headers()
self.wfile.write(self._meta_resources[self.path]) self.wfile.write(self.server._resources[path])
else: else:
self.send_response(404) self.send_response(404)
self.end_headers() self.end_headers()
def do_PUT(self): 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.send_response(200)
self.end_headers() self.end_headers()
self.wfile.write( self.wfile.write(
b"AQAAALH-k7i18JMkK-ORLZQfAa7nkNjQbKwpQPExNHqzk1oL_7eh-A==" 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: else:
self.send_response(404) self.send_response(404)
self.end_headers() self.end_headers()
@@ -61,16 +75,89 @@ class SocketHTTPServer(HTTPServer):
fron the test runner. 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) BaseServer.__init__(self, server_address, RequestHandlerClass)
self.socket = socket self.socket = socket
self.server_address = self.socket.getsockname() 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) # See sd_listen_fds(3)
fileno = os.getenv("LISTEN_FD") fileno = os.getenv("LISTEN_FDS")
if fileno is not None: 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: else:
addr = ("localhost", 0) addr = ("localhost", 0)
s = socket.socket() s = socket.socket()
@@ -78,8 +165,7 @@ else:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1)
s.bind(addr) s.bind(addr)
httpd = SocketHTTPServer(None, MockCloudMDRequestHandler, socket=s, resources=resources)
httpd = SocketHTTPServer(None, MockCloudMDRequestHandler, socket=s)
print("Listening on http://%s:%d" % (httpd.server_address[0], httpd.server_address[1])) print("Listening on http://%s:%d" % (httpd.server_address[0], httpd.server_address[1]))
httpd.server_activate() httpd.server_activate()