• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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