diff options
-rwxr-xr-x | tools/test-cloud-meta-mock.py | 257 |
1 files changed, 185 insertions, 72 deletions
diff --git a/tools/test-cloud-meta-mock.py b/tools/test-cloud-meta-mock.py index 4135745d11..f52e54a60f 100755 --- a/tools/test-cloud-meta-mock.py +++ b/tools/test-cloud-meta-mock.py @@ -17,13 +17,45 @@ import os import socket -from sys import argv +import sys from http.server import HTTPServer from http.server import BaseHTTPRequestHandler from socketserver import BaseServer +PROVIDERS = [ + "aliyun", + "azure", + "ec2", + "gcp", +] + + +def _s_to_bool(s): + s0 = s + if isinstance(s, bytes): + s = s.encode("utf-8", error="replace") + if isinstance(s, str): + s = s.lower() + if s in ["yes", "y", "true", "1"]: + return True + if s in ["no", "n", "false", "0"]: + return False + if isinstance(s, int): + if s in [0, 1]: + return s == 1 + raise ValueError(f'Not a boolean value ("{s0}")') + + +DEBUG = _s_to_bool(os.environ.get("NM_TEST_CLOUD_SETUP_MOCK_DEBUG", "0")) + + +def dbg(msg): + if DEBUG: + print("DBG: %s" % (msg,)) + + class MockCloudMDRequestHandler(BaseHTTPRequestHandler): """ Respond to cloud metadata service requests. @@ -33,32 +65,67 @@ class MockCloudMDRequestHandler(BaseHTTPRequestHandler): def log_message(self, format, *args): pass - def _response_and_end(self, code): + def _response_and_end(self, code, write=None): self.send_response(code) self.end_headers() + if write is None: + dbg("response %s" % (code,)) + else: + if isinstance(write, str): + write = write.encode("utf-8") + dbg("response %s, %s" % (code, write)) + self.wfile.write(write) + + def _read(self): + length = int(self.headers["content-length"]) + v = self.rfile.read(length) + dbg('receive "%s"' % (v,)) + return v def do_GET(self): path = self.path.encode("ascii") + dbg("GET %s" % (path,)) + r = None if path in self.server._resources: - self._response_and_end(200) - self.wfile.write(self.server._resources[path]) - else: + r = self.server._resources[path] + elif self.server.config_get_allow_default(): + for p in self.server.config_get_providers(): + if path in DEFAULT_RESOURCES[p]: + r = DEFAULT_RESOURCES[p][path] + break + if r is None: self._response_and_end(404) + return + self._response_and_end(200, write=r) def do_PUT(self): path = self.path.encode("ascii") - if path == b"/latest/api/token": - self._response_and_end(200) - self.wfile.write( - b"AQAAALH-k7i18JMkK-ORLZQfAa7nkNjQbKwpQPExNHqzk1oL_7eh-A==" - ) + dbg("PUT %s" % (path,)) + if path.startswith(b"/.nmtest/"): + conf_name = path[len(b"/.nmtest/") :] + v = self._read() + + self.server._config[conf_name] = v + + assert self.server.config_get_providers() is not None + assert self.server.config_get_allow_default() is not None + + self._response_and_end(201) + elif path == b"/latest/api/token": + if "ec2" not in self.server.config_get_providers(): + self._response_and_end(404) + else: + self._response_and_end( + 200, + write="AQAAALH-k7i18JMkK-ORLZQfAa7nkNjQbKwpQPExNHqzk1oL_7eh-A==", + ) else: - length = int(self.headers["content-length"]) - self.server._resources[path] = self.rfile.read(length) + self.server._resources[path] = self._read() self._response_and_end(201) def do_DELETE(self): path = self.path.encode("ascii") + dbg("DELETE %s" % (path,)) if path in self.server._resources: del self.server._resources[path] self._response_and_end(204) @@ -73,25 +140,35 @@ class SocketHTTPServer(HTTPServer): fron the test runner. """ - def __init__(self, server_address, RequestHandlerClass, socket, resources): + def __init__( + self, + server_address, + RequestHandlerClass, + socket, + resources=None, + allow_default=True, + ): BaseServer.__init__(self, server_address, RequestHandlerClass) self.socket = socket self.server_address = self.socket.getsockname() - self._resources = resources - + self._resources = resources or {} + self._config = { + "allow-default": "yes" if allow_default else "no", + } -def default_resources(): - ec2_macs = b"/2018-09-24/meta-data/network/interfaces/macs/" + def config_get_providers(self): + conf = self._config.get(b"providers", None) + if not conf: + return PROVIDERS + parsed = [s.lower() for s in conf.decode("utf-8", errors="replace").split(" ")] + assert all(p in PROVIDERS for p in parsed) + return parsed - aliyun_meta = b"/2016-01-01/meta-data/" - aliyun_macs = aliyun_meta + b"network/interfaces/macs/" + def config_get_allow_default(self): + return _s_to_bool(self._config.get(b"allow-default", "yes")) - 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/" +def create_default_resources_for_provider(provider): mac1 = b"cc:00:00:00:00:01" mac2 = b"cc:00:00:00:00:02" @@ -99,56 +176,87 @@ def default_resources(): 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 + if provider == "aliyun": + aliyun_meta = b"/2016-01-01/meta-data/" + aliyun_macs = aliyun_meta + b"network/interfaces/macs/" + return { + 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", + } + + if provider == "azure": + azure_meta = b"/metadata/instance" + azure_iface = azure_meta + b"/network/interface/" + azure_query = b"?format=text&api-version=2017-04-02" + return { + 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", + } + + if provider == "ec2": + ec2_macs = b"/2018-09-24/meta-data/network/interfaces/macs/" + 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, + }, + ) + + if provider == "gcp": + gcp_meta = b"/computeMetadata/v1/instance/" + gcp_iface = gcp_meta + b"network-interfaces/" + return { + 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, + } + + raise ValueError("invalid provider %s" % (provider,)) + + +def create_default_resources(): + + return {p: create_default_resources_for_provider(p) for p in PROVIDERS} + + +DEFAULT_RESOURCES = create_default_resources() + + +allow_default = True try: - if argv[1] == "--empty": - resources = {} + if sys.argv[1] == "--empty": + allow_default = False except IndexError: pass -if resources is None: - resources = default_resources() # See sd_listen_fds(3) fileno = os.getenv("LISTEN_FDS") @@ -163,7 +271,12 @@ else: s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) s.bind(addr) -httpd = SocketHTTPServer(None, MockCloudMDRequestHandler, socket=s, resources=resources) +httpd = SocketHTTPServer( + None, + MockCloudMDRequestHandler, + socket=s, + allow_default=allow_default, +) print("Listening on http://%s:%d" % (httpd.server_address[0], httpd.server_address[1])) httpd.server_activate() |