Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 21 additions & 13 deletions cloudinit/sources/DataSourceAzure.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,6 +292,7 @@ def get_resource_disk_on_freebsd(port_id) -> Optional[str]:
"disk_aliases": {"ephemeral0": RESOURCE_DISK_PATH},
"apply_network_config": True, # Use IMDS published network configuration
"apply_network_config_for_secondary_ips": True, # Configure secondary ips
"experimental_skip_ready_report": False, # Skip final ready report
}

BUILTIN_CLOUD_EPHEMERAL_DISK_CONFIG = {
Expand Down Expand Up @@ -838,21 +839,28 @@ def crawl_metadata(self):
crawled_data["metadata"]["instance-id"] = self._iid()

if self._negotiated is False and self._is_ephemeral_networking_up():
# Report ready and fetch public-keys from Wireserver, if required.
pubkey_info = self._determine_wireserver_pubkey_info(
cfg=cfg, imds_md=imds_md
)
try:
ssh_keys = self._report_ready(pubkey_info=pubkey_info)
except Exception:
# Failed to report ready, but continue with best effort.
pass
if self.ds_cfg.get("experimental_skip_ready_report", False):
LOG.debug(
"Skipping final health report as "
"experimental_skip_ready_report is enabled."
)
else:
LOG.debug("negotiating returned %s", ssh_keys)
if ssh_keys:
crawled_data["metadata"]["public-keys"] = ssh_keys
# Report ready and fetch public-keys from Wireserver,
# if required.
pubkey_info = self._determine_wireserver_pubkey_info(
cfg=cfg, imds_md=imds_md
)
try:
ssh_keys = self._report_ready(pubkey_info=pubkey_info)
except Exception:
# Failed to report ready, but continue with best effort.
pass
else:
LOG.debug("negotiating returned %s", ssh_keys)
if ssh_keys:
crawled_data["metadata"]["public-keys"] = ssh_keys

self._cleanup_markers()
self._cleanup_markers()

return crawled_data

Expand Down
207 changes: 207 additions & 0 deletions tests/unittests/sources/test_azure.py
Original file line number Diff line number Diff line change
Expand Up @@ -5212,6 +5212,213 @@ def test_os_disk_pps(self, mock_sleep, subp_side_effect):
assert len(self.mock_kvp_report_via_kvp.mock_calls) == 1
assert len(self.mock_kvp_report_success_to_host.mock_calls) == 1

@pytest.mark.parametrize("pps_type", ["None", "Running", "Savable"])
def test_skip_ready_report(self, pps_type):
"""Verify ready report is skipped when configured to."""
self.azure_ds.ds_cfg["experimental_skip_ready_report"] = True

is_pps = pps_type in ("Running", "Savable")

imds_md_source = copy.deepcopy(self.imds_md)
imds_md_source["extended"]["compute"]["ppsType"] = pps_type

nl_sock = mock.MagicMock()
self.mock_netlink.create_bound_netlink_socket.return_value = nl_sock
if pps_type == "Savable":
self.mock_netlink.wait_for_nic_detach_event.return_value = "eth9"
self.mock_netlink.wait_for_nic_attach_event.return_value = (
"ethAttached1"
)

if is_pps:
self.mock_readurl.side_effect = [
mock.MagicMock(contents=json.dumps(imds_md_source).encode()),
mock.MagicMock(
contents=construct_ovf_env(
provision_guest_proxy_agent=False
).encode()
),
mock.MagicMock(contents=json.dumps(self.imds_md).encode()),
]
else:
ovf = construct_ovf_env(provision_guest_proxy_agent=False)
md, ud, cfg = dsaz.read_azure_ovf(ovf)
self.mock_util_mount_cb.return_value = (md, ud, cfg, {})
self.mock_readurl.side_effect = [
mock.MagicMock(contents=json.dumps(self.imds_md).encode()),
]

self.mock_azure_get_metadata_from_fabric.return_value = []

self.azure_ds._check_and_get_data()

assert self.mock_subp_subp.mock_calls == []

# Verify IMDS calls.
if is_pps:
assert self.mock_readurl.mock_calls == [
mock.call(
"http://169.254.169.254/metadata/instance?"
"api-version=2021-08-01&extended=true",
exception_cb=mock.ANY,
headers_cb=imds.headers_cb,
infinite=True,
log_req_resp=True,
timeout=30,
),
mock.call(
"http://169.254.169.254/metadata/reprovisiondata?"
"api-version=2019-06-01",
exception_cb=mock.ANY,
headers_cb=imds.headers_cb,
log_req_resp=False,
infinite=True,
timeout=30,
),
mock.call(
"http://169.254.169.254/metadata/instance?"
"api-version=2021-08-01&extended=true",
exception_cb=mock.ANY,
headers_cb=imds.headers_cb,
infinite=True,
log_req_resp=True,
timeout=30,
),
]
else:
assert self.mock_readurl.mock_calls == [
mock.call(
"http://169.254.169.254/metadata/instance?"
"api-version=2021-08-01&extended=true",
timeout=30,
headers_cb=imds.headers_cb,
exception_cb=mock.ANY,
infinite=True,
log_req_resp=True,
),
]

# Verify DHCP setup.
if pps_type == "Running":
assert (
self.mock_wrapping_setup_ephemeral_networking.mock_calls
== [
mock.call(timeout_minutes=20),
mock.call(timeout_minutes=5),
]
)
assert (
self.mock_net_dhcp_maybe_perform_dhcp_discovery.mock_calls
== [
mock.call(self.azure_ds.distro, None, dsaz.dhcp_log_cb),
mock.call(self.azure_ds.distro, None, dsaz.dhcp_log_cb),
]
)
elif pps_type == "Savable":
assert (
self.mock_wrapping_setup_ephemeral_networking.mock_calls
== [
mock.call(timeout_minutes=20),
mock.call(
iface="ethAttached1",
timeout_minutes=20,
report_failure_if_not_primary=False,
),
]
)
assert (
self.mock_net_dhcp_maybe_perform_dhcp_discovery.mock_calls
== [
mock.call(self.azure_ds.distro, None, dsaz.dhcp_log_cb),
mock.call(
self.azure_ds.distro,
"ethAttached1",
dsaz.dhcp_log_cb,
),
]
)
else:
assert (
self.mock_wrapping_setup_ephemeral_networking.mock_calls
== [mock.call(timeout_minutes=20)]
)
assert (
self.mock_net_dhcp_maybe_perform_dhcp_discovery.mock_calls
== [
mock.call(self.azure_ds.distro, None, dsaz.dhcp_log_cb),
]
)

assert self.azure_ds._wireserver_endpoint == "10.11.12.13"
assert self.azure_ds._is_ephemeral_networking_up() is False

# Verify DMI usage.
assert self.mock_dmi_read_dmi_data.mock_calls == [
mock.call("chassis-asset-tag"),
mock.call("system-uuid"),
]
assert (
self.azure_ds.metadata["instance-id"]
== "50109936-ef07-47fe-ac82-890c853f60d5"
)

# Verify IMDS metadata.
assert self.azure_ds.metadata["imds"] == self.imds_md

# PPS types still report ready once (source), no-PPS skips entirely.
if is_pps:
assert self.mock_azure_get_metadata_from_fabric.mock_calls == [
mock.call(
endpoint="10.11.12.13",
distro=self.azure_ds.distro,
iso_dev="/dev/sr0",
pubkey_info=None,
),
]
else:
assert self.mock_azure_get_metadata_from_fabric.mock_calls == []

# Verify netlink operations.
if pps_type == "Running":
assert self.mock_netlink.mock_calls == [
mock.call.create_bound_netlink_socket(),
mock.call.wait_for_media_disconnect_connect(
mock.ANY, "ethBoot0"
),
mock.call.create_bound_netlink_socket().close(),
]
elif pps_type == "Savable":
assert self.mock_netlink.mock_calls == [
mock.call.create_bound_netlink_socket(),
mock.call.wait_for_nic_detach_event(nl_sock),
mock.call.wait_for_nic_attach_event(nl_sock, ["ethAttached1"]),
mock.call.create_bound_netlink_socket().close(),
]
else:
assert self.mock_netlink.mock_calls == []

# Verify reported_ready marker cleaned up.
if is_pps:
assert self.wrapped_util_write_file.mock_calls[0] == mock.call(
self.patched_reported_ready_marker_path.as_posix(),
mock.ANY,
)
else:
assert self.wrapped_util_write_file.mock_calls == []
assert self.patched_reported_ready_marker_path.exists() is False

# Verify KVP reports.
assert not self.mock_kvp_report_via_kvp.mock_calls
assert not self.mock_azure_report_failure_to_fabric.mock_calls
expected_kvp_count = 1 if is_pps else 0
assert (
len(self.mock_kvp_report_success_to_host.mock_calls)
== expected_kvp_count
)
assert (
len(self.mock_report_dmesg_to_kvp.mock_calls) == expected_kvp_count
)

def test_imds_failure_results_in_provisioning_failure(self):
self.mock_readurl.side_effect = url_helper.UrlError(
requests.ConnectionError(
Expand Down