1 /* 2 * Copyright 2018, OpenCensus Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package io.opencensus.resource; 18 19 import com.google.auto.value.AutoValue; 20 import io.opencensus.common.ExperimentalApi; 21 import io.opencensus.internal.DefaultVisibilityForTesting; 22 import io.opencensus.internal.StringUtils; 23 import io.opencensus.internal.Utils; 24 import java.util.Collections; 25 import java.util.HashMap; 26 import java.util.LinkedHashMap; 27 import java.util.List; 28 import java.util.Map; 29 import java.util.Map.Entry; 30 import javax.annotation.Nullable; 31 import javax.annotation.concurrent.Immutable; 32 33 /** 34 * {@link Resource} represents a resource, which capture identifying information about the entities 35 * for which signals (stats or traces) are reported. It further provides a framework for detection 36 * of resource information from the environment and progressive population as signals propagate from 37 * the core instrumentation library to a backend's exporter. 38 * 39 * @since 0.18 40 */ 41 @Immutable 42 @AutoValue 43 @ExperimentalApi 44 public abstract class Resource { 45 @DefaultVisibilityForTesting static final int MAX_LENGTH = 255; 46 private static final String OC_RESOURCE_TYPE_ENV = "OC_RESOURCE_TYPE"; 47 private static final String OC_RESOURCE_LABELS_ENV = "OC_RESOURCE_LABELS"; 48 private static final String LABEL_LIST_SPLITTER = ","; 49 private static final String LABEL_KEY_VALUE_SPLITTER = "="; 50 private static final String ERROR_MESSAGE_INVALID_CHARS = 51 " should be a ASCII string with a length greater than 0 and not exceed " 52 + MAX_LENGTH 53 + " characters."; 54 private static final String ERROR_MESSAGE_INVALID_VALUE = 55 " should be a ASCII string with a length not exceed " + MAX_LENGTH + " characters."; 56 57 @Nullable 58 private static final String ENV_TYPE = parseResourceType(System.getenv(OC_RESOURCE_TYPE_ENV)); 59 60 private static final Map<String, String> ENV_LABEL_MAP = 61 parseResourceLabels(System.getenv(OC_RESOURCE_LABELS_ENV)); 62 Resource()63 Resource() {} 64 65 /** 66 * Returns the type identifier for the resource. 67 * 68 * @return the type identifier for the resource. 69 * @since 0.18 70 */ 71 @Nullable getType()72 public abstract String getType(); 73 74 /** 75 * Returns a map of labels that describe the resource. 76 * 77 * @return a map of labels. 78 * @since 0.18 79 */ getLabels()80 public abstract Map<String, String> getLabels(); 81 82 /** 83 * Returns a {@link Resource}. This resource information is loaded from the OC_RESOURCE_TYPE and 84 * OC_RESOURCE_LABELS environment variables. 85 * 86 * @return a {@code Resource}. 87 * @since 0.18 88 */ createFromEnvironmentVariables()89 public static Resource createFromEnvironmentVariables() { 90 return createInternal(ENV_TYPE, ENV_LABEL_MAP); 91 } 92 93 /** 94 * Returns a {@link Resource}. 95 * 96 * @param type the type identifier for the resource. 97 * @param labels a map of labels that describe the resource. 98 * @return a {@code Resource}. 99 * @throws NullPointerException if {@code labels} is null. 100 * @throws IllegalArgumentException if type or label key or label value is not a valid printable 101 * ASCII string or exceed {@link #MAX_LENGTH} characters. 102 * @since 0.18 103 */ create(@ullable String type, Map<String, String> labels)104 public static Resource create(@Nullable String type, Map<String, String> labels) { 105 return createInternal( 106 type, 107 Collections.unmodifiableMap( 108 new LinkedHashMap<String, String>(Utils.checkNotNull(labels, "labels")))); 109 } 110 111 /** 112 * Returns a {@link Resource} that runs all input resources sequentially and merges their results. 113 * In case a type of label key is already set, the first set value takes precedence. 114 * 115 * @param resources a list of resources. 116 * @return a {@code Resource}. 117 * @since 0.18 118 */ 119 @Nullable mergeResources(List<Resource> resources)120 public static Resource mergeResources(List<Resource> resources) { 121 Resource currentResource = null; 122 for (Resource resource : resources) { 123 currentResource = merge(currentResource, resource); 124 } 125 return currentResource; 126 } 127 createInternal(@ullable String type, Map<String, String> labels)128 private static Resource createInternal(@Nullable String type, Map<String, String> labels) { 129 return new AutoValue_Resource(type, labels); 130 } 131 132 /** 133 * Creates a resource type from the OC_RESOURCE_TYPE environment variable. 134 * 135 * <p>OC_RESOURCE_TYPE: A string that describes the type of the resource prefixed by a domain 136 * namespace, e.g. “kubernetes.io/container”. 137 */ 138 @Nullable parseResourceType(@ullable String rawEnvType)139 static String parseResourceType(@Nullable String rawEnvType) { 140 if (rawEnvType != null && !rawEnvType.isEmpty()) { 141 Utils.checkArgument(isValidAndNotEmpty(rawEnvType), "Type" + ERROR_MESSAGE_INVALID_CHARS); 142 return rawEnvType.trim(); 143 } 144 return rawEnvType; 145 } 146 147 /* 148 * Creates a label map from the OC_RESOURCE_LABELS environment variable. 149 * 150 * <p>OC_RESOURCE_LABELS: A comma-separated list of labels describing the source in more detail, 151 * e.g. “key1=val1,key2=val2”. Domain names and paths are accepted as label keys. Values may be 152 * quoted or unquoted in general. If a value contains whitespaces, =, or " characters, it must 153 * always be quoted. 154 */ parseResourceLabels(@ullable String rawEnvLabels)155 static Map<String, String> parseResourceLabels(@Nullable String rawEnvLabels) { 156 if (rawEnvLabels == null) { 157 return Collections.<String, String>emptyMap(); 158 } else { 159 Map<String, String> labels = new HashMap<String, String>(); 160 String[] rawLabels = rawEnvLabels.split(LABEL_LIST_SPLITTER, -1); 161 for (String rawLabel : rawLabels) { 162 String[] keyValuePair = rawLabel.split(LABEL_KEY_VALUE_SPLITTER, -1); 163 if (keyValuePair.length != 2) { 164 continue; 165 } 166 String key = keyValuePair[0].trim(); 167 String value = keyValuePair[1].trim().replaceAll("^\"|\"$", ""); 168 Utils.checkArgument(isValidAndNotEmpty(key), "Label key" + ERROR_MESSAGE_INVALID_CHARS); 169 Utils.checkArgument(isValid(value), "Label value" + ERROR_MESSAGE_INVALID_VALUE); 170 labels.put(key, value); 171 } 172 return Collections.unmodifiableMap(labels); 173 } 174 } 175 176 /** 177 * Returns a new, merged {@link Resource} by merging two resources. In case of a collision, first 178 * resource takes precedence. 179 */ 180 @Nullable merge(@ullable Resource resource, @Nullable Resource otherResource)181 private static Resource merge(@Nullable Resource resource, @Nullable Resource otherResource) { 182 if (otherResource == null) { 183 return resource; 184 } 185 if (resource == null) { 186 return otherResource; 187 } 188 189 String mergedType = resource.getType() != null ? resource.getType() : otherResource.getType(); 190 Map<String, String> mergedLabelMap = 191 new LinkedHashMap<String, String>(otherResource.getLabels()); 192 193 // Labels from resource overwrite labels from otherResource. 194 for (Entry<String, String> entry : resource.getLabels().entrySet()) { 195 mergedLabelMap.put(entry.getKey(), entry.getValue()); 196 } 197 return createInternal(mergedType, Collections.unmodifiableMap(mergedLabelMap)); 198 } 199 200 /** 201 * Determines whether the given {@code String} is a valid printable ASCII string with a length not 202 * exceed {@link #MAX_LENGTH} characters. 203 * 204 * @param name the name to be validated. 205 * @return whether the name is valid. 206 */ isValid(String name)207 private static boolean isValid(String name) { 208 return name.length() <= MAX_LENGTH && StringUtils.isPrintableString(name); 209 } 210 211 /** 212 * Determines whether the given {@code String} is a valid printable ASCII string with a length 213 * greater than 0 and not exceed {@link #MAX_LENGTH} characters. 214 * 215 * @param name the name to be validated. 216 * @return whether the name is valid. 217 */ isValidAndNotEmpty(String name)218 private static boolean isValidAndNotEmpty(String name) { 219 return !name.isEmpty() && isValid(name); 220 } 221 } 222