Skip to content

Commit

Permalink
Add Imds data retriever as a service provider (helidon-io#8928)
Browse files Browse the repository at this point in the history
* Add Imds data retriever as a service provider

* Refactor ImdsInstanceInfoProvider to reuse HelidoOci methods and update relevant readmes

* Remove system.out.println

* Remove maven compiler plugin that was erroneously added.

* Update service provider readme to correct a wrong supplier example
  • Loading branch information
klustria authored Jul 3, 2024
1 parent bdac5a7 commit 3ab2ad3
Show file tree
Hide file tree
Showing 10 changed files with 494 additions and 27 deletions.
18 changes: 18 additions & 0 deletions integrations/oci/oci/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,3 +78,21 @@ The following out-of-the-box implementations exists:
- OCI SDK based region provider: uses `Region.registerFromInstanceMetadataService()` to find region (this has timeout of 30
seconds, so if we reach this provider, it may block for a while - but only once); Weight: default - 100

## Instance Metadata Service Instance Information

Any service may need some instance information from the Instance Metadata Service (IMDS) and it can be looked up using
Service Registry lookup methods.

The `IMDS` instance information is discovered by simply using an instance of `io.helidon.integrations.oci.ImdsInstanceInfo`.
The following fields are provided from `io.helidon.integrations.oci.ImdsInstanceInfo`:
- canonicalRegionName
- displayName
- hostName
- region
- ociAdName
- faultDomain
- compartmentId
- tenantId
- A JsonObject containing all the contents of the instance information from IMDS.


27 changes: 27 additions & 0 deletions integrations/oci/oci/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -52,15 +52,32 @@
<groupId>com.oracle.oci.sdk</groupId>
<artifactId>oci-java-sdk-common</artifactId>
</dependency>
<dependency>
<groupId>jakarta.json</groupId>
<artifactId>jakarta.json-api</artifactId>
</dependency>
<dependency>
<groupId>jakarta.ws.rs</groupId>
<artifactId>jakarta.ws.rs-api</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.webclient</groupId>
<artifactId>helidon-webclient</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.http.media</groupId>
<artifactId>helidon-http-media-jsonp</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.common.testing</groupId>
<artifactId>helidon-common-testing-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.helidon.webserver.testing.junit5</groupId>
<artifactId>helidon-webserver-testing-junit5</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
Expand All @@ -76,6 +93,16 @@
<artifactId>slf4j-jdk14</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.helidon.webserver</groupId>
<artifactId>helidon-webserver</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.helidon.webserver.testing.junit5</groupId>
<artifactId>helidon-webserver-testing-junit5</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,14 @@
import java.time.Duration;

import io.helidon.common.media.type.MediaTypes;
import io.helidon.http.Header;
import io.helidon.http.HeaderNames;
import io.helidon.http.HeaderValues;
import io.helidon.http.Status;
import io.helidon.webclient.api.ClientResponseTyped;
import io.helidon.webclient.api.WebClient;

import jakarta.json.JsonObject;

import static io.helidon.integrations.oci.OciConfigSupport.IMDS_HOSTNAME;
import static io.helidon.integrations.oci.OciConfigSupport.IMDS_URI;

Expand All @@ -35,6 +39,7 @@
*/
public final class HelidonOci {
private static final System.Logger LOGGER = System.getLogger(HelidonOci.class.getName());
private static final Header BEARER_HEADER = HeaderValues.create(HeaderNames.AUTHORIZATION, "Bearer Oracle");

private HelidonOci() {
}
Expand All @@ -49,8 +54,7 @@ public static boolean imdsAvailable(OciConfig config) {
Duration timeout = config.imdsTimeout();

try {
URI imdsUri = config.imdsBaseUri()
.orElse(IMDS_URI);
URI imdsUri = imdsUri(config);

if (InetAddress.getByName(imdsUri.getHost())
.isReachable((int) timeout.toMillis())) {
Expand All @@ -68,23 +72,28 @@ public static boolean imdsAvailable(OciConfig config) {

private static boolean imdsAvailable(OciConfig config, URI imdsUri) {
// check if the endpoint is available (we have only checked the host/IP address)
return imdsContent(config, imdsUri) != null;
}

static JsonObject imdsContent(OciConfig config, URI imdsUri) {
int retries = config.imdsDetectRetries().orElse(0);

Exception firstException = null;
Status firstStatus = null;

for (int retry = 0; retry <= retries; retry++) {
try {
ClientResponseTyped<String> response = WebClient.builder()
var response = WebClient.builder()
.connectTimeout(config.imdsTimeout())
.readTimeout(config.imdsTimeout())
.baseUri(imdsUri)
.build()
.get("instance/regionInfo")
.get("instance")
.accept(MediaTypes.APPLICATION_JSON)
.request(String.class);
.header(BEARER_HEADER)
.request();
if (response.status() == Status.OK_200) {
return true;
return response.as(JsonObject.class);
}
firstStatus = firstStatus == null ? response.status() : firstStatus;
} catch (Exception e) {
Expand All @@ -99,6 +108,11 @@ private static boolean imdsAvailable(OciConfig config, URI imdsUri) {
LOGGER.log(Level.TRACE, message, firstException);
}

return false;
return null;
}

static URI imdsUri(OciConfig config) {
return config.imdsBaseUri()
.orElse(IMDS_URI);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/*
* Copyright (c) 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.helidon.integrations.oci;

import io.helidon.builder.api.Option;
import io.helidon.builder.api.Prototype;

import jakarta.json.JsonObject;

/**
* Information about the instance retrieved from Imds.
*/
@Prototype.Blueprint
@Prototype.Configured
interface ImdsInstanceInfoBlueprint {
/**
* Display Name.
*
* @return Display Name of the Instance
*/
@Option.Configured
String displayName();

/**
* Host Name.
*
* @return Host Name of the Instance
*/
@Option.Configured
String hostName();

/**
* Canonical Region Name.
*
* @return Canonical Region Name of where the Instance exists
*/
@Option.Configured
String canonicalRegionName();

/**
* Region Name.
*
* @return Short Region Name of where the Instance exists
*/
@Option.Configured
String region();

/**
* Oci Availability Domain Name.
*
* @return Physical Availaibility Domain Name where the Instance exists
*/
@Option.Configured
String ociAdName();

/**
* Fault Domain Name.
*
* @return Fault Domain Name where the Instance exists
*/
@Option.Configured
String faultDomain();

/**
* Compartment Id.
*
* @return Compartment Id where the Instance was provisioned.
*/
@Option.Configured
String compartmentId();

/**
* Tenant Id.
*
* @return Tenant Id where the Instance was provisioned.
*/
@Option.Configured
String tenantId();

/**
* Instance Data.
*
* @return Full information about the Instance as a {@link jakarta.json.JsonObject}
*/
@Option.Configured
JsonObject jsonObject();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright (c) 2024 Oracle and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.helidon.integrations.oci;

import java.util.Optional;
import java.util.function.Supplier;

import io.helidon.common.LazyValue;
import io.helidon.common.Weight;
import io.helidon.common.Weighted;
import io.helidon.service.registry.Service;

import jakarta.json.JsonObject;

// the type must be fully qualified, as it is code generated
@Service.Provider
@Weight(Weighted.DEFAULT_WEIGHT - 100)
class ImdsInstanceInfoProvider implements Supplier<Optional<io.helidon.integrations.oci.ImdsInstanceInfo>> {

// Commonly used key names for instance data that will be used for the getter methods
static final String DISPLAY_NAME = "displayName";
static final String HOST_NAME = "hostname";
static final String CANONICAL_REGION_NAME = "canonicalRegionName";
static final String OCI_AD_NAME = "ociAdName";
static final String FAULT_DOMAIN = "faultDomain";
static final String REGION = "region";
static final String COMPARTMENT_ID = "compartmentId";
static final String TENANT_ID = "tenantId";

private LazyValue<Optional<ImdsInstanceInfo>> instanceInfo;

ImdsInstanceInfoProvider(Supplier<OciConfig> config) {
this.instanceInfo = LazyValue.create(() -> Optional.ofNullable(loadInstanceMetadata(config.get())));
}

@Override
public Optional<ImdsInstanceInfo> get() {
return instanceInfo.get();
}

ImdsInstanceInfo loadInstanceMetadata(OciConfig ociConfig) {
JsonObject metadataJson = HelidonOci.imdsContent(ociConfig, HelidonOci.imdsUri(ociConfig));
if (metadataJson != null) {
return ImdsInstanceInfo.builder()
.displayName(metadataJson.getString(DISPLAY_NAME))
.hostName(metadataJson.getString(HOST_NAME))
.canonicalRegionName(metadataJson.getString(CANONICAL_REGION_NAME))
.region(metadataJson.getString(REGION))
.ociAdName(metadataJson.getString(OCI_AD_NAME))
.faultDomain(metadataJson.getString(FAULT_DOMAIN))
.compartmentId(metadataJson.getString(COMPARTMENT_ID))
.tenantId(metadataJson.getString(TENANT_ID))
.jsonObject(metadataJson)
.build();
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ interface OciConfigBlueprint {
* <p>
* Known and supported authentication strategies for public OCI:
* <ul>
* <li>{@value #AUTHENTICATION_METHOD_AUTO} - use the list of {@link io.helidon.integrations.oci.OciConfig#allowedAuthenticationMethods()}
* <li>{@value #AUTHENTICATION_METHOD_AUTO} - use the list of
* {@link io.helidon.integrations.oci.OciConfig#allowedAuthenticationMethods()}
* (in the provided order), and choose the first one capable of providing data</li>
* <li>{@value AuthenticationMethodConfig#METHOD} -
* use configuration of the application to obtain values needed to set up connectivity, uses
Expand All @@ -82,8 +83,8 @@ interface OciConfigBlueprint {
String authenticationMethod();

/**
* List of attempted authentication strategies in case {@link io.helidon.integrations.oci.OciConfig#authenticationMethod()} is set
* to {@value #AUTHENTICATION_METHOD_AUTO}.
* List of attempted authentication strategies in case {@link io.helidon.integrations.oci.OciConfig#authenticationMethod()} is
* set to {@value #AUTHENTICATION_METHOD_AUTO}.
* <p>
* In case the list is empty, all available strategies will be tried, ordered by their {@link io.helidon.common.Weight}
*
Expand Down Expand Up @@ -125,7 +126,7 @@ interface OciConfigBlueprint {
* @return the OCI IMDS connection timeout
*/
@Option.Configured
@Option.Default("PT0.1S")
@Option.Default("PT1S")
Duration imdsTimeout();

/**
Expand Down
4 changes: 3 additions & 1 deletion integrations/oci/oci/src/main/java/module-info.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@
requires io.helidon.webclient;

requires oci.java.sdk.common;
requires vavr;

// needed for IMDS (instance metadata service) processing
requires jakarta.json;

exports io.helidon.integrations.oci;
exports io.helidon.integrations.oci.spi;
Expand Down
Loading

0 comments on commit 3ab2ad3

Please sign in to comment.