From 2d1add70f81d754086566d884529e52b8c3ffddb Mon Sep 17 00:00:00 2001 From: minherz Date: Tue, 19 Oct 2021 12:35:28 +0000 Subject: [PATCH] chore: refactor retrieving resource metadata host logic for getting metadata values for monitored resource in MetadataLoader refactor MonitoredResourceUitl to use MetadataLoader --- .../google/cloud/logging/MetadataLoader.java | 204 ++++++++++++++++++ .../cloud/logging/MonitoredResourceUtil.java | 167 ++------------ 2 files changed, 218 insertions(+), 153 deletions(-) create mode 100644 google-cloud-logging/src/main/java/com/google/cloud/logging/MetadataLoader.java diff --git a/google-cloud-logging/src/main/java/com/google/cloud/logging/MetadataLoader.java b/google-cloud-logging/src/main/java/com/google/cloud/logging/MetadataLoader.java new file mode 100644 index 000000000..fdb61f64e --- /dev/null +++ b/google-cloud-logging/src/main/java/com/google/cloud/logging/MetadataLoader.java @@ -0,0 +1,204 @@ +/* + * Copyright 2021 Google LLC + * + * 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 com.google.cloud.logging; + +import com.google.cloud.logging.MonitoredResourceUtil.Label; +import com.google.common.collect.ImmutableMap; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.function.Supplier; + +public final class MetadataLoader { + public static final String ENV_FLEXIBLE = "flex"; + public static final String ENV_STANDARD = "standard"; + + private ResourceTypeEnvironmentGetter getter; + + private final ImmutableMap> labelResolvers = + ImmutableMap.>builder() + .put(Label.ClusterName, () -> getClusterName()) + .put(Label.ConfigurationName, () -> getConfigName()) + .put(Label.ContainerName, () -> getContainerName()) + .put(Label.Env, () -> getEnv()) + .put(Label.FunctionName, () -> getFunctionName()) + .put(Label.InstanceId, () -> getInstanceId()) + .put(Label.InstanceName, () -> getInstanceName()) + .put(Label.CloudRunLocation, () -> getCloudRunLocation()) + .put(Label.GKELocation, () -> getGKELocation()) + .put(Label.ModuleId, () -> getModuleId()) + .put(Label.NamespaceName, () -> getNamespaceName()) + .put(Label.PodName, () -> getPodName()) + .put(Label.ProjectId, () -> getProjectId()) + .put(Label.Region, () -> getRegion()) + .put(Label.RevisionName, () -> getRevisionName()) + .put(Label.ServiceName, () -> getServiceName()) + .put(Label.VersionId, () -> getVersionId()) + .put(Label.Zone, () -> getZone()) + .build(); + + public MetadataLoader(ResourceTypeEnvironmentGetter getter) { + this.getter = getter; + } + + /** + * Loads metadata value for the {@link label} argument. + * + * @param label A resource metadata label of type {@see MonitoredResourceUtil.Label} + * @return A string with metadata value or {@code null} if the label is not supported. + */ + public String getValue(MonitoredResourceUtil.Label label) { + Supplier lambda = labelResolvers.get(label); + if (lambda != null) { + return lambda.get(); + } + return null; + } + + private String getClusterName() { + return getter.getAttribute("instance/attributes/cluster-name"); + } + + private String getConfigName() { + return getter.getEnv("K_CONFIGURATION"); + } + + // due to lack of options to discover container name from within process + // allow users to provide the container name via environment variable + private String getContainerName() { + return getter.getEnv("CONTAINER_NAME"); + } + /** + * Distinguish between Standard and Flexible GAE environments. There is no indicator of the + * environment. The path to the startup-script in the metadata attribute was selected as one of + * the new values that explitly mentioning "flex" and cannot be altered by user (e.g. environment + * variable). The method assumes that the resource type is already identified as {@link + * Resource.AppEngine}. + * + * @return {@link MetadataLoader.ENV_FLEXIBLE} for the Flexible environment and {@link + * MetadataLoader.ENV_STANDARD} for the Standard environment. + */ + private String getEnv() { + String value = getter.getAttribute("instance/attributes/startup-script"); + if (value == "/var/lib/flex/startup_script.sh") { + return ENV_FLEXIBLE; + } + return ENV_STANDARD; + } + + private String getFunctionName() { + String value = getter.getEnv("K_SERVICE"); + if (value == null) { + // keep supporting custom function name if is not provided by default + // for backward compatability only; reconsider removing it after Gen2 + // environment is enrolled for Cloud Function + value = getter.getEnv("FUNCTION_NAME"); + } + return value; + } + + private String getInstanceId() { + return getter.getAttribute("instance/id"); + } + + private String getInstanceName() { + return getter.getAttribute("instance/name"); + } + + private String getCloudRunLocation() { + return getRegion(); + } + + private String getGKELocation() { + return getZone(); + } + + private String getModuleId() { + return getter.getEnv("GAE_SERVICE"); + } + /** + * Heuristic to discover the namespace name of the current environment. There is no determenistic + * way to discover the namespace name of the process. The name is read from the {@link + * K8S_POD_NAMESPACE_PATH} when available or read from a user defined environment variable + * "NAMESPACE_NAME" + * + * @return Namespace name or empty string if the name could not be discovered + */ + private String getNamespaceName() { + String value = ""; + try { + value = + new String( + Files.readAllBytes( + Paths.get("/var/run/secrets/kubernetes.io/serviceaccount/namespace")), + StandardCharsets.UTF_8); + } catch (IOException e) { + // if SA token is not shared the info about namespace is unavailable + // allow users to define the namespace name explicitly + value = getter.getEnv("NAMESPACE_NAME"); + if (value == null) { + value = ""; + } + } + return value; + } + // Kubernetes set hostname of the pod to the pod name by default, however hostname can be override + // in manifest + // allow users to provide the container name via environment variable + private String getPodName() { + String value = getter.getEnv("POD_NAME"); + if (value != null) { + return value; + } + return getter.getEnv("HOSTNAME"); + } + + private String getProjectId() { + return getter.getAttribute("project/project-id"); + } + /** + * Retrieves a region from the qualified region of 'projects/[PROJECT_NUMBER]/regions/[REGION]' + * + * @return region string id + */ + private String getRegion() { + String loc = getter.getAttribute("instance/region"); + return loc.substring(loc.lastIndexOf('/') + 1); + } + + private String getRevisionName() { + return getter.getEnv("K_REVISION"); + } + + private String getServiceName() { + return getter.getEnv("K_SERVICE"); + } + + private String getVersionId() { + return getter.getEnv("GAE_VERSION"); + } + /** + * Retrieves a zone from the qualified zone of 'projects/[PROJECT_NUMBER]/zones/[ZONE]' + * + * @return zone string id + */ + private String getZone() { + String loc = getter.getAttribute("instance/zone"); + return loc.substring(loc.lastIndexOf('/') + 1); + } +} diff --git a/google-cloud-logging/src/main/java/com/google/cloud/logging/MonitoredResourceUtil.java b/google-cloud-logging/src/main/java/com/google/cloud/logging/MonitoredResourceUtil.java index 75517aa9c..098cfbc60 100644 --- a/google-cloud-logging/src/main/java/com/google/cloud/logging/MonitoredResourceUtil.java +++ b/google-cloud-logging/src/main/java/com/google/cloud/logging/MonitoredResourceUtil.java @@ -20,10 +20,6 @@ import com.google.cloud.logging.LogEntry.Builder; import com.google.common.base.Strings; import com.google.common.collect.ImmutableMultimap; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -37,13 +33,8 @@ public class MonitoredResourceUtil { private static final String APPENGINE_LABEL_PREFIX = "appengine.googleapis.com/"; - private static final String CLUSTER_NAME_ATTRIBUTE = "instance/attributes/cluster-name"; - private static final String K8S_POD_NAMESPACE_PATH = - "/var/run/secrets/kubernetes.io/serviceaccount/namespace"; - private static final String FLEX_ENV = "flex"; - private static final String STD_ENV = "standard"; - private enum Label { + protected enum Label { ClusterName("cluster_name"), ConfigurationName("configuration_name"), ContainerName("container_name"), @@ -51,7 +42,8 @@ private enum Label { FunctionName("function_name"), InstanceId("instance_id"), InstanceName("instance_name"), - Location("location"), + CloudRunLocation("location"), + GKELocation("location"), ModuleId("module_id"), NamespaceName("namespace_name"), PodName("pod_name"), @@ -92,29 +84,30 @@ String getKey() { } } - private static ImmutableMultimap resourceTypeWithLabels = + private static final ImmutableMultimap resourceTypeWithLabels = ImmutableMultimap.builder() .putAll(Resource.CloudFunction.getKey(), Label.FunctionName, Label.Region) .putAll( Resource.CloudRun.getKey(), Label.RevisionName, Label.ServiceName, - Label.Location, + Label.CloudRunLocation, Label.ConfigurationName) .putAll( Resource.AppEngine.getKey(), Label.ModuleId, Label.VersionId, Label.Zone, Label.Env) .putAll(Resource.GceInstance.getKey(), Label.InstanceId, Label.Zone) .putAll( Resource.K8sContainer.getKey(), - Label.Location, + Label.GKELocation, Label.ClusterName, Label.NamespaceName, Label.PodName, Label.ContainerName) .build(); - private static Map cachedMonitoredResources = new HashMap<>(); + private static final Map cachedMonitoredResources = new HashMap<>(); private static ResourceTypeEnvironmentGetter getter = new ResourceTypeEnvironmentGetterImpl(); + private static MetadataLoader metadataLoader = new MetadataLoader(getter); private MonitoredResourceUtil() {} @@ -125,6 +118,7 @@ private MonitoredResourceUtil() {} */ protected static void setEnvironmentGetter(ResourceTypeEnvironmentGetter getter) { MonitoredResourceUtil.getter = getter; + MonitoredResourceUtil.metadataLoader = new MetadataLoader(getter); } /** @@ -138,7 +132,7 @@ protected static void setEnvironmentGetter(ResourceTypeEnvironmentGetter getter) */ public static MonitoredResource getResource(String projectId, String resourceType) { if (projectId == null || projectId.trim().isEmpty()) { - projectId = getter.getAttribute("project/project-id"); + projectId = metadataLoader.getValue(Label.ProjectId); } MonitoredResource result = cachedMonitoredResources.get(projectId + "/" + resourceType); @@ -154,7 +148,7 @@ public static MonitoredResource getResource(String projectId, String resourceTyp MonitoredResource.newBuilder(resourceType).addLabel(Label.ProjectId.getKey(), projectId); for (Label label : resourceTypeWithLabels.get(resourceType)) { - String value = getValue(label, resourceType); + String value = metadataLoader.getValue(label); if (value != null) { builder.addLabel(label.getKey(), value); } @@ -189,7 +183,7 @@ private static Resource detectResourceType() { && getter.getEnv("FUNCTION_TARGET") != null) { return Resource.CloudFunction; } - if (getter.getAttribute(CLUSTER_NAME_ATTRIBUTE) != null) { + if (getter.getAttribute("instance/attributes/cluster-name") != null) { return Resource.K8sContainer; } if (getter.getAttribute("instance/preempted") != null @@ -211,144 +205,11 @@ public static List getResourceEnhancers() { return createEnhancers(resourceType); } - @SuppressWarnings("incomplete-switch") - private static String getValue(Label label, String resourceType) { - String value = ""; - - switch (label) { - case ClusterName: - value = getter.getAttribute(CLUSTER_NAME_ATTRIBUTE); - break; - case ConfigurationName: - value = getter.getEnv("K_CONFIGURATION"); - break; - case ContainerName: - // there is no determenistic way to discover name of container - // allow users to define the container name explicitly - value = getter.getEnv("CONTAINER_NAME"); - if (value == null) { - value = ""; - } - break; - case Env: - value = getAppEngineEnvironment(); - break; - case FunctionName: - case ServiceName: - value = getter.getEnv("K_SERVICE"); - if (value == null) { - value = getter.getEnv("FUNCTION_NAME"); - } - break; - case InstanceId: - value = getter.getAttribute("instance/id"); - break; - case InstanceName: - value = getter.getAttribute("instance/name"); - break; - case Location: - if (Resource.CloudFunction.getKey() == resourceType - || Resource.CloudRun.getKey() == resourceType) { - value = getRegion(); - } else { - value = getZone(); - } - break; - case ModuleId: - value = getter.getEnv("GAE_SERVICE"); - break; - case NamespaceName: - value = getK8sNamespace(); - break; - case PodName: - // there is no determenistic way to discover name of container - // by default the pod name is set as pod's hostname - // note that hostname can be overriden in pod manifest or at runtime - value = getter.getEnv("HOSTNAME"); - break; - case Region: - value = getRegion(); - break; - case RevisionName: - value = getter.getEnv("K_REVISION"); - break; - case VersionId: - value = getter.getEnv("GAE_VERSION"); - break; - case Zone: - value = getZone(); - break; - } - - return value; - } - - /** - * Heuristic to discover the namespace name of the current environment. There is no determenistic - * way to discover the namespace name of the process. The name is read from the {@link - * K8S_POD_NAMESPACE_PATH} when available or read from a user defined environment variable - * "NAMESPACE_NAME" - * - * @return Namespace name or empty string if the name could not be discovered - */ - private static String getK8sNamespace() { - String value = ""; - try { - value = - new String(Files.readAllBytes(Paths.get(K8S_POD_NAMESPACE_PATH)), StandardCharsets.UTF_8); - } catch (IOException e) { - // if SA token is not shared the info about namespace is unavailable - // allow users to define the namespace name explicitly - value = getter.getEnv("NAMESPACE_NAME"); - if (value == null) { - value = ""; - } - } - return value; - } - - /** - * Distinguish between Standard and Flexible GAE environments. There is no indicator of the - * environment. The path to the startup-script in the metadata attribute was selected as one of - * the new values that explitly mentioning "flex" and cannot be altered by user (e.g. environment - * variable). The method assumes that the resource type is already identified as {@link - * Resource.AppEngine}. - * - * @return "flex" {@link String} for the Flexible environment and "standard" for the Standard. - */ - private static String getAppEngineEnvironment() { - String value = getter.getAttribute("instance/attributes/startup-script"); - if (value == "/var/lib/flex/startup_script.sh") { - return FLEX_ENV; - } - return STD_ENV; - } - - /** - * Retrieves a region from the qualified region of 'projects/[PROJECT_NUMBER]/regions/[REGION]' - * - * @return region string id - */ - private static String getRegion() { - String loc = getter.getAttribute("instance/region"); - return loc.substring(loc.lastIndexOf('/') + 1); - } - - /** - * Retrieves a zone from the qualified zone of 'projects/[PROJECT_NUMBER]/zones/[ZONE]' - * - * @return zone string id - */ - private static String getZone() { - String loc = getter.getAttribute("instance/zone"); - return loc.substring(loc.lastIndexOf('/') + 1); - } - private static List createEnhancers(Resource resourceType) { List enhancers = new ArrayList<>(2); if (resourceType == Resource.AppEngine) { enhancers.add(new TraceLoggingEnhancer(APPENGINE_LABEL_PREFIX)); - if (getAppEngineEnvironment() == FLEX_ENV) { + if (metadataLoader.getValue(Label.Env) == MetadataLoader.ENV_FLEXIBLE) { enhancers.add( new LabelLoggingEnhancer( APPENGINE_LABEL_PREFIX, Collections.singletonList(Label.InstanceName))); @@ -374,7 +235,7 @@ private static class LabelLoggingEnhancer implements LoggingEnhancer { for (Label labelName : labelNames) { String fullLabelName = (prefix != null) ? prefix + labelName.getKey() : labelName.getKey(); - String labelValue = MonitoredResourceUtil.getValue(labelName, fullLabelName); + String labelValue = metadataLoader.getValue(labelName); if (labelValue != null) { labels.put(fullLabelName, labelValue); }