merge: branch 'lr/cs-oci-revert'

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2128
This commit is contained in:
Lubomir Rintel
2025-02-06 10:35:31 +01:00
3 changed files with 89 additions and 421 deletions

View File

@@ -6538,9 +6538,18 @@ find_device_for_activation(NMManager *self,
return FALSE; return FALSE;
device = find_device_by_iface(self, iface, connection, NULL, NULL); device = find_device_by_iface(self, iface, connection, NULL, NULL);
if (!device) {
g_set_error_literal(error,
NM_MANAGER_ERROR,
NM_MANAGER_ERROR_UNKNOWN_DEVICE,
"Failed to find a compatible device for this connection");
return FALSE;
}
} }
} }
nm_assert(is_vpn || NM_IS_DEVICE(device));
*out_device = device; *out_device = device;
*out_is_vpn = is_vpn; *out_is_vpn = is_vpn;
@@ -6665,14 +6674,7 @@ impl_manager_activate_connection(NMDBusObject *obj,
if (!subject) if (!subject)
goto error; goto error;
if (!find_device_for_activation(self, sett_conn, NULL, device_path, &device, &is_vpn, &error)) if (!find_device_for_activation(self, sett_conn, NULL, device_path, &device, &is_vpn, &error)) {
goto error;
if (!device && !is_vpn) {
g_set_error_literal(&error,
NM_MANAGER_ERROR,
NM_MANAGER_ERROR_UNKNOWN_DEVICE,
"Failed to find a compatible device for this connection");
goto error; goto error;
} }
@@ -6703,10 +6705,6 @@ impl_manager_activate_connection(NMDBusObject *obj,
return; return;
error: error:
if (device && nm_device_is_software(device)) {
if (_check_remove_dev_on_link_deleted(self, device))
remove_device(self, device, FALSE);
}
if (sett_conn) { if (sett_conn) {
nm_audit_log_connection_op(NM_AUDIT_OP_CONN_ACTIVATE, nm_audit_log_connection_op(NM_AUDIT_OP_CONN_ACTIVATE,
sett_conn, sett_conn,
@@ -6799,7 +6797,6 @@ _add_and_activate_auth_done(NMManager *self,
const char *error_desc) const char *error_desc)
{ {
NMManagerPrivate *priv; NMManagerPrivate *priv;
NMDevice *device;
GError *error = NULL; GError *error = NULL;
if (!success) { if (!success) {
@@ -6812,12 +6809,6 @@ _add_and_activate_auth_done(NMManager *self,
nm_active_connection_get_subject(active), nm_active_connection_get_subject(active),
error->message); error->message);
g_dbus_method_invocation_take_error(invocation, error); g_dbus_method_invocation_take_error(invocation, error);
device = nm_active_connection_get_device(active);
if (device && nm_device_is_software(device)) {
if (_check_remove_dev_on_link_deleted(self, device))
remove_device(self, device, FALSE);
}
return; return;
} }
@@ -6973,11 +6964,32 @@ impl_manager_add_and_activate_connection(NMDBusObject *obj,
goto error; goto error;
} }
conns = nm_settings_connections_array_to_connections( if (is_vpn) {
nm_settings_get_connections(priv->settings, NULL), /* Try to fill the VPN's connection setting and name at least */
-1); if (!nm_connection_get_setting_vpn(incompl_conn)) {
error = g_error_new_literal(NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_MISSING_SETTING,
"VPN connections require a 'vpn' setting");
g_prefix_error(&error, "%s: ", NM_SETTING_VPN_SETTING_NAME);
goto error;
}
if (device) { conns = nm_settings_connections_array_to_connections(
nm_settings_get_connections(priv->settings, NULL),
-1);
nm_utils_complete_generic(priv->platform,
incompl_conn,
NM_SETTING_VPN_SETTING_NAME,
conns,
NULL,
_("VPN connection"),
NULL,
NULL);
} else {
conns = nm_settings_connections_array_to_connections(
nm_settings_get_connections(priv->settings, NULL),
-1);
/* Let each device subclass complete the connection */ /* Let each device subclass complete the connection */
if (!nm_device_complete_connection(device, if (!nm_device_complete_connection(device,
incompl_conn, incompl_conn,
@@ -6985,33 +6997,10 @@ impl_manager_add_and_activate_connection(NMDBusObject *obj,
conns, conns,
&error)) &error))
goto error; goto error;
} else {
nm_utils_complete_generic(priv->platform,
incompl_conn,
nm_connection_get_connection_type(incompl_conn),
conns,
is_vpn ? _("VPN connection") : NULL,
nm_connection_get_connection_type(incompl_conn),
NULL,
NULL);
if (!nm_connection_verify(incompl_conn, &error))
goto error;
if (!is_vpn && !device) {
nm_assert(nm_connection_is_virtual(incompl_conn));
device = system_create_virtual_device(self, incompl_conn);
if (!device) {
g_set_error_literal(&error,
NM_MANAGER_ERROR,
NM_MANAGER_ERROR_UNKNOWN_DEVICE,
"Failed to create a device for this connection");
goto error;
}
}
} }
nm_assert(_nm_connection_verify(incompl_conn, NULL) == NM_SETTING_VERIFY_SUCCESS);
active = _new_active_connection(self, active = _new_active_connection(self,
is_vpn, is_vpn,
NULL, NULL,

View File

@@ -295,7 +295,7 @@ _nmc_get_ethernet_hwaddrs(NMClient *nmc)
} }
static NMDevice * static NMDevice *
_nmc_get_device_by_hwaddr(NMClient *nmc, const GType type_device, const char *hwaddr) _nmc_get_device_by_hwaddr(NMClient *nmc, const char *hwaddr)
{ {
const GPtrArray *devices; const GPtrArray *devices;
guint i; guint i;
@@ -307,7 +307,7 @@ _nmc_get_device_by_hwaddr(NMClient *nmc, const GType type_device, const char *hw
const char *hwaddr_dev; const char *hwaddr_dev;
gs_free char *s = NULL; gs_free char *s = NULL;
if (!G_TYPE_CHECK_INSTANCE_TYPE(device, type_device)) if (!NM_IS_DEVICE_ETHERNET(device))
continue; continue;
hwaddr_dev = _device_get_hwaddr(device); hwaddr_dev = _device_get_hwaddr(device);
@@ -427,23 +427,13 @@ _nmc_mangle_connection(NMDevice *device,
NM_SET_OUT(out_skipped_single_addr, FALSE); NM_SET_OUT(out_skipped_single_addr, FALSE);
NM_SET_OUT(out_changed, FALSE); NM_SET_OUT(out_changed, FALSE);
if (strcmp(nm_connection_get_connection_type(connection), NM_SETTING_MACVLAN_SETTING_NAME)
== 0) {
/* The MACVLAN just sits in between, no L3 configuration on it */
return;
} else if (strcmp(nm_connection_get_connection_type(connection), NM_SETTING_VLAN_SETTING_NAME)
!= 0) {
/* Preserve existing L3 configuration if not a VLAN */
if (device) {
if ((ac = nm_device_get_active_connection(device))
&& (remote_connection = NM_CONNECTION(nm_active_connection_get_connection(ac))))
remote_s_ip = nm_connection_get_setting_ip4_config(remote_connection);
}
}
s_ip = nm_connection_get_setting_ip4_config(connection); s_ip = nm_connection_get_setting_ip4_config(connection);
nm_assert(NM_IS_SETTING_IP4_CONFIG(s_ip)); nm_assert(NM_IS_SETTING_IP4_CONFIG(s_ip));
if ((ac = nm_device_get_active_connection(device))
&& (remote_connection = NM_CONNECTION(nm_active_connection_get_connection(ac))))
remote_s_ip = nm_connection_get_setting_ip4_config(remote_connection);
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);
@@ -576,31 +566,54 @@ _nmc_mangle_connection(NMDevice *device,
/*****************************************************************************/ /*****************************************************************************/
static gboolean static gboolean
_config_existing(SigTermData *sigterm_data, _config_one(SigTermData *sigterm_data,
const NMCSProviderGetConfigIfaceData *config_data, NMClient *nmc,
NMClient *nmc, const NMCSProviderGetConfigResult *result,
const NMCSProviderGetConfigResult *result, guint idx)
const char *connection_type,
NMDevice *device)
{ {
const char *hwaddr = config_data->hwaddr; const NMCSProviderGetConfigIfaceData *config_data = result->iface_datas_arr[idx];
gs_unref_object NMConnection *applied_connection = NULL; const char *hwaddr = config_data->hwaddr;
guint64 applied_version_id; gs_unref_object NMDevice *device = NULL;
gs_free_error GError *error = NULL; gs_unref_object NMConnection *applied_connection = NULL;
gboolean changed; guint64 applied_version_id;
gboolean skipped_single_addr; gs_free_error GError *error = NULL;
gboolean version_id_changed; gboolean changed;
guint try_count; gboolean skipped_single_addr;
gboolean any_changes; gboolean version_id_changed;
gboolean maybe_no_preserved_external_ip; guint try_count;
gboolean any_changes = FALSE;
gboolean maybe_no_preserved_external_ip;
g_main_context_iteration(NULL, FALSE);
if (g_cancellable_is_cancelled(sigterm_data->cancellable))
return FALSE;
device = nm_g_object_ref(_nmc_get_device_by_hwaddr(nmc, hwaddr));
if (!device) {
_LOGD("config device %s: skip because device not found", hwaddr);
return FALSE;
}
if (!nmcs_provider_get_config_iface_data_is_valid(config_data)) {
_LOGD("config device %s: skip because meta data not successfully fetched", hwaddr);
return FALSE;
}
if (config_data->iface_idx >= 100) {
/* since we use the iface_idx to select a table number, the range is limited from
* 0 to 99. Note that the providers are required to provide increasing numbers,
* so this means we bail out after the first 100 devices. */
_LOGD("config device %s: skip because number of supported interfaces reached", hwaddr);
return FALSE;
}
_LOGD("config device %s: configuring \"%s\" (%s)...", _LOGD("config device %s: configuring \"%s\" (%s)...",
hwaddr, hwaddr,
nm_device_get_iface(device) ?: "/unknown/", nm_device_get_iface(device) ?: "/unknown/",
nm_object_get_path(NM_OBJECT(device))); nm_object_get_path(NM_OBJECT(device)));
try_count = 0; try_count = 0;
any_changes = FALSE;
try_again: try_again:
g_clear_object(&applied_connection); g_clear_object(&applied_connection);
@@ -625,7 +638,7 @@ try_again:
return any_changes; return any_changes;
} }
if (_nmc_skip_connection_by_type(applied_connection, connection_type)) { if (_nmc_skip_connection_by_type(applied_connection, NM_SETTING_WIRED_SETTING_NAME)) {
_LOGD("config device %s: device has no suitable applied connection. Skip", hwaddr); _LOGD("config device %s: device has no suitable applied connection. Skip", hwaddr);
return any_changes; return any_changes;
} }
@@ -692,7 +705,7 @@ try_again:
nm_connection_get_uuid(applied_connection), nm_connection_get_uuid(applied_connection),
error->message); error->message);
} }
return TRUE; return any_changes;
} }
_LOGD("config device %s: connection \"%s\" (%s) reapplied", _LOGD("config device %s: connection \"%s\" (%s) reapplied",
@@ -700,233 +713,17 @@ try_again:
nm_connection_get_id(applied_connection), nm_connection_get_id(applied_connection),
nm_connection_get_uuid(applied_connection)); nm_connection_get_uuid(applied_connection));
return TRUE;
}
static gboolean
_config_ethernet(SigTermData *sigterm_data,
const NMCSProviderGetConfigIfaceData *config_data,
NMClient *nmc,
const NMCSProviderGetConfigResult *result)
{
gs_unref_object NMDevice *device = NULL;
device = nm_g_object_ref(
_nmc_get_device_by_hwaddr(nmc, NM_TYPE_DEVICE_ETHERNET, config_data->hwaddr));
if (!device) {
_LOGD("config device %s: skip because device not found", config_data->hwaddr);
return FALSE;
}
return _config_existing(sigterm_data,
config_data,
nmc,
result,
NM_SETTING_WIRED_SETTING_NAME,
device);
}
static gboolean
_oci_new_vlan_dev(SigTermData *sigterm_data,
const NMCSProviderGetConfigIfaceData *config_data,
NMClient *nmc,
const NMCSProviderGetConfigResult *result,
const char *connection_type,
const char *parent_hwaddr)
{
const char *hwaddr = config_data->hwaddr;
gs_unref_object NMConnection *connection = NULL;
gs_unref_object NMActiveConnection *active_connection = NULL;
gs_free_error GError *error = NULL;
gs_free char *macvlan_name = NULL;
gs_free char *connection_id = NULL;
char *ifname = NULL;
const char *ip4_config_method;
NMSetting *s_user;
connection = nm_simple_connection_new();
macvlan_name = g_strdup_printf("macvlan%ld", config_data->iface_idx);
connection_id = g_strdup_printf("%s%ld", connection_type, config_data->iface_idx);
if (strcmp(connection_type, NM_SETTING_MACVLAN_SETTING_NAME) == 0) {
nm_connection_add_setting(connection,
g_object_new(NM_TYPE_SETTING_MACVLAN,
NM_SETTING_MACVLAN_MODE,
NM_SETTING_MACVLAN_MODE_VEPA,
NULL));
nm_connection_add_setting(connection,
g_object_new(NM_TYPE_SETTING_IP6_CONFIG,
NM_SETTING_IP_CONFIG_METHOD,
NM_SETTING_IP6_CONFIG_METHOD_DISABLED,
NULL));
ip4_config_method = NM_SETTING_IP4_CONFIG_METHOD_DISABLED;
ifname = macvlan_name;
} else if (strcmp(connection_type, NM_SETTING_VLAN_SETTING_NAME) == 0) {
nm_connection_add_setting(connection,
g_object_new(NM_TYPE_SETTING_VLAN,
NM_SETTING_VLAN_PARENT,
macvlan_name,
NM_SETTING_VLAN_ID,
config_data->priv.oci.vlan_tag,
NULL));
ip4_config_method = NM_SETTING_IP4_CONFIG_METHOD_MANUAL;
} else {
g_return_val_if_reached(FALSE);
}
nm_connection_add_setting(connection,
g_object_new(NM_TYPE_SETTING_CONNECTION,
NM_SETTING_CONNECTION_ID,
connection_id,
NM_SETTING_CONNECTION_TYPE,
connection_type,
NM_SETTING_CONNECTION_INTERFACE_NAME,
ifname,
NULL));
nm_connection_add_setting(connection,
g_object_new(NM_TYPE_SETTING_IP4_CONFIG,
NM_SETTING_IP_CONFIG_METHOD,
ip4_config_method,
NULL));
nm_connection_add_setting(connection,
g_object_new(NM_TYPE_SETTING_WIRED,
NM_SETTING_WIRED_MAC_ADDRESS,
parent_hwaddr,
NM_SETTING_WIRED_CLONED_MAC_ADDRESS,
hwaddr,
NULL));
s_user = nm_setting_user_new();
nm_connection_add_setting(connection, s_user);
nm_setting_user_set_data(NM_SETTING_USER(s_user),
"org.freedesktop.NetworkManager.origin",
"nm-cloud-setup",
NULL);
_nmc_mangle_connection(NULL, connection, result, config_data, NULL, NULL);
_LOGD("config device %s: creating %s connection for VLAN %d on %s...",
hwaddr,
ifname ?: connection_type,
config_data->priv.oci.vlan_tag,
parent_hwaddr);
active_connection = nmcs_add_and_activate(nmc, NULL, connection, &error);
if (active_connection == NULL) {
if (!nm_utils_error_is_cancelled(error)) {
_LOGD("config device %s: failure to activate connection: %s", hwaddr, error->message);
}
return FALSE;
}
_LOGD("config device %s: connection \"%s\" (%s) created",
hwaddr,
nm_active_connection_get_id(active_connection),
nm_active_connection_get_uuid(active_connection));
return TRUE;
}
static gboolean
_oci_config_vnic_dev(SigTermData *sigterm_data,
const NMCSProviderGetConfigIfaceData *config_data,
NMClient *nmc,
const NMCSProviderGetConfigResult *result,
const GType device_type,
const char *connection_type,
const char *parent_hwaddr)
{
gs_unref_object NMDevice *device = NULL;
device = nm_g_object_ref(_nmc_get_device_by_hwaddr(nmc, device_type, config_data->hwaddr));
if (device) {
/* There is a device. Modify and reapply the currently applied connection. */
return _config_existing(sigterm_data, config_data, nmc, result, connection_type, device);
} else {
/* There is no device, but we're configuring a VLAN.
* We can just go ahead and create one with a new connection. */
return _oci_new_vlan_dev(sigterm_data,
config_data,
nmc,
result,
connection_type,
parent_hwaddr);
}
}
static gboolean
_config_one(SigTermData *sigterm_data,
NMCSProvider *provider,
NMClient *nmc,
const NMCSProviderGetConfigResult *result,
guint idx)
{
const NMCSProviderGetConfigIfaceData *config_data = result->iface_datas_arr[idx];
gboolean any_changes;
g_main_context_iteration(NULL, FALSE);
if (g_cancellable_is_cancelled(sigterm_data->cancellable))
return FALSE;
if (!nmcs_provider_get_config_iface_data_is_valid(config_data)) {
_LOGD("config device %s: skip because meta data not successfully fetched",
config_data->hwaddr);
return FALSE;
}
if (config_data->iface_idx >= 100) {
/* since we use the iface_idx to select a table number, the range is limited from
* 0 to 99. Note that the providers are required to provide increasing numbers,
* so this means we bail out after the first 100 devices. */
_LOGD("config device %s: skip because number of supported interfaces reached",
config_data->hwaddr);
return FALSE;
}
if (NMCS_IS_PROVIDER_OCI(provider) && config_data->priv.oci.vlan_tag != 0) {
if (config_data->priv.oci.parent_hwaddr == NULL) {
_LOGW("config device %s: has vlan id %d but no parent device",
config_data->hwaddr,
config_data->priv.oci.vlan_tag);
return FALSE;
}
/* MACVLAN first, because VLAN is on top of it. */
any_changes = _oci_config_vnic_dev(sigterm_data,
config_data,
nmc,
result,
NM_TYPE_DEVICE_MACVLAN,
NM_SETTING_MACVLAN_SETTING_NAME,
config_data->priv.oci.parent_hwaddr);
any_changes += _oci_config_vnic_dev(sigterm_data,
config_data,
nmc,
result,
NM_TYPE_DEVICE_VLAN,
NM_SETTING_VLAN_SETTING_NAME,
config_data->hwaddr);
} else {
any_changes = _config_ethernet(sigterm_data, config_data, nmc, result);
}
return any_changes; return any_changes;
} }
static gboolean static gboolean
_config_all(SigTermData *sigterm_data, _config_all(SigTermData *sigterm_data, NMClient *nmc, const NMCSProviderGetConfigResult *result)
NMCSProvider *provider,
NMClient *nmc,
const NMCSProviderGetConfigResult *result)
{ {
gboolean any_changes = FALSE; gboolean any_changes = FALSE;
guint i; guint i;
for (i = 0; i < result->n_iface_datas; i++) { for (i = 0; i < result->n_iface_datas; i++) {
if (_config_one(sigterm_data, provider, nmc, result, i)) if (_config_one(sigterm_data, nmc, result, i))
any_changes = TRUE; any_changes = TRUE;
} }
@@ -1011,7 +808,7 @@ main(int argc, const char *const *argv)
if (!result) if (!result)
goto done; goto done;
if (_config_all(&sigterm_data, provider, nmc, result)) if (_config_all(&sigterm_data, nmc, result))
_LOGI("some changes were applied for provider %s", nmcs_provider_get_name(provider)); _LOGI("some changes were applied for provider %s", nmcs_provider_get_name(provider));
else else
_LOGD("no changes were applied for provider %s", nmcs_provider_get_name(provider)); _LOGD("no changes were applied for provider %s", nmcs_provider_get_name(provider));

View File

@@ -2907,124 +2907,6 @@ class TestNmCloudSetup(unittest.TestCase):
) )
self.assertEqual(exitstatus, 0, "Unexpectedly returned a non-zero status") self.assertEqual(exitstatus, 0, "Unexpectedly returned a non-zero status")
@cloud_setup_test
def test_oci_vlans(self):
self._mock_devices()
oci_meta = "/opc/v2/"
self._mock_path(oci_meta + "instance", "{}")
self._mock_path(
oci_meta + "vnics",
"""
[
{
"macAddr": "%s",
"privateIp": "%s",
"subnetCidrBlock": "172.31.16.0/20",
"virtualRouterIp": "172.31.16.1",
"vlanTag": 0,
"nicIndex": 0,
"vnicId": "ocid1.vnic.oc1.cz-adamov1.foobarbaz"
},
{
"macAddr": "%s",
"privateIp": "%s",
"subnetCidrBlock": "172.31.166.0/20",
"virtualRouterIp": "172.31.166.1",
"vlanTag": 0,
"nicIndex": 1,
"vnicId": "ocid1.vnic.oc1.uk-hogwarts.expelliarmus"
},
{
"macAddr": "C0:00:00:00:00:10",
"privateIp": "172.31.10.10",
"subnetCidrBlock": "172.31.10.0/20",
"virtualRouterIp": "172.31.10.1",
"vlanTag": 700,
"nicIndex": 0,
"vnicId": "ocid1.vnic.oc1.uk-hogwarts.keka"
}
]
"""
% (
TestNmCloudSetup._mac1,
TestNmCloudSetup._ip1,
TestNmCloudSetup._mac2,
TestNmCloudSetup._ip2,
),
)
# Run nm-cloud-setup for the first time
pexp = self.ctx.cmd_call_pexpect(
ENV_NM_TEST_CLIENT_CLOUD_SETUP_PATH,
[],
{
"NM_CLOUD_SETUP_OCI_HOST": self.md_url,
"NM_CLOUD_SETUP_LOG": "trace",
"NM_CLOUD_SETUP_OCI": "yes",
},
)
pexp.expect("provider oci detected")
pexp.expect("found interfaces: CC:00:00:00:00:01, CC:00:00:00:00:02")
pexp.expect("get-config: starting")
pexp.expect("get-config: success")
pexp.expect("meta data received")
# No configuration for the ethernets
pexp.expect('configuring "eth0"')
pexp.expect("device has no suitable applied connection. Skip")
# Setting up the VLAN
pexp.expect("creating macvlan2 connection for VLAN 700 on CC:00:00:00:00:01...")
pexp.expect("creating vlan connection for VLAN 700 on C0:00:00:00:00:10...")
pexp.expect("some changes were applied for provider oci")
(exitstatus, signalstatus, valgrind_log) = self.ctx.cmd_close_pexpect(pexp)
Util.valgrind_check_log(valgrind_log, "test_oci_vlans")
self.assertIsNone(
signalstatus,
"Unexpectedly got " + Util.signal_no_to_str(signalstatus or 0),
)
self.assertEqual(exitstatus, 0, "Unexpectedly returned a non-zero status")
# TODO: Actually check the contents of the connection
# Probably needs changes to the mock service API
conn_macvlan = self.ctx.srv.findConnections(con_id="connection-3")
assert conn_macvlan is not None
conn_vlan = self.ctx.srv.findConnections(con_id="connection-4")
assert conn_vlan is not None
# Run nm-cloud-setup for the second time
pexp = self.ctx.cmd_call_pexpect(
ENV_NM_TEST_CLIENT_CLOUD_SETUP_PATH,
[],
{
"NM_CLOUD_SETUP_OCI_HOST": self.md_url,
"NM_CLOUD_SETUP_LOG": "trace",
"NM_CLOUD_SETUP_OCI": "yes",
},
)
# Just the same ol' thing, just no changes this time
pexp.expect("provider oci detected")
pexp.expect("found interfaces: CC:00:00:00:00:01, CC:00:00:00:00:02")
pexp.expect("get-config: starting")
pexp.expect("get-config: success")
pexp.expect("meta data received")
pexp.expect('configuring "eth0"')
pexp.expect("device has no suitable applied connection. Skip")
pexp.expect("no changes were applied for provider oci")
(exitstatus, signalstatus, valgrind_log) = self.ctx.cmd_close_pexpect(pexp)
Util.valgrind_check_log(valgrind_log, "test_oci_vlans")
self.assertIsNone(
signalstatus,
"Unexpectedly got " + Util.signal_no_to_str(signalstatus or 0),
)
self.assertEqual(exitstatus, 0, "Unexpectedly returned a non-zero status")
############################################################################### ###############################################################################