summaryrefslogtreecommitdiff
path: root/cloudinit/sources
diff options
context:
space:
mode:
authorChris Patterson <cpatterson@microsoft.com>2023-04-28 14:29:14 -0700
committerGitHub <noreply@github.com>2023-04-28 16:29:14 -0500
commit967104088db0e9724096e3776c9c31ccbb3f97cb (patch)
treeb3bfb5c07bd22f1beae0b0f1c2b329fbfee61764 /cloudinit/sources
parent5abf5f5f2cf93c57ac74220251d2a2acce5f7099 (diff)
downloadcloud-init-git-967104088db0e9724096e3776c9c31ccbb3f97cb.tar.gz
sources/azure: report success to host and introduce kvp module (#2141)
Add success reporting to the host via KVP. - Move _report_failure_to_host() into kvp module. - Tweak error description to use result=error instead of PROVISIONING_ERROR: ... - Use result=success for the successful ("ready") reports. - report_x_via_kvp => report_x_to_host for consistency with fabric. ReportableError.as_description() => as_encoded_report() Signed-off-by: Chris Patterson <cpatterson@microsoft.com>
Diffstat (limited to 'cloudinit/sources')
-rw-r--r--cloudinit/sources/DataSourceAzure.py23
-rw-r--r--cloudinit/sources/azure/errors.py38
-rw-r--r--cloudinit/sources/azure/kvp.py57
-rw-r--r--cloudinit/sources/helpers/azure.py2
4 files changed, 86 insertions, 34 deletions
diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py
index 969cb376..923644e1 100644
--- a/cloudinit/sources/DataSourceAzure.py
+++ b/cloudinit/sources/DataSourceAzure.py
@@ -25,8 +25,8 @@ from cloudinit.net.dhcp import (
NoDHCPLeaseMissingDhclientError,
)
from cloudinit.net.ephemeral import EphemeralDHCPv4
-from cloudinit.reporting import events, handlers, instantiated_handler_registry
-from cloudinit.sources.azure import errors, identity, imds
+from cloudinit.reporting import events
+from cloudinit.sources.azure import errors, identity, imds, kvp
from cloudinit.sources.helpers import netlink
from cloudinit.sources.helpers.azure import (
DEFAULT_WIRESERVER_ENDPOINT,
@@ -1175,20 +1175,6 @@ class DataSourceAzure(sources.DataSource):
return reprovision_data
@azure_ds_telemetry_reporter
- def _report_failure_to_host(self, error: errors.ReportableError) -> bool:
- """Report failure to host via well-known key."""
- value = error.as_description()
- kvp_handler = instantiated_handler_registry.registered_items.get(
- "telemetry"
- )
- if not isinstance(kvp_handler, handlers.HyperVKvpReportingHandler):
- LOG.debug("KVP handler not enabled, skipping host report.")
- return False
-
- kvp_handler.write_key("PROVISIONING_REPORT", value)
- return True
-
- @azure_ds_telemetry_reporter
def _report_failure(self, error: errors.ReportableError) -> bool:
"""Tells the Azure fabric that provisioning has failed.
@@ -1196,9 +1182,10 @@ class DataSourceAzure(sources.DataSource):
@return: The success status of sending the failure signal.
"""
report_diagnostic_event(
- f"Azure datasource failure occurred: {error.as_description()}",
+ f"Azure datasource failure occurred: {error.as_encoded_report()}",
logger_func=LOG.error,
)
+ kvp.report_failure_to_host(error)
if self._is_ephemeral_networking_up():
try:
@@ -1253,6 +1240,8 @@ class DataSourceAzure(sources.DataSource):
:returns: List of SSH keys, if requested.
"""
+ kvp.report_success_to_host()
+
try:
data = get_metadata_from_fabric(
endpoint=self._wireserver_endpoint,
diff --git a/cloudinit/sources/azure/errors.py b/cloudinit/sources/azure/errors.py
index 1a452635..5c4ad7db 100644
--- a/cloudinit/sources/azure/errors.py
+++ b/cloudinit/sources/azure/errors.py
@@ -8,7 +8,7 @@ import logging
import traceback
from datetime import datetime
from io import StringIO
-from typing import Any, Dict, Optional
+from typing import Any, Dict, List, Optional
from cloudinit import version
from cloudinit.sources.azure import identity
@@ -16,6 +16,22 @@ from cloudinit.sources.azure import identity
LOG = logging.getLogger(__name__)
+def encode_report(
+ data: List[str], delimiter: str = "|", quotechar: str = "'"
+) -> str:
+ """Encode report data with csv."""
+ with StringIO() as io:
+ csv.writer(
+ io,
+ delimiter=delimiter,
+ quotechar=quotechar,
+ quoting=csv.QUOTE_MINIMAL,
+ ).writerow(data)
+
+ # strip trailing \r\n
+ return io.getvalue().rstrip()
+
+
class ReportableError(Exception):
def __init__(
self,
@@ -39,10 +55,11 @@ class ReportableError(Exception):
except Exception as id_error:
self.vm_id = f"failed to read vm id: {id_error!r}"
- def as_description(
- self, *, delimiter: str = "|", quotechar: str = "'"
+ def as_encoded_report(
+ self,
) -> str:
data = [
+ "result=error",
f"reason={self.reason}",
f"agent={self.agent}",
]
@@ -53,18 +70,7 @@ class ReportableError(Exception):
f"documentation_url={self.documentation_url}",
]
- with StringIO() as io:
- csv.writer(
- io,
- delimiter=delimiter,
- quotechar=quotechar,
- quoting=csv.QUOTE_MINIMAL,
- ).writerow(data)
-
- # strip trailing \r\n
- csv_data = io.getvalue().rstrip()
-
- return f"PROVISIONING_ERROR: {csv_data}"
+ return encode_report(data)
def __eq__(self, other) -> bool:
return (
@@ -75,7 +81,7 @@ class ReportableError(Exception):
)
def __repr__(self) -> str:
- return self.as_description()
+ return self.as_encoded_report()
class ReportableErrorUnhandledException(ReportableError):
diff --git a/cloudinit/sources/azure/kvp.py b/cloudinit/sources/azure/kvp.py
new file mode 100644
index 00000000..735c4616
--- /dev/null
+++ b/cloudinit/sources/azure/kvp.py
@@ -0,0 +1,57 @@
+# Copyright (C) 2022 Microsoft Corporation.
+#
+# This file is part of cloud-init. See LICENSE file for license information.
+
+import logging
+from datetime import datetime
+from typing import Optional
+
+from cloudinit import version
+from cloudinit.reporting import handlers, instantiated_handler_registry
+from cloudinit.sources.azure import errors, identity
+
+LOG = logging.getLogger(__name__)
+
+
+def get_kvp_handler() -> Optional[handlers.HyperVKvpReportingHandler]:
+ """Get instantiated KVP telemetry handler."""
+ kvp_handler = instantiated_handler_registry.registered_items.get(
+ "telemetry"
+ )
+ if not isinstance(kvp_handler, handlers.HyperVKvpReportingHandler):
+ return None
+
+ return kvp_handler
+
+
+def report_via_kvp(report: str) -> bool:
+ """Report to host via PROVISIONING_REPORT KVP key."""
+ kvp_handler = get_kvp_handler()
+ if kvp_handler is None:
+ LOG.debug("KVP handler not enabled, skipping host report.")
+ return False
+
+ kvp_handler.write_key("PROVISIONING_REPORT", report)
+ return True
+
+
+def report_failure_to_host(error: errors.ReportableError) -> bool:
+ return report_via_kvp(error.as_encoded_report())
+
+
+def report_success_to_host() -> bool:
+ try:
+ vm_id = identity.query_vm_id()
+ except Exception as id_error:
+ vm_id = f"failed to read vm id: {id_error!r}"
+
+ report = errors.encode_report(
+ [
+ "result=success",
+ f"agent=Cloud-Init/{version.version_string()}",
+ f"timestamp={datetime.utcnow().isoformat()}",
+ f"vm_id={vm_id}",
+ ]
+ )
+
+ return report_via_kvp(report)
diff --git a/cloudinit/sources/helpers/azure.py b/cloudinit/sources/helpers/azure.py
index 2413d6b0..6e5c1f43 100644
--- a/cloudinit/sources/helpers/azure.py
+++ b/cloudinit/sources/helpers/azure.py
@@ -1023,7 +1023,7 @@ def get_metadata_from_fabric(
@azure_ds_telemetry_reporter
def report_failure_to_fabric(endpoint: str, error: "errors.ReportableError"):
shim = WALinuxAgentShim(endpoint=endpoint)
- description = error.as_description()
+ description = error.as_encoded_report()
try:
shim.register_with_azure_and_report_failure(description=description)
finally: