merge: branch 'ih/nmcs-oci'

cloud-setup: Add OCI (Oracle Cloud) provider

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/2056
This commit is contained in:
Íñigo Huguet
2024-10-24 14:16:23 +00:00
9 changed files with 289 additions and 0 deletions

1
NEWS
View File

@@ -22,6 +22,7 @@ USE AT YOUR OWN RISK. NOT RECOMMENDED FOR PRODUCTION USE!
* Support automatically adding routes to DNS servers via the * Support automatically adding routes to DNS servers via the
ipv4.routed-dns and ipv6.routed-dns properties; when enabled, each ipv4.routed-dns and ipv6.routed-dns properties; when enabled, each
name server is reached only via the device that specifies it. name server is reached only via the device that specifies it.
* Support OCI in nm-cloud-setup
============================================= =============================================
NetworkManager-1.50 NetworkManager-1.50

View File

@@ -184,6 +184,10 @@
<para><literal>NM_CLOUD_SETUP_ALIYUN</literal>: boolean, whether Alibaba Cloud (Aliyun) support is enabled. Defaults <para><literal>NM_CLOUD_SETUP_ALIYUN</literal>: boolean, whether Alibaba Cloud (Aliyun) support is enabled. Defaults
to <literal>no</literal>.</para> to <literal>no</literal>.</para>
</listitem> </listitem>
<listitem>
<para><literal>NM_CLOUD_SETUP_OCI</literal>: boolean, whether Oracle Cloud (OCI) support is enabled. Defaults
to <literal>no</literal>.</para>
</listitem>
</itemizedlist> </itemizedlist>
</refsect1> </refsect1>
@@ -417,6 +421,34 @@ ln -s /etc/systemd/system/timers.target.wants/nm-cloud-setup.timer /usr/lib/syst
</itemizedlist> </itemizedlist>
</refsect2> </refsect2>
<refsect2>
<title>Oracle Cloud (OCI)</title>
<para>For OCI, the tools tries to fetch configuration from <literal>http://169.254.169.254/</literal>. Currently, it only
configures IPv4 and does nothing about IPv6. It will do the following.</para>
<itemizedlist>
<listitem>
<para>First fetch <literal>http://169.254.169.254/opc/v2/instance</literal> to determine whether the
expected API is present. This determines whether OCI environment is detected and whether to proceed
to configure the host using OCI meta data.</para>
</listitem>
<listitem>
<para>Fetch <literal>http://169.254.169.254/opc/v2/vnics</literal> to get the configuration
for all the VNICs, getting their MAC address, private IP address, gateway and subnet block.</para>
</listitem>
<listitem>
<para>Then nm-cloud-setup iterates over all interfaces for which it could fetch a configuration.
If no ethernet device for the respective MAC address is found, it is skipped.
Also, if the device is currently not activated in NetworkManager or if the currently
activated profile has a user-data <literal>org.freedesktop.nm-cloud-setup.skip=yes</literal>,
it is skipped. Also, there is only one interface and one IP address, the tool does nothing.</para>
<para>Then the tool configures the system like doing for AWS environment. That is, using source based policy routing
with the tables/rules 30200/30400.</para>
</listitem>
</itemizedlist>
</refsect2>
</refsect1> </refsect1>
<refsect1> <refsect1>

View File

@@ -800,6 +800,7 @@ endif
enable_nm_cloud_setup = get_option('nm_cloud_setup') enable_nm_cloud_setup = get_option('nm_cloud_setup')
if enable_nm_cloud_setup if enable_nm_cloud_setup
assert(libcurl_dep.found(), 'nm-cloud-setup requires libcurl library. Use -Dnm_cloud_setup=false to disable it') assert(libcurl_dep.found(), 'nm-cloud-setup requires libcurl library. Use -Dnm_cloud_setup=false to disable it')
assert(jansson_dep.found(), 'nm-cloud-setup requires jansson library. Use -Dnm_cloud_setup=false to disable it')
endif endif
enable_docs = get_option('docs') enable_docs = get_option('docs')

View File

@@ -11,6 +11,7 @@
#include "nmcs-provider-gcp.h" #include "nmcs-provider-gcp.h"
#include "nmcs-provider-azure.h" #include "nmcs-provider-azure.h"
#include "nmcs-provider-aliyun.h" #include "nmcs-provider-aliyun.h"
#include "nmcs-provider-oci.h"
#include "libnm-core-aux-intern/nm-libnm-core-utils.h" #include "libnm-core-aux-intern/nm-libnm-core-utils.h"
/*****************************************************************************/ /*****************************************************************************/
@@ -104,6 +105,7 @@ _provider_detect(SigTermData *sigterm_data)
NMCS_TYPE_PROVIDER_GCP, NMCS_TYPE_PROVIDER_GCP,
NMCS_TYPE_PROVIDER_AZURE, NMCS_TYPE_PROVIDER_AZURE,
NMCS_TYPE_PROVIDER_ALIYUN, NMCS_TYPE_PROVIDER_ALIYUN,
NMCS_TYPE_PROVIDER_OCI,
}; };
int i; int i;
gulong cancellable_signal_id; gulong cancellable_signal_id;

View File

@@ -36,12 +36,14 @@ libnm_cloud_setup_core = static_library(
'nmcs-provider-gcp.c', 'nmcs-provider-gcp.c',
'nmcs-provider-azure.c', 'nmcs-provider-azure.c',
'nmcs-provider-aliyun.c', 'nmcs-provider-aliyun.c',
'nmcs-provider-oci.c',
'nmcs-provider.c', 'nmcs-provider.c',
), ),
dependencies: [ dependencies: [
libnm_dep, libnm_dep,
glib_dep, glib_dep,
libcurl_dep, libcurl_dep,
jansson_dep,
], ],
) )

View File

@@ -12,6 +12,7 @@
#define NMCS_ENV_NM_CLOUD_SETUP_AZURE "NM_CLOUD_SETUP_AZURE" #define NMCS_ENV_NM_CLOUD_SETUP_AZURE "NM_CLOUD_SETUP_AZURE"
#define NMCS_ENV_NM_CLOUD_SETUP_EC2 "NM_CLOUD_SETUP_EC2" #define NMCS_ENV_NM_CLOUD_SETUP_EC2 "NM_CLOUD_SETUP_EC2"
#define NMCS_ENV_NM_CLOUD_SETUP_GCP "NM_CLOUD_SETUP_GCP" #define NMCS_ENV_NM_CLOUD_SETUP_GCP "NM_CLOUD_SETUP_GCP"
#define NMCS_ENV_NM_CLOUD_SETUP_OCI "NM_CLOUD_SETUP_OCI"
#define NMCS_ENV_NM_CLOUD_SETUP_LOG "NM_CLOUD_SETUP_LOG" #define NMCS_ENV_NM_CLOUD_SETUP_LOG "NM_CLOUD_SETUP_LOG"
/* Undocumented/internal environment variables for configuring nm-cloud-setup. /* Undocumented/internal environment variables for configuring nm-cloud-setup.
@@ -20,6 +21,7 @@
#define NMCS_ENV_NM_CLOUD_SETUP_AZURE_HOST "NM_CLOUD_SETUP_AZURE_HOST" #define NMCS_ENV_NM_CLOUD_SETUP_AZURE_HOST "NM_CLOUD_SETUP_AZURE_HOST"
#define NMCS_ENV_NM_CLOUD_SETUP_EC2_HOST "NM_CLOUD_SETUP_EC2_HOST" #define NMCS_ENV_NM_CLOUD_SETUP_EC2_HOST "NM_CLOUD_SETUP_EC2_HOST"
#define NMCS_ENV_NM_CLOUD_SETUP_GCP_HOST "NM_CLOUD_SETUP_GCP_HOST" #define NMCS_ENV_NM_CLOUD_SETUP_GCP_HOST "NM_CLOUD_SETUP_GCP_HOST"
#define NMCS_ENV_NM_CLOUD_SETUP_OCI_HOST "NM_CLOUD_SETUP_OCI_HOST"
#define NMCS_ENV_NM_CLOUD_SETUP_MAP_INTERFACES "NM_CLOUD_SETUP_MAP_INTERFACES" #define NMCS_ENV_NM_CLOUD_SETUP_MAP_INTERFACES "NM_CLOUD_SETUP_MAP_INTERFACES"
/*****************************************************************************/ /*****************************************************************************/

View File

@@ -31,6 +31,7 @@ ExecStart=@libexecdir@/nm-cloud-setup
#Environment=NM_CLOUD_SETUP_GCP=yes #Environment=NM_CLOUD_SETUP_GCP=yes
#Environment=NM_CLOUD_SETUP_AZURE=yes #Environment=NM_CLOUD_SETUP_AZURE=yes
#Environment=NM_CLOUD_SETUP_ALIYUN=yes #Environment=NM_CLOUD_SETUP_ALIYUN=yes
#Environment=NM_CLOUD_SETUP_OCI=yes
CapabilityBoundingSet= CapabilityBoundingSet=
KeyringMode=private KeyringMode=private

View File

@@ -0,0 +1,221 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "libnm-client-aux-extern/nm-default-client.h"
#include "nmcs-provider-oci.h"
#include "nm-cloud-setup-utils.h"
#include "libnm-glib-aux/nm-jansson.h"
/*****************************************************************************/
#define HTTP_TIMEOUT_MS 3000
#define NM_OCI_HEADER "Authorization:Bearer Oracle"
#define NM_OCI_HOST "169.254.169.254"
#define NM_OCI_BASE "http://" NM_OCI_HOST
NMCS_DEFINE_HOST_BASE(_oci_base, NMCS_ENV_NM_CLOUD_SETUP_OCI_HOST, NM_OCI_BASE);
#define _oci_uri_concat(...) nmcs_utils_uri_build_concat(_oci_base(), "opc/v2/", __VA_ARGS__)
/*****************************************************************************/
struct _NMCSProviderOCI {
NMCSProvider parent;
};
struct _NMCSProviderOCIClass {
NMCSProviderClass parent;
};
G_DEFINE_TYPE(NMCSProviderOCI, nmcs_provider_oci, NMCS_TYPE_PROVIDER);
/*****************************************************************************/
static void
_detect_done_cb(GObject *source, GAsyncResult *result, gpointer user_data)
{
gs_unref_object GTask *task = user_data;
gs_free_error GError *get_error = NULL;
gs_free_error GError *error = NULL;
nm_http_client_poll_req_finish(NM_HTTP_CLIENT(source), result, NULL, NULL, &get_error);
if (nm_utils_error_is_cancelled(get_error)) {
g_task_return_error(task, g_steal_pointer(&get_error));
return;
}
if (get_error) {
nm_utils_error_set(&error,
NM_UTILS_ERROR_UNKNOWN,
"failure to get OCI instance data: %s",
get_error->message);
g_task_return_error(task, g_steal_pointer(&error));
return;
}
g_task_return_boolean(task, TRUE);
}
static void
detect(NMCSProvider *provider, GTask *task)
{
NMHttpClient *http_client;
gs_free char *uri = NULL;
http_client = nmcs_provider_get_http_client(provider);
nm_http_client_poll_req(http_client,
(uri = _oci_uri_concat("instance")),
HTTP_TIMEOUT_MS,
256 * 1024,
7000,
1000,
NM_MAKE_STRV(NM_OCI_HEADER),
NULL,
g_task_get_cancellable(task),
NULL,
NULL,
_detect_done_cb,
task);
}
/*****************************************************************************/
static void
_get_config_done_cb(GObject *source, GAsyncResult *result, gpointer user_data)
{
NMCSProviderGetConfigTaskData *get_config_data;
NMCSProviderGetConfigIfaceData *config_iface_data;
gs_unref_bytes GBytes *response = NULL;
gs_free_error GError *error = NULL;
nm_auto_decref_json json_t *vnics = NULL;
size_t i;
nm_http_client_poll_req_finish(NM_HTTP_CLIENT(source), result, NULL, &response, &error);
if (nm_utils_error_is_cancelled(error))
return;
get_config_data = user_data;
if (error)
goto out;
vnics = json_loads(g_bytes_get_data(response, NULL), JSON_REJECT_DUPLICATES, NULL);
if (!vnics || !json_is_array(vnics)) {
nm_utils_error_set(&error,
NM_UTILS_ERROR_UNKNOWN,
"get-config: JSON parse failure, can't configure VNICs");
goto out;
}
for (i = 0; i < json_array_size(vnics); i++) {
json_t *vnic, *field;
const char *vnic_id, *val;
gs_free char *mac = NULL;
in_addr_t addr;
int prefix;
vnic = json_array_get(vnics, i);
if (!json_is_object(vnic)) {
_LOGW("get-config: JSON parse failure for VNIC at index %zu, ignoring VNIC", i);
continue;
}
field = json_object_get(vnic, "vnicId");
vnic_id = field && json_is_string(field) ? json_string_value(field) : "";
field = json_object_get(vnic, "macAddr");
val = field && json_is_string(field) ? json_string_value(field) : NULL;
if (!val) {
_LOGW("get-config: missing or invalid 'macAddr' (VNIC %s idx=%zu), ignoring VNIC",
vnic_id,
i);
continue;
}
mac = nmcs_utils_hwaddr_normalize(val, json_string_length(field));
config_iface_data = nmcs_provider_get_config_iface_data_create(get_config_data, FALSE, mac);
config_iface_data->iface_idx = i;
field = json_object_get(vnic, "privateIp");
val = field && json_is_string(field) ? json_string_value(field) : NULL;
if (val && nm_inet_parse_bin(AF_INET, val, NULL, &addr)) {
config_iface_data->has_ipv4s = TRUE;
config_iface_data->ipv4s_len = 1;
config_iface_data->ipv4s_arr = g_new(in_addr_t, 1);
config_iface_data->ipv4s_arr[0] = addr;
} else {
_LOGW("get-config: missing or invalid 'privateIp' (VNIC %s idx=%zu)", vnic_id, i);
}
field = json_object_get(vnic, "virtualRouterIp");
val = field && json_is_string(field) ? json_string_value(field) : NULL;
if (val && nm_inet_parse_bin(AF_INET, val, NULL, &addr)) {
config_iface_data->has_gateway = TRUE;
config_iface_data->gateway = addr;
} else {
_LOGW("get-config: missing or invalid 'virtualRouterIp' (VNIC %s idx=%zu)", vnic_id, i);
}
field = json_object_get(vnic, "subnetCidrBlock");
val = field && json_is_string(field) ? json_string_value(field) : NULL;
if (val && nm_inet_parse_with_prefix_bin(AF_INET, val, NULL, &addr, &prefix)) {
config_iface_data->has_cidr = TRUE;
config_iface_data->cidr_addr = addr;
config_iface_data->cidr_prefix = prefix;
} else {
_LOGW("get-config: missing or invalid 'subnetCidrBlock' (VNIC %s idx=%zu)", vnic_id, i);
}
}
out:
_nmcs_provider_get_config_task_maybe_return(get_config_data, g_steal_pointer(&error));
}
static void
get_config(NMCSProvider *provider, NMCSProviderGetConfigTaskData *get_config_data)
{
gs_free const char *uri = NULL;
nm_http_client_poll_req(nmcs_provider_get_http_client(provider),
(uri = _oci_uri_concat("vnics")),
HTTP_TIMEOUT_MS,
256 * 1024,
15000,
1000,
NM_MAKE_STRV(NM_OCI_HEADER),
NULL,
get_config_data->intern_cancellable,
NULL,
NULL,
_get_config_done_cb,
get_config_data);
}
/*****************************************************************************/
static void
nmcs_provider_oci_init(NMCSProviderOCI *self)
{}
static void
dispose(GObject *object)
{
G_OBJECT_CLASS(nmcs_provider_oci_parent_class)->dispose(object);
}
static void
nmcs_provider_oci_class_init(NMCSProviderOCIClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS(klass);
NMCSProviderClass *provider_class = NMCS_PROVIDER_CLASS(klass);
object_class->dispose = dispose;
provider_class->_name = "oci";
provider_class->_env_provider_enabled = NMCS_ENV_NM_CLOUD_SETUP_OCI;
provider_class->detect = detect;
provider_class->get_config = get_config;
}

View File

@@ -0,0 +1,27 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#ifndef __NMCS_PROVIDER_OCI_H__
#define __NMCS_PROVIDER_OCI_H__
#include "nmcs-provider.h"
/*****************************************************************************/
typedef struct _NMCSProviderOCI NMCSProviderOCI;
typedef struct _NMCSProviderOCIClass NMCSProviderOCIClass;
#define NMCS_TYPE_PROVIDER_OCI (nmcs_provider_oci_get_type())
#define NMCS_PROVIDER_OCI(obj) \
(_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NMCS_TYPE_PROVIDER_OCI, NMCSProviderOCI))
#define NMCS_PROVIDER_OCI_CLASS(klass) \
(G_TYPE_CHECK_CLASS_CAST((klass), NMCS_TYPE_PROVIDER_OCI, NMCSProviderOCIClass))
#define NMCS_IS_PROVIDER_OCI(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NMCS_TYPE_PROVIDER_OCI))
#define NMCS_IS_PROVIDER_OCI_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NMCS_TYPE_PROVIDER_OCI))
#define NMCS_PROVIDER_OCI_GET_CLASS(obj) \
(G_TYPE_INSTANCE_GET_CLASS((obj), NMCS_TYPE_PROVIDER_OCI, NMCSProviderOCIClass))
GType nmcs_provider_oci_get_type(void);
/*****************************************************************************/
#endif /* __NMCS_PROVIDER_OCI_H__ */