Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

distro: add eject FreeBSD code path #1

Closed
wants to merge 3 commits into from
Closed
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
15 changes: 15 additions & 0 deletions cloudinit/distros/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1183,6 +1183,21 @@ def build_dhclient_cmd(
"/bin/true",
] + (["-cf", config_file, interface] if config_file else [interface])

@staticmethod
def eject_media(device: str) -> None:
cmd = None
if subp.which("eject"):
cmd = ["eject", device]
elif subp.which("/lib/udev/cdrom_id"):
cmd = ["/lib/udev/cdrom_id", "--eject-media", device]
else:
raise subp.ProcessExecutionError(
cmd="eject_media_cmd",
description="eject command not found",
reason="neither eject nor /lib/udev/cdrom_id are found",
)
subp.subp(cmd)


def _apply_hostname_transformations_to_url(url: str, transformations: list):
"""
Expand Down
4 changes: 4 additions & 0 deletions cloudinit/distros/freebsd.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,3 +226,7 @@ def build_dhclient_cmd(
return [path, "-l", lease_file, "-p", pid_file] + (
["-c", config_file, interface] if config_file else [interface]
)

@staticmethod
def eject_media(device: str) -> None:
subp.subp(["camcontrol", "eject", device])
1 change: 1 addition & 0 deletions cloudinit/sources/DataSourceAzure.py
Original file line number Diff line number Diff line change
Expand Up @@ -1366,6 +1366,7 @@ def _report_ready(
try:
data = get_metadata_from_fabric(
endpoint=self._wireserver_endpoint,
distro=self.distro,
iso_dev=self._iso_dev,
pubkey_info=pubkey_info,
)
Expand Down
13 changes: 7 additions & 6 deletions cloudinit/sources/helpers/azure.py
Original file line number Diff line number Diff line change
Expand Up @@ -719,10 +719,10 @@ def clean_up(self):
self.openssl_manager.clean_up()

@azure_ds_telemetry_reporter
def eject_iso(self, iso_dev) -> None:
def eject_iso(self, iso_dev, distro: distros.Distro) -> None:
LOG.debug("Ejecting the provisioning iso")
try:
LOG.debug("Ejecting the provisioning iso")
subp.subp(["eject", iso_dev])
distro.eject_media(iso_dev)
except Exception as e:
report_diagnostic_event(
"Failed ejecting the provisioning iso: %s" % e,
Expand All @@ -731,7 +731,7 @@ def eject_iso(self, iso_dev) -> None:

@azure_ds_telemetry_reporter
def register_with_azure_and_fetch_data(
self, pubkey_info=None, iso_dev=None
self, distro: distros.Distro, pubkey_info=None, iso_dev=None
) -> Optional[List[str]]:
"""Gets the VM's GoalState from Azure, uses the GoalState information
to report ready/send the ready signal/provisioning complete signal to
Expand Down Expand Up @@ -762,7 +762,7 @@ def register_with_azure_and_fetch_data(
)

if iso_dev is not None:
self.eject_iso(iso_dev)
self.eject_iso(iso_dev, distro=distro)

health_reporter.send_ready_signal()
return ssh_keys
Expand Down Expand Up @@ -940,13 +940,14 @@ def _filter_pubkeys(keys_by_fingerprint: dict, pubkey_info: list) -> list:
@azure_ds_telemetry_reporter
def get_metadata_from_fabric(
endpoint: str,
distro: distros.Distro,
pubkey_info: Optional[List[str]] = None,
iso_dev: Optional[str] = None,
):
shim = WALinuxAgentShim(endpoint=endpoint)
try:
return shim.register_with_azure_and_fetch_data(
pubkey_info=pubkey_info, iso_dev=iso_dev
distro=distro, pubkey_info=pubkey_info, iso_dev=iso_dev
)
finally:
shim.clean_up()
Expand Down
51 changes: 51 additions & 0 deletions tests/integration_tests/datasources/test_azure.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import pytest
from pycloudlib.cloud import ImageType

from tests.integration_tests.clouds import IntegrationCloud
from tests.integration_tests.conftest import get_validated_source
from tests.integration_tests.instances import IntegrationInstance
from tests.integration_tests.integration_settings import PLATFORM
from tests.integration_tests.releases import CURRENT_RELEASE
from tests.integration_tests.util import verify_clean_log


def _check_for_eject_errors(
instance: IntegrationInstance,
):
assert "sr0" not in instance.execute("mount")
log = instance.read_from_file("/var/log/cloud-init.log")
assert "Failed ejecting the provisioning iso" not in log
verify_clean_log(log)


@pytest.mark.skipif(PLATFORM != "azure", reason="Test is Azure specific")
def test_azure_eject(session_cloud: IntegrationCloud):
"""Integration test for GitHub #4732.

Azure uses `eject` but that is not always available on minimal images.
Ensure udev's eject can be used on systemd-enabled systems.
"""
with session_cloud.launch(
launch_kwargs={
"image_id": session_cloud.cloud_instance.daily_image(
CURRENT_RELEASE.series, image_type=ImageType.MINIMAL
)
}
) as instance:
source = get_validated_source(session_cloud)
if source.installs_new_version():
instance.install_new_cloud_init(
source, take_snapshot=False, clean=True
)
snapshot_id = instance.snapshot()
try:
with session_cloud.launch(
launch_kwargs={
"image_id": snapshot_id,
}
) as snapshot_instance:
_check_for_eject_errors(snapshot_instance)
finally:
session_cloud.cloud_instance.delete_image(snapshot_id)
else:
_check_for_eject_errors(instance)
9 changes: 9 additions & 0 deletions tests/unittests/sources/test_azure.py
Original file line number Diff line number Diff line change
Expand Up @@ -3756,6 +3756,7 @@ def test_no_pps(self):
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,
)
Expand Down Expand Up @@ -3921,11 +3922,13 @@ def test_running_pps(self):
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,
),
mock.call(
endpoint="10.11.12.13",
distro=self.azure_ds.distro,
iso_dev=None,
pubkey_info=None,
),
Expand Down Expand Up @@ -4041,11 +4044,13 @@ def test_savable_pps(self):
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,
),
mock.call(
endpoint="10.11.12.13",
distro=self.azure_ds.distro,
iso_dev=None,
pubkey_info=None,
),
Expand Down Expand Up @@ -4197,11 +4202,13 @@ def test_savable_pps_early_unplug(self, fabric_side_effect):
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,
),
mock.call(
endpoint="10.11.12.13",
distro=self.azure_ds.distro,
iso_dev=None,
pubkey_info=None,
),
Expand Down Expand Up @@ -4285,6 +4292,7 @@ def test_recovery_pps(self, pps_type):
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,
),
Expand Down Expand Up @@ -4394,6 +4402,7 @@ def test_os_disk_pps(self, mock_sleep, subp_side_effect):
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,
)
Expand Down
56 changes: 37 additions & 19 deletions tests/unittests/sources/test_azure_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from cloudinit.util import load_file
from tests.unittests.helpers import CiTestCase, ExitStack, mock
from tests.unittests.sources.test_azure import construct_ovf_env
from tests.unittests.util import MockDistro

GOAL_STATE_TEMPLATE = """\
<?xml version="1.0" encoding="utf-8"?>
Expand Down Expand Up @@ -1007,16 +1008,19 @@ def setUp(self):
self.GoalState.return_value.instance_id = self.test_instance_id

def test_eject_iso_is_called(self):
mock_distro = MockDistro()
shim = wa_shim(endpoint="test_endpoint")
with mock.patch.object(
shim, "eject_iso", autospec=True
) as m_eject_iso:
shim.register_with_azure_and_fetch_data(iso_dev="/dev/sr0")
m_eject_iso.assert_called_once_with("/dev/sr0")
shim.register_with_azure_and_fetch_data(
distro=mock_distro, iso_dev="/dev/sr0"
)
m_eject_iso.assert_called_once_with("/dev/sr0", distro=mock_distro)

def test_http_client_does_not_use_certificate_for_report_ready(self):
shim = wa_shim(endpoint="test_endpoint")
shim.register_with_azure_and_fetch_data()
shim.register_with_azure_and_fetch_data(distro=None)
self.assertEqual(
[mock.call(None)], self.AzureEndpointHttpClient.call_args_list
)
Expand All @@ -1030,7 +1034,7 @@ def test_http_client_does_not_use_certificate_for_report_failure(self):

def test_correct_url_used_for_goalstate_during_report_ready(self):
shim = wa_shim(endpoint="test_endpoint")
shim.register_with_azure_and_fetch_data()
shim.register_with_azure_and_fetch_data(distro=None)
m_get = self.AzureEndpointHttpClient.return_value.get
self.assertEqual(
[mock.call("http://test_endpoint/machine/?comp=goalstate")],
Expand Down Expand Up @@ -1082,7 +1086,9 @@ def test_certificates_used_to_determine_public_keys(self):
}
sslmgr = self.OpenSSLManager.return_value
sslmgr.parse_certificates.return_value = certs
data = shim.register_with_azure_and_fetch_data(pubkey_info=mypk)
data = shim.register_with_azure_and_fetch_data(
distro=None, pubkey_info=mypk
)
self.assertEqual(
[mock.call(self.GoalState.return_value.certificates_xml)],
sslmgr.parse_certificates.call_args_list,
Expand All @@ -1095,12 +1101,14 @@ def test_absent_certificates_produces_empty_public_keys(self):
mypk = [{"fingerprint": "fp1", "path": "path1"}]
self.GoalState.return_value.certificates_xml = None
shim = wa_shim(endpoint="test_endpoint")
data = shim.register_with_azure_and_fetch_data(pubkey_info=mypk)
data = shim.register_with_azure_and_fetch_data(
distro=None, pubkey_info=mypk
)
self.assertEqual([], data)

def test_correct_url_used_for_report_ready(self):
shim = wa_shim(endpoint="test_endpoint")
shim.register_with_azure_and_fetch_data()
shim.register_with_azure_and_fetch_data(distro=None)
expected_url = "http://test_endpoint/machine?comp=health"
self.assertEqual(
[mock.call(expected_url, data=mock.ANY, extra_headers=mock.ANY)],
Expand All @@ -1118,7 +1126,7 @@ def test_correct_url_used_for_report_failure(self):

def test_goal_state_values_used_for_report_ready(self):
shim = wa_shim(endpoint="test_endpoint")
shim.register_with_azure_and_fetch_data()
shim.register_with_azure_and_fetch_data(distro=None)
posted_document = (
self.AzureEndpointHttpClient.return_value.post.call_args[1]["data"]
)
Expand All @@ -1138,7 +1146,7 @@ def test_goal_state_values_used_for_report_failure(self):

def test_xml_elems_in_report_ready_post(self):
shim = wa_shim(endpoint="test_endpoint")
shim.register_with_azure_and_fetch_data()
shim.register_with_azure_and_fetch_data(distro=None)
health_document = get_formatted_health_report_xml_bytes(
incarnation=escape(self.test_incarnation),
container_id=escape(self.test_container_id),
Expand Down Expand Up @@ -1176,7 +1184,7 @@ def test_register_with_azure_and_fetch_data_calls_send_ready_signal(
self, m_goal_state_health_reporter
):
shim = wa_shim(endpoint="test_endpoint")
shim.register_with_azure_and_fetch_data()
shim.register_with_azure_and_fetch_data(distro=None)
self.assertEqual(
1,
m_goal_state_health_reporter.return_value.send_ready_signal.call_count, # noqa: E501
Expand Down Expand Up @@ -1210,14 +1218,14 @@ def test_clean_up_can_be_called_at_any_time(self):

def test_openssl_manager_not_instantiated_by_shim_report_status(self):
shim = wa_shim(endpoint="test_endpoint")
shim.register_with_azure_and_fetch_data()
shim.register_with_azure_and_fetch_data(distro=None)
shim.register_with_azure_and_report_failure(description="TestDesc")
shim.clean_up()
self.OpenSSLManager.assert_not_called()

def test_clean_up_after_report_ready(self):
shim = wa_shim(endpoint="test_endpoint")
shim.register_with_azure_and_fetch_data()
shim.register_with_azure_and_fetch_data(distro=None)
shim.clean_up()
self.OpenSSLManager.return_value.clean_up.assert_not_called()

Expand All @@ -1233,7 +1241,7 @@ def test_fetch_goalstate_during_report_ready_raises_exc_on_get_exc(self):
)
shim = wa_shim(endpoint="test_endpoint")
self.assertRaises(
url_helper.UrlError, shim.register_with_azure_and_fetch_data
url_helper.UrlError, shim.register_with_azure_and_fetch_data, None
)

def test_fetch_goalstate_during_report_failure_raises_exc_on_get_exc(self):
Expand All @@ -1251,7 +1259,7 @@ def test_fetch_goalstate_during_report_ready_raises_exc_on_parse_exc(self):
self.GoalState.side_effect = url_helper.UrlError("retry", code=404)
shim = wa_shim(endpoint="test_endpoint")
self.assertRaises(
url_helper.UrlError, shim.register_with_azure_and_fetch_data
url_helper.UrlError, shim.register_with_azure_and_fetch_data, None
)

def test_fetch_goalstate_during_report_failure_raises_exc_on_parse_exc(
Expand All @@ -1271,7 +1279,7 @@ def test_failure_to_send_report_ready_health_doc_bubbles_up(self):
)
shim = wa_shim(endpoint="test_endpoint")
self.assertRaises(
url_helper.UrlError, shim.register_with_azure_and_fetch_data
url_helper.UrlError, shim.register_with_azure_and_fetch_data, None
)

def test_failure_to_send_report_failure_health_doc_bubbles_up(self):
Expand All @@ -1297,14 +1305,18 @@ def setUp(self):
)

def test_data_from_shim_returned(self):
ret = azure_helper.get_metadata_from_fabric(endpoint="test_endpoint")
ret = azure_helper.get_metadata_from_fabric(
distro=None, endpoint="test_endpoint"
)
self.assertEqual(
self.m_shim.return_value.register_with_azure_and_fetch_data.return_value, # noqa: E501
ret,
)

def test_success_calls_clean_up(self):
azure_helper.get_metadata_from_fabric(endpoint="test_endpoint")
azure_helper.get_metadata_from_fabric(
distro=None, endpoint="test_endpoint"
)
self.assertEqual(1, self.m_shim.return_value.clean_up.call_count)

def test_failure_in_registration_propagates_exc_and_calls_clean_up(self):
Expand All @@ -1315,13 +1327,15 @@ def test_failure_in_registration_propagates_exc_and_calls_clean_up(self):
url_helper.UrlError,
azure_helper.get_metadata_from_fabric,
"test_endpoint",
None,
)
self.assertEqual(1, self.m_shim.return_value.clean_up.call_count)

def test_calls_shim_register_with_azure_and_fetch_data(self):
m_pubkey_info = mock.MagicMock()
azure_helper.get_metadata_from_fabric(
endpoint="test_endpoint",
distro=None,
pubkey_info=m_pubkey_info,
iso_dev="/dev/sr0",
)
Expand All @@ -1330,12 +1344,16 @@ def test_calls_shim_register_with_azure_and_fetch_data(self):
self.m_shim.return_value.register_with_azure_and_fetch_data.call_count, # noqa: E501
)
self.assertEqual(
mock.call(iso_dev="/dev/sr0", pubkey_info=m_pubkey_info),
mock.call(
distro=None, iso_dev="/dev/sr0", pubkey_info=m_pubkey_info
),
self.m_shim.return_value.register_with_azure_and_fetch_data.call_args, # noqa: E501
)

def test_instantiates_shim_with_kwargs(self):
azure_helper.get_metadata_from_fabric(endpoint="test_endpoint")
azure_helper.get_metadata_from_fabric(
endpoint="test_endpoint", distro=None
)
self.assertEqual(1, self.m_shim.call_count)
self.assertEqual(
mock.call(endpoint="test_endpoint"),
Expand Down
Loading