imds: expose instance metadata as an io.systemd.Metrics provider
When systemd-imds is invoked as a Varlink service (via the new systemd-imds-metrics.socket), it now acts as an io.systemd.Metrics provider for systemd-report. It connects to systemd-imdsd over the existing io.systemd.InstanceMetadata interface to acquire the real data and re-exposes the detected cloud vendor plus the well-known hostname, region, zone and public IPv4/IPv6 fields as metrics in the io.systemd.InstanceMetadata.* namespace. The metrics logic lives entirely on the client side (imds-tool-metrics.c); systemd-imdsd is unchanged. Each metric is acquired on demand with a blocking call to the daemon, benefiting from its local cache. Fields that are unset or unsupported by the vendor are simply omitted. The metrics socket is statically enabled into sockets.target.wants/.
This commit is contained in:
@@ -69,6 +69,17 @@
|
||||
fields are imported into the local credential store, where they are used to configure and parameterize
|
||||
the system. For details see below.</para></listitem>
|
||||
</itemizedlist>
|
||||
|
||||
<para>In addition, when invoked as a Varlink service (i.e. via socket activation through
|
||||
<filename>systemd-imds-metrics.socket</filename>), <command>systemd-imds</command> acts as an
|
||||
<constant>io.systemd.Metrics</constant> provider for
|
||||
<citerefentry><refentrytitle>systemd-report</refentrytitle><manvolnum>1</manvolnum></citerefentry>. It
|
||||
exposes the detected cloud vendor and the well-known <literal>hostname</literal>,
|
||||
<literal>region</literal>, <literal>zone</literal>, <literal>ipv4-public</literal> and
|
||||
<literal>ipv6-public</literal> fields as metrics in the <literal>io.systemd.InstanceMetadata.</literal>
|
||||
namespace. The data is acquired on demand from
|
||||
<citerefentry><refentrytitle>systemd-imdsd@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
|
||||
benefiting from its local cache.</para>
|
||||
</refsect1>
|
||||
|
||||
<refsect1>
|
||||
@@ -167,6 +178,7 @@
|
||||
<member><citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd-imdsd@.service</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd-imds-generator</refentrytitle><manvolnum>8</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd-report</refentrytitle><manvolnum>1</manvolnum></citerefentry></member>
|
||||
<member><citerefentry><refentrytitle>systemd.system-credentials</refentrytitle><manvolnum>7</manvolnum></citerefentry></member>
|
||||
</simplelist></para>
|
||||
</refsect1>
|
||||
|
||||
@@ -189,6 +189,11 @@ static int run(const char *dest, const char *dest_early, const char *dest_late)
|
||||
return log_error_errno(r, "Failed to hook in systemd-imds-import.service: %m");
|
||||
}
|
||||
|
||||
/* Enable IMDS metrics in case IMDS is supported */
|
||||
r = generator_add_symlink(dest_early, SPECIAL_SOCKETS_TARGET, "wants", SYSTEM_DATA_UNIT_DIR "/systemd-imds-metrics.socket");
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to hook in systemd-imds-metrics.socket: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,164 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "sd-json.h"
|
||||
#include "sd-varlink.h"
|
||||
|
||||
#include "imds-tool.h"
|
||||
#include "imds-tool-metrics.h"
|
||||
#include "imds-util.h"
|
||||
#include "log.h"
|
||||
#include "metrics.h"
|
||||
#include "string-util.h"
|
||||
#include "varlink-io.systemd.Metrics.h"
|
||||
#include "varlink-util.h"
|
||||
|
||||
#define METRIC_IO_SYSTEMD_INSTANCE_METADATA_PREFIX "io.systemd.InstanceMetadata."
|
||||
|
||||
static int metric_vendor_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) {
|
||||
sd_varlink *imds = userdata; /* The live systemd-imdsd connection, or NULL if unavailable */
|
||||
_cleanup_free_ char *vendor = NULL;
|
||||
int r;
|
||||
|
||||
assert(mf && mf->name);
|
||||
assert(vl);
|
||||
|
||||
if (!imds)
|
||||
return 0;
|
||||
|
||||
r = acquire_imds_vendor(imds, &vendor);
|
||||
if (r <= 0)
|
||||
return 0; /* On error or absence simply omit the metric */
|
||||
|
||||
return metric_build_send_string(mf, vl, /* object= */ NULL, vendor, /* fields= */ NULL);
|
||||
}
|
||||
|
||||
static int metric_well_known_build_json(
|
||||
const MetricFamily *mf,
|
||||
sd_varlink *vl,
|
||||
void *userdata,
|
||||
ImdsWellKnown wk) {
|
||||
|
||||
sd_varlink *imds = userdata;
|
||||
_cleanup_free_ char *value = NULL;
|
||||
int r;
|
||||
|
||||
assert(mf && mf->name);
|
||||
assert(vl);
|
||||
|
||||
if (!imds)
|
||||
return 0;
|
||||
|
||||
r = acquire_imds_key_as_string(imds, wk, /* key= */ NULL, &value);
|
||||
if (r <= 0 || isempty(value))
|
||||
return 0; /* Field not supported/set, or error: omit the metric */
|
||||
|
||||
return metric_build_send_string(mf, vl, /* object= */ NULL, value, /* fields= */ NULL);
|
||||
}
|
||||
|
||||
static int metric_hostname_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) {
|
||||
return metric_well_known_build_json(mf, vl, userdata, IMDS_HOSTNAME);
|
||||
}
|
||||
|
||||
static int metric_ipv4_public_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) {
|
||||
return metric_well_known_build_json(mf, vl, userdata, IMDS_IPV4_PUBLIC);
|
||||
}
|
||||
|
||||
static int metric_ipv6_public_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) {
|
||||
return metric_well_known_build_json(mf, vl, userdata, IMDS_IPV6_PUBLIC);
|
||||
}
|
||||
|
||||
static int metric_region_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) {
|
||||
return metric_well_known_build_json(mf, vl, userdata, IMDS_REGION);
|
||||
}
|
||||
|
||||
static int metric_zone_build_json(const MetricFamily *mf, sd_varlink *vl, void *userdata) {
|
||||
return metric_well_known_build_json(mf, vl, userdata, IMDS_ZONE);
|
||||
}
|
||||
|
||||
/* Keep metrics ordered alphabetically */
|
||||
static const MetricFamily imds_metric_family_table[] = {
|
||||
{
|
||||
.name = METRIC_IO_SYSTEMD_INSTANCE_METADATA_PREFIX "Hostname",
|
||||
.description = "Instance hostname reported by IMDS",
|
||||
.type = METRIC_FAMILY_TYPE_STRING,
|
||||
.generate = metric_hostname_build_json,
|
||||
},
|
||||
{
|
||||
.name = METRIC_IO_SYSTEMD_INSTANCE_METADATA_PREFIX "IPv4Public",
|
||||
.description = "Public IPv4 address reported by IMDS",
|
||||
.type = METRIC_FAMILY_TYPE_STRING,
|
||||
.generate = metric_ipv4_public_build_json,
|
||||
},
|
||||
{
|
||||
.name = METRIC_IO_SYSTEMD_INSTANCE_METADATA_PREFIX "IPv6Public",
|
||||
.description = "Public IPv6 address reported by IMDS",
|
||||
.type = METRIC_FAMILY_TYPE_STRING,
|
||||
.generate = metric_ipv6_public_build_json,
|
||||
},
|
||||
{
|
||||
.name = METRIC_IO_SYSTEMD_INSTANCE_METADATA_PREFIX "Region",
|
||||
.description = "Cloud region reported by IMDS",
|
||||
.type = METRIC_FAMILY_TYPE_STRING,
|
||||
.generate = metric_region_build_json,
|
||||
},
|
||||
{
|
||||
.name = METRIC_IO_SYSTEMD_INSTANCE_METADATA_PREFIX "Vendor",
|
||||
.description = "Detected cloud vendor",
|
||||
.type = METRIC_FAMILY_TYPE_STRING,
|
||||
.generate = metric_vendor_build_json,
|
||||
},
|
||||
{
|
||||
.name = METRIC_IO_SYSTEMD_INSTANCE_METADATA_PREFIX "Zone",
|
||||
.description = "Cloud availability zone reported by IMDS",
|
||||
.type = METRIC_FAMILY_TYPE_STRING,
|
||||
.generate = metric_zone_build_json,
|
||||
},
|
||||
{}
|
||||
};
|
||||
|
||||
static int vl_method_metrics_describe(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
|
||||
return metrics_method_describe(imds_metric_family_table, link, parameters, flags, userdata);
|
||||
}
|
||||
|
||||
static int vl_method_metrics_list(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) {
|
||||
int r;
|
||||
|
||||
/* Acquire a single connection to systemd-imdsd, shared by all metric generators (so they benefit
|
||||
* from the daemon's token/data cache). If the daemon is unreachable we still serve an empty list
|
||||
* rather than failing the whole request. */
|
||||
_cleanup_(sd_varlink_unrefp) sd_varlink *imds = NULL;
|
||||
r = connect_imdsd(&imds);
|
||||
if (r < 0)
|
||||
log_debug_errno(r, "Failed to connect to systemd-imdsd, serving empty metrics list: %m");
|
||||
|
||||
return metrics_method_list(imds_metric_family_table, link, parameters, flags, imds);
|
||||
}
|
||||
|
||||
int imds_metrics_run(void) {
|
||||
int r;
|
||||
|
||||
/* Invocation as a Varlink metrics provider (io.systemd.Metrics), typically socket-activated via
|
||||
* /run/systemd/report/io.systemd.InstanceMetadata. */
|
||||
|
||||
_cleanup_(sd_varlink_server_unrefp) sd_varlink_server *server = NULL;
|
||||
r = varlink_server_new(&server, /* flags= */ 0, /* userdata= */ NULL);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to allocate Varlink server: %m");
|
||||
|
||||
r = sd_varlink_server_add_interface(server, &vl_interface_io_systemd_Metrics);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to add Varlink interface: %m");
|
||||
|
||||
r = sd_varlink_server_bind_method_many(
|
||||
server,
|
||||
"io.systemd.Metrics.List", vl_method_metrics_list,
|
||||
"io.systemd.Metrics.Describe", vl_method_metrics_describe);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to bind Varlink methods: %m");
|
||||
|
||||
r = sd_varlink_server_loop_auto(server);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to run Varlink event loop: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
/* Runs systemd-imds as an io.systemd.Metrics provider (socket-activated). */
|
||||
int imds_metrics_run(void);
|
||||
+67
-14
@@ -18,6 +18,8 @@
|
||||
#include "format-util.h"
|
||||
#include "fs-util.h"
|
||||
#include "hexdecoct.h"
|
||||
#include "imds-tool.h"
|
||||
#include "imds-tool-metrics.h"
|
||||
#include "imds-util.h"
|
||||
#include "in-addr-util.h"
|
||||
#include "io-util.h"
|
||||
@@ -232,7 +234,7 @@ static int acquire_imds_key(
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int acquire_imds_key_as_string(
|
||||
int acquire_imds_key_as_string(
|
||||
sd_varlink *link,
|
||||
ImdsWellKnown wk,
|
||||
const char *key,
|
||||
@@ -292,14 +294,11 @@ static int acquire_imds_key_as_ip_address(
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int action_summary(sd_varlink *link) {
|
||||
int acquire_imds_vendor(sd_varlink *link, char **ret) {
|
||||
int r;
|
||||
|
||||
assert(link);
|
||||
|
||||
_cleanup_(table_unrefp) Table *table = table_new_vertical();
|
||||
if (!table)
|
||||
return log_oom();
|
||||
assert(ret);
|
||||
|
||||
const char *error_id = NULL;
|
||||
sd_json_variant *reply = NULL;
|
||||
@@ -311,17 +310,48 @@ static int action_summary(sd_varlink *link) {
|
||||
&error_id);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to issue io.systemd.InstanceMetadata.GetVendorInfo(): %m");
|
||||
if (error_id)
|
||||
if (error_id) {
|
||||
if (streq(error_id, "io.systemd.InstanceMetadata.NotSupported")) {
|
||||
*ret = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
return log_error_errno(sd_varlink_error_to_errno(error_id, reply), "Failed to issue io.systemd.InstanceMetadata.GetVendorInfo(): %s", error_id);
|
||||
}
|
||||
|
||||
const char *vendor = NULL;
|
||||
static const sd_json_dispatch_field dispatch_table[] = {
|
||||
{ "vendor", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, 0, 0 },
|
||||
{ "vendor", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, 0, 0 },
|
||||
{}
|
||||
};
|
||||
r = sd_json_dispatch(reply, dispatch_table, SD_JSON_ALLOW_EXTENSIONS|SD_JSON_LOG, &vendor);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (isempty(vendor)) {
|
||||
*ret = NULL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (strdup_to_full(ret, vendor) < 0)
|
||||
return log_oom();
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int action_summary(sd_varlink *link) {
|
||||
int r;
|
||||
|
||||
assert(link);
|
||||
|
||||
_cleanup_(table_unrefp) Table *table = table_new_vertical();
|
||||
if (!table)
|
||||
return log_oom();
|
||||
|
||||
_cleanup_free_ char *vendor = NULL;
|
||||
r = acquire_imds_vendor(link, &vendor);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
if (vendor) {
|
||||
r = table_add_many(table,
|
||||
TABLE_FIELD, "Vendor",
|
||||
@@ -820,14 +850,10 @@ static int action_import(sd_varlink *link) {
|
||||
return RET_GATHER(ret, import_credentials(data.iov_base));
|
||||
}
|
||||
|
||||
static int run(int argc, char* argv[]) {
|
||||
int connect_imdsd(sd_varlink **ret) {
|
||||
int r;
|
||||
|
||||
log_setup();
|
||||
|
||||
r = parse_argv(argc, argv);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
assert(ret);
|
||||
|
||||
_cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL;
|
||||
r = sd_varlink_connect_address(&link, "/run/systemd/io.systemd.InstanceMetadata");
|
||||
@@ -850,6 +876,33 @@ static int run(int argc, char* argv[]) {
|
||||
return log_error_errno(r, "Failed to connect to imdsd service: %m");
|
||||
}
|
||||
|
||||
*ret = TAKE_PTR(link);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int run(int argc, char* argv[]) {
|
||||
int r;
|
||||
|
||||
log_setup();
|
||||
|
||||
/* When invoked as a Varlink service (socket activation) we act as an io.systemd.Metrics provider
|
||||
* and run a server loop. The metric generators connect to systemd-imdsd on demand, so we never open
|
||||
* a one-shot connection here. */
|
||||
r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m");
|
||||
if (r > 0)
|
||||
return imds_metrics_run();
|
||||
|
||||
r = parse_argv(argc, argv);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
_cleanup_(sd_varlink_unrefp) sd_varlink *link = NULL;
|
||||
r = connect_imdsd(&link);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
switch (arg_action) {
|
||||
|
||||
case ACTION_SUMMARY:
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "imds-util.h"
|
||||
#include "shared-forward.h"
|
||||
|
||||
/* Helpers shared between imds-tool.c and imds-tool-metrics.c for talking to systemd-imdsd. */
|
||||
|
||||
int connect_imdsd(sd_varlink **ret);
|
||||
int acquire_imds_key_as_string(sd_varlink *link, ImdsWellKnown wk, const char *key, char **ret);
|
||||
int acquire_imds_vendor(sd_varlink *link, char **ret);
|
||||
@@ -14,7 +14,9 @@ executables += [
|
||||
libexec_template + {
|
||||
'name' : 'systemd-imds',
|
||||
'public' : true,
|
||||
'sources' : files('imds-tool.c'),
|
||||
'sources' : files(
|
||||
'imds-tool.c',
|
||||
'imds-tool-metrics.c'),
|
||||
'objects' : ['systemd-imdsd'],
|
||||
},
|
||||
generator_template + {
|
||||
|
||||
@@ -413,6 +413,14 @@ units = [
|
||||
'file' : 'systemd-imds-import.service.in',
|
||||
'conditions' : ['ENABLE_IMDS'],
|
||||
},
|
||||
{
|
||||
'file' : 'systemd-imds-metrics.socket',
|
||||
'conditions' : ['ENABLE_IMDS'],
|
||||
},
|
||||
{
|
||||
'file' : 'systemd-imds-metrics@.service.in',
|
||||
'conditions' : ['ENABLE_IMDS'],
|
||||
},
|
||||
{
|
||||
'file' : 'systemd-importd.service.in',
|
||||
'conditions' : ['ENABLE_IMPORTD'],
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# systemd is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
[Unit]
|
||||
Description=Cloud Instance Metadata Metrics
|
||||
Documentation=man:systemd-imds(1)
|
||||
DefaultDependencies=no
|
||||
Before=sockets.target
|
||||
|
||||
[Socket]
|
||||
ListenStream=/run/systemd/report/io.systemd.InstanceMetadata
|
||||
FileDescriptorName=varlink
|
||||
SocketMode=0666
|
||||
Accept=yes
|
||||
MaxConnectionsPerSource=16
|
||||
RemoveOnStop=yes
|
||||
@@ -0,0 +1,25 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
#
|
||||
# This file is part of systemd.
|
||||
#
|
||||
# systemd is free software; you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published by
|
||||
# the Free Software Foundation; either version 2.1 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
[Unit]
|
||||
Description=Cloud Instance Metadata Metrics
|
||||
Documentation=man:systemd-imds(1)
|
||||
DefaultDependencies=no
|
||||
Conflicts=shutdown.target initrd-switch-root.target
|
||||
Before=shutdown.target initrd-switch-root.target
|
||||
After=systemd-imdsd.socket
|
||||
|
||||
[Service]
|
||||
# Runs systemd-imds as an io.systemd.Metrics provider. It connects to
|
||||
# systemd-imdsd to acquire the actual data. Runs as root so the daemon's Polkit
|
||||
# check on io.systemd.InstanceMetadata.Get() passes.
|
||||
ExecStart={{LIBEXECDIR}}/systemd-imds
|
||||
LockPersonality=yes
|
||||
MemoryDenyWriteExecute=yes
|
||||
NoNewPrivileges=yes
|
||||
Reference in New Issue
Block a user