/* * Copyright 2018, OpenCensus Authors * * 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.opencensus.resource; import com.google.auto.value.AutoValue; import io.opencensus.common.ExperimentalApi; import io.opencensus.internal.DefaultVisibilityForTesting; import io.opencensus.internal.StringUtils; import io.opencensus.internal.Utils; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; /** * {@link Resource} represents a resource, which capture identifying information about the entities * for which signals (stats or traces) are reported. It further provides a framework for detection * of resource information from the environment and progressive population as signals propagate from * the core instrumentation library to a backend's exporter. * * @since 0.18 */ @Immutable @AutoValue @ExperimentalApi public abstract class Resource { @DefaultVisibilityForTesting static final int MAX_LENGTH = 255; private static final String OC_RESOURCE_TYPE_ENV = "OC_RESOURCE_TYPE"; private static final String OC_RESOURCE_LABELS_ENV = "OC_RESOURCE_LABELS"; private static final String LABEL_LIST_SPLITTER = ","; private static final String LABEL_KEY_VALUE_SPLITTER = "="; private static final String ERROR_MESSAGE_INVALID_CHARS = " should be a ASCII string with a length greater than 0 and not exceed " + MAX_LENGTH + " characters."; private static final String ERROR_MESSAGE_INVALID_VALUE = " should be a ASCII string with a length not exceed " + MAX_LENGTH + " characters."; @Nullable private static final String ENV_TYPE = parseResourceType(System.getenv(OC_RESOURCE_TYPE_ENV)); private static final Map ENV_LABEL_MAP = parseResourceLabels(System.getenv(OC_RESOURCE_LABELS_ENV)); Resource() {} /** * Returns the type identifier for the resource. * * @return the type identifier for the resource. * @since 0.18 */ @Nullable public abstract String getType(); /** * Returns a map of labels that describe the resource. * * @return a map of labels. * @since 0.18 */ public abstract Map getLabels(); /** * Returns a {@link Resource}. This resource information is loaded from the OC_RESOURCE_TYPE and * OC_RESOURCE_LABELS environment variables. * * @return a {@code Resource}. * @since 0.18 */ public static Resource createFromEnvironmentVariables() { return createInternal(ENV_TYPE, ENV_LABEL_MAP); } /** * Returns a {@link Resource}. * * @param type the type identifier for the resource. * @param labels a map of labels that describe the resource. * @return a {@code Resource}. * @throws NullPointerException if {@code labels} is null. * @throws IllegalArgumentException if type or label key or label value is not a valid printable * ASCII string or exceed {@link #MAX_LENGTH} characters. * @since 0.18 */ public static Resource create(@Nullable String type, Map labels) { return createInternal( type, Collections.unmodifiableMap( new LinkedHashMap(Utils.checkNotNull(labels, "labels")))); } /** * Returns a {@link Resource} that runs all input resources sequentially and merges their results. * In case a type of label key is already set, the first set value takes precedence. * * @param resources a list of resources. * @return a {@code Resource}. * @since 0.18 */ @Nullable public static Resource mergeResources(List resources) { Resource currentResource = null; for (Resource resource : resources) { currentResource = merge(currentResource, resource); } return currentResource; } private static Resource createInternal(@Nullable String type, Map labels) { return new AutoValue_Resource(type, labels); } /** * Creates a resource type from the OC_RESOURCE_TYPE environment variable. * *

OC_RESOURCE_TYPE: A string that describes the type of the resource prefixed by a domain * namespace, e.g. “kubernetes.io/container”. */ @Nullable static String parseResourceType(@Nullable String rawEnvType) { if (rawEnvType != null && !rawEnvType.isEmpty()) { Utils.checkArgument(isValidAndNotEmpty(rawEnvType), "Type" + ERROR_MESSAGE_INVALID_CHARS); return rawEnvType.trim(); } return rawEnvType; } /* * Creates a label map from the OC_RESOURCE_LABELS environment variable. * *

OC_RESOURCE_LABELS: A comma-separated list of labels describing the source in more detail, * e.g. “key1=val1,key2=val2”. Domain names and paths are accepted as label keys. Values may be * quoted or unquoted in general. If a value contains whitespaces, =, or " characters, it must * always be quoted. */ static Map parseResourceLabels(@Nullable String rawEnvLabels) { if (rawEnvLabels == null) { return Collections.emptyMap(); } else { Map labels = new HashMap(); String[] rawLabels = rawEnvLabels.split(LABEL_LIST_SPLITTER, -1); for (String rawLabel : rawLabels) { String[] keyValuePair = rawLabel.split(LABEL_KEY_VALUE_SPLITTER, -1); if (keyValuePair.length != 2) { continue; } String key = keyValuePair[0].trim(); String value = keyValuePair[1].trim().replaceAll("^\"|\"$", ""); Utils.checkArgument(isValidAndNotEmpty(key), "Label key" + ERROR_MESSAGE_INVALID_CHARS); Utils.checkArgument(isValid(value), "Label value" + ERROR_MESSAGE_INVALID_VALUE); labels.put(key, value); } return Collections.unmodifiableMap(labels); } } /** * Returns a new, merged {@link Resource} by merging two resources. In case of a collision, first * resource takes precedence. */ @Nullable private static Resource merge(@Nullable Resource resource, @Nullable Resource otherResource) { if (otherResource == null) { return resource; } if (resource == null) { return otherResource; } String mergedType = resource.getType() != null ? resource.getType() : otherResource.getType(); Map mergedLabelMap = new LinkedHashMap(otherResource.getLabels()); // Labels from resource overwrite labels from otherResource. for (Entry entry : resource.getLabels().entrySet()) { mergedLabelMap.put(entry.getKey(), entry.getValue()); } return createInternal(mergedType, Collections.unmodifiableMap(mergedLabelMap)); } /** * Determines whether the given {@code String} is a valid printable ASCII string with a length not * exceed {@link #MAX_LENGTH} characters. * * @param name the name to be validated. * @return whether the name is valid. */ private static boolean isValid(String name) { return name.length() <= MAX_LENGTH && StringUtils.isPrintableString(name); } /** * Determines whether the given {@code String} is a valid printable ASCII string with a length * greater than 0 and not exceed {@link #MAX_LENGTH} characters. * * @param name the name to be validated. * @return whether the name is valid. */ private static boolean isValidAndNotEmpty(String name) { return !name.isEmpty() && isValid(name); } }