diff options
| author | Michael Biebl <biebl@debian.org> | 2023-08-09 21:55:35 +0200 |
|---|---|---|
| committer | Michael Biebl <biebl@debian.org> | 2023-08-09 21:55:35 +0200 |
| commit | 05e4a733f2141995181a551854d5df929f084adf (patch) | |
| tree | 83bb937740a6667525ba0df046748ecaa829c269 /tools | |
| parent | 14b0f3a9dc9ea90d60a3b057350fd4d637dc021a (diff) | |
New upstream version 1.44.0 upstream/1.44.0
Diffstat (limited to 'tools')
| -rwxr-xr-x | tools/check-docs.sh | 2 | ||||
| -rwxr-xr-x | tools/check-nm-autoptr.sh | 26 | ||||
| -rwxr-xr-x | tools/check-vapi.sh | 25 | ||||
| -rwxr-xr-x | tools/create-exports-NetworkManager.sh | 2 | ||||
| -rwxr-xr-x | tools/generate-docs-nm-property-infos.py | 23 | ||||
| -rwxr-xr-x | tools/generate-docs-nm-settings-docs-gir.py | 17 | ||||
| -rwxr-xr-x | tools/generate-docs-nm-settings-docs-merge.py | 171 | ||||
| -rwxr-xr-x | tools/meson-post-install.sh | 1 | ||||
| -rwxr-xr-x | tools/test-cloud-meta-mock.py | 285 | ||||
| -rwxr-xr-x | tools/test-networkmanager-service.py | 90 |
10 files changed, 569 insertions, 73 deletions
diff --git a/tools/check-docs.sh b/tools/check-docs.sh index 2746848c..b0fd3131 100755 --- a/tools/check-docs.sh +++ b/tools/check-docs.sh @@ -39,7 +39,7 @@ libnm_headers() { SOURCEDIR="$1" BUILDDIR="$2" -if test "$SOURCEDIR" == "$BUILDDIR"; then +if test "$SOURCEDIR" = "$BUILDDIR"; then BUILDDIR= fi [ -z "$SOURCEDIR" ] && SOURCEDIR='.' diff --git a/tools/check-nm-autoptr.sh b/tools/check-nm-autoptr.sh new file mode 100755 index 00000000..466958c8 --- /dev/null +++ b/tools/check-nm-autoptr.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +set -e + +die() { + printf "%s\n" "$*" >&2 + exit 1 +} + +cd "$(dirname "$(readlink -f "$0")")/.." || die "cannot change to srcdir" + +AUTOPTR_H=./src/libnm-client-public/nm-autoptr.h + +for s in $( sed -n 's/^ *typedef \+struct \+.*[A-Za-z0-9_]\+ \+\(NMSetting[A-Za-z0-9_]\+\)\> *;$/\1/p' ./src/libnm-core-public/nm-core-types.h ) ; do + grep -q "^ *G_DEFINE_AUTOPTR_CLEANUP_FUNC *( *\\<$s\\> *, *g_object_unref *)" -- "$AUTOPTR_H" || die "didn't see setting '$s' in \"$AUTOPTR_H\"" +done + +for s in $( grep -h -o '\<NMDevice[A-Za-z0-9_]\+\>' ./src/libnm-client-public/nm-device-*.h | sort -u | grep -v 'Class$') ; do + case "$s" in + NMDeviceModemCapabilities| \ + NMDeviceWifiCapabilities) + continue + ;; + esac + grep -q "^ *G_DEFINE_AUTOPTR_CLEANUP_FUNC *( *\\<$s\\> *, *g_object_unref *)" -- "$AUTOPTR_H" || die "didn't see device '$s' in \"$AUTOPTR_H\"" +done diff --git a/tools/check-vapi.sh b/tools/check-vapi.sh new file mode 100755 index 00000000..da640cfd --- /dev/null +++ b/tools/check-vapi.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +set -e + +die() { + printf "%s\n" "$*" >&2 + exit 1 +} + +cd "$(dirname "$(readlink -f "$0")")/.." || die "cannot change to srcdir" + +VAPI=./vapi/NM-1.0.metadata + +for s in $(grep -r -h '#define \+\<NM_SETTING_.*SETTING_NAME\>' -- ./src/libnm-core-public/ \ + | sed -n 's/^#define \+NM_\(SETTING_[A-Z0-9_]\+\)_SETTING_NAME\> \+.*/\1/p') ; do + grep -q "^$s" -- "$VAPI" || die "didn't see '$s' in \"$VAPI\"" +done + +for f in ./src/libnm-client-public/nm-device-*.h ; do + D=( $(sed -n 's/^#define \+NM_IS_DEVICE_\([A-Z0-9_]\+\)_CLASS\>(.*/\1/p' "$f") ) + test ${#D[@]} = 1 || die "did not detect device in \"$f\"" + s="${D[0]}" + c="$(grep -c "^DEVICE_${s}_\* *parent=" -- "$VAPI")" + test "$c" = 1 || die "didn't see device '$s' in \"$VAPI\"" +done diff --git a/tools/create-exports-NetworkManager.sh b/tools/create-exports-NetworkManager.sh index 21ba369a..ef4b381a 100755 --- a/tools/create-exports-NetworkManager.sh +++ b/tools/create-exports-NetworkManager.sh @@ -71,7 +71,7 @@ get_symbols_missing() { } pretty() { - sed 's/.*/\t\0;/' + sed 's/.*/\t&;/' } do_build() { diff --git a/tools/generate-docs-nm-property-infos.py b/tools/generate-docs-nm-property-infos.py index 58ad751f..d7e3c8c2 100755 --- a/tools/generate-docs-nm-property-infos.py +++ b/tools/generate-docs-nm-property-infos.py @@ -116,7 +116,7 @@ def get_file_infos(source_files): KEYWORD_XML_TYPE_NESTED = "nested" -KEYWORD_XML_TYPE_NODE = "node" +KEYWORD_XML_TYPE_ELEM = "elem" KEYWORD_XML_TYPE_ATTR = "attr" keywords = collections.OrderedDict( @@ -127,7 +127,7 @@ keywords = collections.OrderedDict( ("values", KEYWORD_XML_TYPE_ATTR), ("default", KEYWORD_XML_TYPE_ATTR), ("example", KEYWORD_XML_TYPE_ATTR), - ("description", KEYWORD_XML_TYPE_ATTR), + ("description", KEYWORD_XML_TYPE_ELEM), ("description-docbook", KEYWORD_XML_TYPE_NESTED), ] ) @@ -163,7 +163,7 @@ def write_data(tag, setting_node, line_no, parsed_data): # Set as XML nodes. The input data is XML itself. des = ET.fromstring("<%s>%s</%s>" % (k, v, k)) property_node.append(des) - elif xmltype == KEYWORD_XML_TYPE_NODE: + elif xmltype == KEYWORD_XML_TYPE_ELEM: node = ET.SubElement(property_node, k) node.text = v elif xmltype == KEYWORD_XML_TYPE_ATTR: @@ -171,6 +171,23 @@ def write_data(tag, setting_node, line_no, parsed_data): else: assert False + if ( + parsed_data.get("description", None) is not None + and parsed_data.get("description-docbook", None) is None + ): + # we have a description, but no docbook. Generate one. + node = ET.SubElement(property_node, "description-docbook") + for l in re.split("\n", parsed_data["description"]): + paragraph = ET.SubElement(node, "para") + paragraph.text = l + elif ( + parsed_data.get("description-docbook", None) is not None + and parsed_data.get("description", None) is None + ): + raise Exception( + 'Invalid configuration. When specifying "description-docbook:" there MUST be also a "description:"' + ) + kwd_first_line_re = re.compile(r"^ *\* ([-a-z0-9]+): (.*)$") kwd_more_line_re = re.compile(r"^ *\*( *)(.*?)\s*$") diff --git a/tools/generate-docs-nm-settings-docs-gir.py b/tools/generate-docs-nm-settings-docs-gir.py index 88b87a72..b8f29d3b 100755 --- a/tools/generate-docs-nm-settings-docs-gir.py +++ b/tools/generate-docs-nm-settings-docs-gir.py @@ -203,7 +203,6 @@ def settings_sort_key(x): def create_desc_docbook(desc_docbook, description): lines = re.split("\n", description) - in_par = True paragraph = ET.SubElement( desc_docbook, @@ -211,10 +210,11 @@ def create_desc_docbook(desc_docbook, description): ) for l in lines: - if l and in_par: - paragraph.text = l - else: + if not l: + # A blank line. This starts a new paragraph paragraph = ET.SubElement(desc_docbook, "para") + continue + paragraph.text = l def main(gir_path_str, output_path_str): @@ -328,12 +328,9 @@ def main(gir_path_str, output_path_str): }, ).text = deprecated_desc - deprecated_docbook = ET.SubElement( - property_element, - "deprecated-docbook", - ) - - create_desc_docbook(deprecated_docbook, deprecated_desc) + # The text should only be one line. Otherwise, our simple "<deprecated>" element + # cannot be rendered nicely. + assert re.split("\n", deprecated_desc) == [deprecated_desc] docs_gir.write( output_path_str, diff --git a/tools/generate-docs-nm-settings-docs-merge.py b/tools/generate-docs-nm-settings-docs-merge.py index 0131a024..a927400f 100755 --- a/tools/generate-docs-nm-settings-docs-merge.py +++ b/tools/generate-docs-nm-settings-docs-merge.py @@ -4,15 +4,19 @@ from __future__ import print_function import collections +import os import sys import xml.etree.ElementTree as ET ############################################################################### +DEBUG = os.environ.get("NM_GENERATE_DOCS_NM_SETTINGS_DOCS_MERGE_DEBUG", None) == "1" + + def dbg(msg): - pass - # print("%s" % (msg,)) + if DEBUG: + print("%s" % (msg,)) ############################################################################### @@ -113,40 +117,135 @@ def node_set_attr(dst_node, name, nodes): def find_attr(properties_attrs, name): for p_attr in properties_attrs: - if p_attr is not None: - p_attr = p_attr.find(name) + if p_attr is None: + continue + p_attr = p_attr.find(name) if p_attr is not None: return p_attr +def find_description(properties_attrs): + for p in properties_attrs: + if p is None: + continue + + # These are not attributes, but XML element. + assert p.get("description", None) is None + assert p.get("description-docbook", None) is None + + p_elem = p.find("description") + p_elem_docbook = p.find("description-docbook") + + if p_elem is not None or p_elem_docbook is not None: + if p_elem is None or p_elem_docbook is None: + # invalid input! + if p_elem: + s = ET.tostring(p_elem) + else: + s = ET.tostring(p_elem_docbook) + raise Exception( + "We expect both a <description> and <description-docbook> tag, but we only have %s" + % (s,) + ) + return p_elem, p_elem_docbook + + return None, None + + +def find_deprecated(properties_attrs): + for p in properties_attrs: + if p is None: + continue + + # These are not attributes, but XML element. + assert p.get("deprecated", None) is None + assert p.get("deprecated-docbook", None) is None + + # We don't expect a <deprecated-docbook> tag. + assert p.find("deprecated-docbook") is None + + p_elem = p.find("deprecated") + + if p_elem is not None: + # We require a "since" attribute + assert p_elem.get("since", None) is not None + return p_elem + + return None + + ############################################################################### gl_only_from_first = False -argv = list(sys.argv[1:]) -while True: - if argv[0] == "--only-from-first": - gl_only_from_first = True - del argv[0] - continue - break -if len(argv) < 2: - print("%s [--only-from-first] [OUT_FILE] [SETTING_XML [...]]" % (sys.argv[0])) - exit(1) - -gl_output_xml_file = argv[0] -gl_input_files = list(argv[1:]) +gl_only_properties_from = None +gl_output_xml_file = None +gl_input_files = [] + + +def usage_and_quit(exit_code): + print( + "%s [--only-properties-from SLECTOR_FILE] [OUT_FILE] [SETTING_XML [...]]" + % (sys.argv[0]) + ) + exit(exit_code) + + +i = 1 +special_args = True +while i < len(sys.argv): + if special_args and sys.argv[i] in ["-h", "--help"]: + usage_and_quit(0) + elif special_args and sys.argv[i] == "--only-properties-from": + i += 1 + gl_only_properties_from = sys.argv[i] + elif special_args and sys.argv[i] == "--": + special_args = False + elif gl_output_xml_file is None: + gl_output_xml_file = sys.argv[i] + else: + gl_input_files.append(sys.argv[i]) + i += 1 +if len(gl_input_files) < 2: + usage_and_quit(1) ############################################################################### for f in gl_input_files: dbg("> input file %s" % (f)) -xml_roots = list([ET.parse(f).getroot() for f in gl_input_files]) +xml_roots = [ET.parse(f).getroot() for f in gl_input_files] assert all([root.tag == "nm-setting-docs" for root in xml_roots]) -settings_roots = list([node_to_dict(root, "setting", "name") for root in xml_roots]) + +def skip_property(setting_name, property_name): + return False + + +if gl_only_properties_from: + xml_root = ET.parse(gl_only_properties_from).getroot() + opf_setting_root = node_to_dict(xml_root, "setting", "name") + opf_cache = {} + + def skip_property(setting_name, property_name): + if setting_name not in opf_cache: + s = opf_setting_root.get(setting_name) + if s is not None: + s = node_to_dict(s, "property", "name") + opf_cache[setting_name] = s + else: + s = opf_cache[setting_name] + if not s: + return True + if property_name is not None: + p = s.get(property_name) + if p is None: + return True + return False + + +settings_roots = [node_to_dict(root, "setting", "name") for root in xml_roots] root_node = ET.Element("nm-setting-docs") @@ -154,17 +253,13 @@ for setting_name in iter_keys_of_dicts(settings_roots, key_fcn_setting_name): dbg("> > setting_name: %s" % (setting_name)) - settings = list([d.get(setting_name) for d in settings_roots]) - - if gl_only_from_first and settings[0] is None: - dbg("> > > skip (only-from-first") + if skip_property(setting_name, None): + dbg("> > > skip (only-properties-from)") continue - properties = list([node_to_dict(s, "property", "name") for s in settings]) + settings = [d.get(setting_name) for d in settings_roots] - if gl_only_from_first and not properties[0]: - dbg("> > > skip (no properties") - continue + properties = [node_to_dict(s, "property", "name") for s in settings] setting_node = ET.SubElement(root_node, "setting") @@ -180,14 +275,13 @@ for setting_name in iter_keys_of_dicts(settings_roots, key_fcn_setting_name): dbg("> > > > property_name: %s" % (property_name)) - properties_attrs = list([p.get(property_name) for p in properties]) - description_docbook = find_attr(properties_attrs, "description-docbook") - description = find_attr(properties_attrs, "description") - deprecated_docbook = find_attr(properties_attrs, "deprecated-docbook") - deprecated = find_attr(properties_attrs, "deprecated") + properties_attrs = [p.get(property_name) for p in properties] - if gl_only_from_first and properties_attrs[0] is None: - dbg("> > > > skip (only-from-first") + description, description_docbook = find_description(properties_attrs) + deprecated = find_deprecated(properties_attrs) + + if skip_property(setting_name, property_name): + dbg("> > > > skip (only-properties-from)") continue property_node = ET.SubElement(setting_node, "property") @@ -203,20 +297,13 @@ for setting_name in iter_keys_of_dicts(settings_roots, key_fcn_setting_name): node_set_attr(property_node, "type", properties_attrs) node_set_attr(property_node, "default", properties_attrs) - desc_value = node_get_attr(properties_attrs, "description") node_set_attr(property_node, "alias", properties_attrs) if description_docbook is not None: property_node.insert(0, description_docbook) - if desc_value: - description = ET.Element("description") - description.text = desc_value - property_node.append(description) - elif description is not None: + if description is not None: property_node.append(description) - if deprecated_docbook is not None: - property_node.insert(0, deprecated_docbook) if deprecated is not None: property_node.insert(0, deprecated) diff --git a/tools/meson-post-install.sh b/tools/meson-post-install.sh index 45f3e37c..fe85d417 100755 --- a/tools/meson-post-install.sh +++ b/tools/meson-post-install.sh @@ -55,6 +55,7 @@ if [ "$enable_ifcfg_rh" = 1 ]; then fi if [ "$enable_nm_cloud_setup" = 1 -a "$install_systemdunitdir" = 1 ]; then + ln -sfn '../pre-up.d/90-nm-cloud-setup.sh' "${DESTDIR}${nm_pkglibdir}/dispatcher.d/no-wait.d/90-nm-cloud-setup.sh" ln -sfn 'no-wait.d/90-nm-cloud-setup.sh' "${DESTDIR}${nm_pkglibdir}/dispatcher.d/90-nm-cloud-setup.sh" fi diff --git a/tools/test-cloud-meta-mock.py b/tools/test-cloud-meta-mock.py new file mode 100755 index 00000000..bc0a829e --- /dev/null +++ b/tools/test-cloud-meta-mock.py @@ -0,0 +1,285 @@ +#!/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 +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", errors="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('Not a boolean value ("%s")' % (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. + Currently implements a fairly minimal subset of AWS EC2 API. + """ + + def log_message(self, format, *args): + pass + + 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: + 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") + 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: + 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) + else: + self._response_and_end(404) + + +class SocketHTTPServer(HTTPServer): + """ + A HTTP server that accepts a socket (that has already been + listen()-ed on). This is useful when the socket is passed + fron the test runner. + """ + + 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 or {} + self._config = { + "allow-default": "yes" if allow_default else "no", + } + + 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 + + def config_get_allow_default(self): + return _s_to_bool(self._config.get(b"allow-default", "yes")) + + +def create_default_resources_for_provider(provider): + + mac1 = b"cc:00:00:00:00:01" + mac2 = b"cc:00:00:00:00:02" + + ip1 = b"172.31.26.249" + ip2 = b"172.31.176.249" + + 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 sys.argv[1] == "--empty": + allow_default = False +except IndexError: + pass + +# See sd_listen_fds(3) +fileno = os.getenv("LISTEN_FDS") +if fileno is not None: + if fileno != "1": + raise Exception("Bad LISTEN_FDS") + s = socket.socket(fileno=3) +else: + addr = ("localhost", 0) + s = socket.socket() + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + s.bind(addr) + +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() + +httpd.serve_forever() +httpd.server_close() diff --git a/tools/test-networkmanager-service.py b/tools/test-networkmanager-service.py index a7788510..9a423123 100755 --- a/tools/test-networkmanager-service.py +++ b/tools/test-networkmanager-service.py @@ -288,8 +288,15 @@ class Util: return (Util.ip_addr_ntop(a, family), family) @staticmethod - def eprint(*args, **kwargs): - print(*args, file=sys.stderr, **kwargs) + def log(message): + if gl.log_file: + try: + gl.log_file.write(message + "\n") + gl.log_file.flush() + except Exception: + pass + else: + print(message, file=sys.stderr) @staticmethod def variant_from_dbus(val): @@ -308,9 +315,9 @@ class Util: if isinstance(val, dbus.Array): try: if val.signature == "s": - return GLib.Variant("as", [Util.variant_from_dbus(x) for x in val]) + return GLib.Variant("as", [str(x) for x in val]) if val.signature == "b": - return GLib.Variant("ab", [Util.variant_from_dbus(x) for x in val]) + return GLib.Variant("ab", [bool(x) for x in val]) if val.signature == "y": return GLib.Variant("ay", [int(x) for x in val]) if val.signature == "u": @@ -342,8 +349,7 @@ class Util: ) except Exception as e: raise Exception( - "Cannot convert array element to type '%s': %s" - % (val.signature, e.message) + "Cannot convert array element to type '%s': %s" % (val.signature, e) ) if isinstance(val, dbus.Dictionary): if val.signature == "ss": @@ -358,7 +364,7 @@ class Util: [(str(k), Util.variant_from_dbus(v)) for k, v in val.items()] ), ) - if val.signature == "sa{sv}": + if val.signature == "sa{sv}" or val.signature == "sa{ss}": c = collections.OrderedDict( [ ( @@ -825,10 +831,12 @@ class Device(ExportedObj): ExportedObj.__init__(self, ExportedObj.create_path(Device), ident) + self.applied_con = {} self.ip4_config = None self.ip6_config = None self.dhcp4_config = None self.dhcp6_config = None + self.activation_state_change_delay_ms = 50 self.prp_state = NM.DeviceState.UNAVAILABLE @@ -1037,6 +1045,20 @@ class Device(ExportedObj): def Disconnect(self): pass + @dbus.service.method( + dbus_interface=IFACE_DEVICE, in_signature="u", out_signature="a{sa{sv}}t" + ) + def GetAppliedConnection(self, flags): + ac = self._dbus_property_get(IFACE_DEVICE, PRP_DEVICE_ACTIVE_CONNECTION) + return (self.applied_con, 0) + + @dbus.service.method( + dbus_interface=IFACE_DEVICE, in_signature="a{sa{sv}}tu", out_signature="" + ) + def Reapply(self, connection, version_id, flags): + self.applied_con = connection + pass + @dbus.service.method(dbus_interface=IFACE_DEVICE, in_signature="", out_signature="") def Delete(self): # We don't currently support any software device types, so... @@ -1067,6 +1089,10 @@ class Device(ExportedObj): def set_active_connection(self, ac): self._dbus_property_set(IFACE_DEVICE, PRP_DEVICE_ACTIVE_CONNECTION, ac) + if ac is None: + self.applied_con = {} + else: + self.applied_con = ac.con_inst.con_hash def connection_is_available(self, con_inst): if con_inst.is_vpn(): @@ -1388,9 +1414,9 @@ class ActiveConnection(ExportedObj): self.con_inst = con_inst self.is_vpn = con_inst.is_vpn() + self.activation_state_change_delay_ms = device.activation_state_change_delay_ms self._activation_id = None self._deactivation_id = None - self.activation_state_change_delay_ms = 50 s_con = con_inst.con_hash[NM.SETTING_CONNECTION_SETTING_NAME] @@ -1486,9 +1512,17 @@ class ActiveConnection(ExportedObj): def start_activation(self): assert self._activation_id is None - self._activation_id = GLib.timeout_add( - self.activation_state_change_delay_ms, self._activation_step1 - ) + if self.activation_state_change_delay_ms == 0: + self.device.set_active_connection(self) + self._set_state( + NM.ActiveConnectionState.ACTIVATED, + NM.ActiveConnectionStateReason.UNKNOWN, + ) + self.device.set_state(NM.DeviceState.ACTIVATED, NM.DeviceStateReason.NONE) + else: + self._activation_id = GLib.timeout_add( + self.activation_state_change_delay_ms, self._activation_step1 + ) def start_deactivation(self): assert self._deactivation_id is None @@ -1553,7 +1587,9 @@ class NetworkManager(ExportedObj): PRP_NM_ACTIVATING_CONNECTION: ExportedObj.to_path(None), PRP_NM_STARTUP: False, PRP_NM_STATE: dbus.UInt32(NM.State.DISCONNECTED), - PRP_NM_VERSION: "0.9.9.0", + PRP_NM_VERSION: os.environ.get( + "NM_TEST_NETWORKMANAGER_SERVICE_VERSION", "0.9.9.0" + ), PRP_NM_CONNECTIVITY: dbus.UInt32(NM.ConnectivityState.NONE), } @@ -1918,9 +1954,9 @@ class NetworkManager(ExportedObj): @dbus.service.method(dbus_interface=IFACE_TEST, in_signature="ou", out_signature="") def SetActiveConnectionStateChangedDelay(self, devpath, delay_ms): - for ac in reversed(self.active_connections): - if ac.device.path == devpath: - ac.activation_state_change_delay_ms = delay_ms + for d in self.devices: + if d.path == devpath: + d.activation_state_change_delay_ms = delay_ms return raise BusErr.UnknownDeviceException( "Device with iface '%s' not found" % devpath @@ -2856,6 +2892,26 @@ class ObjectManager(dbus.service.Object): return managed_objects +def setup_log_file(): + """ + Get environment variable for the log file , if any + We accept a %p placeholder to replace with the pid of the current + process + """ + try: + log_file_name = os.environ["NM_TEST_NETWORKMANAGER_SERVICE_LOGFILE"] + except KeyError: + return None + if "%p" in log_file_name: + log_file_name = log_file_name.replace("%p", str(os.getpid())) + try: + log_file = open(log_file_name, "w") + except Exception: + log_file = None + + return log_file + + ############################################################################### @@ -2867,6 +2923,7 @@ def main(): global gl gl = Global() + gl.log_file = setup_log_file() gl.mainloop = GLib.MainLoop() gl.bus = dbus.SessionBus() gl.force_activation_failure = {} @@ -2899,7 +2956,8 @@ def main(): gl.settings.unexport() gl.manager.unexport() gl.object_manager.remove_from_connection() - + if gl.log_file: + gl.log_file.close() sys.exit(0) |