// Copyright 2017 The Nomulus Authors. All Rights Reserved. // // 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 google.registry.proxy.metric; import static java.nio.charset.StandardCharsets.UTF_8; import com.google.api.services.monitoring.v3.model.MonitoredResource; import com.google.common.collect.ImmutableMap; import com.google.common.io.CharStreams; import google.registry.util.FormattingLogger; import java.io.IOException; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.util.Map; import java.util.function.Function; import javax.inject.Inject; /** * Utility class to obtain labels for monitored resource of type {@code gke_container}. * *

Custom metrics collected by the proxy need to be associated with a {@link MonitoredResource}. * When running on GKE, the type is {@code gke_container}. The labels for this type are used to * group related metrics together, and to avoid out-of-order metrics writes. This class provides a * map of the labels where the values are either read from environment variables (pod and container * related labels) or queried from GCE metadata server (cluster and instance related labels). * * @see * Creating Custom Metrics - Choosing a monitored resource type * @see Monitored * Resource Types - gke_container * @see Storing * and Retrieving Instance Metadata - Getting metadata * @see * Expose Pod Information to Containers Through Environment Variables */ public class MetricParameters { // Environment variable names, defined in the GKE deployment pod spec. static final String NAMESPACE_ID_ENV = "NAMESPACE_ID"; static final String POD_ID_ENV = "POD_ID"; static final String CONTAINER_NAME_ENV = "CONTAINER_NAME"; // GCE metadata server URLs to retrieve instance related information. private static final String GCE_METADATA_URL_BASE = "http://metadata.google.internal/"; static final String PROJECT_ID_PATH = "computeMetadata/v1/project/project-id"; static final String CLUSTER_NAME_PATH = "computeMetadata/v1/instance/attributes/cluster-name"; static final String INSTANCE_ID_PATH = "computeMetadata/v1/instance/id"; static final String ZONE_PATH = "computeMetadata/v1/instance/zone"; private static final FormattingLogger logger = FormattingLogger.getLoggerForCallerClass(); private final Map envVarMap; private final Function connectionFactory; MetricParameters( Map envVarMap, Function connectionFactory) { this.envVarMap = envVarMap; this.connectionFactory = connectionFactory; } @Inject MetricParameters() { this(ImmutableMap.copyOf(System.getenv()), MetricParameters::gceConnectionFactory); } private static HttpURLConnection gceConnectionFactory(String path) { String url = GCE_METADATA_URL_BASE + path; try { HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection(); connection.setRequestMethod("GET"); // The metadata server requires this header to be set when querying from a GCE instance. connection.setRequestProperty("Metadata-Flavor", "Google"); connection.setDoOutput(true); return connection; } catch (IOException e) { logger.warningfmt(e, "Incorrect GCE metadata server URL: %s", url); throw new RuntimeException(e); } } private String readEnvVar(String envVar) { return envVarMap.getOrDefault(envVar, ""); } private String readGceMetadata(String path) { String value = ""; HttpURLConnection connection = connectionFactory.apply(path); try { connection.connect(); int responseCode = connection.getResponseCode(); if (responseCode < 200 || responseCode > 299) { logger.warningfmt( "Got an error response: %d\n%s", responseCode, CharStreams.toString(new InputStreamReader(connection.getErrorStream(), UTF_8))); } else { value = CharStreams.toString(new InputStreamReader(connection.getInputStream(), UTF_8)); } } catch (IOException e) { logger.warningfmt(e, "Cannot obtain GCE metadata from path %s", path); } return value; } public ImmutableMap makeLabelsMap() { // The zone metadata is in the form of "projects//zones/". // We only need the last part after the slash. String fullZone = readGceMetadata(ZONE_PATH); String zone; String[] fullZoneArray = fullZone.split("/", -1); if (fullZoneArray.length < 4) { logger.warningfmt("Zone %s is valid.", fullZone); // This will make the metric report throw, but it happens in a different thread and will not // kill the whole application. zone = ""; } else { zone = fullZoneArray[3]; } return new ImmutableMap.Builder() .put("project_id", readGceMetadata(PROJECT_ID_PATH)) .put("cluster_name", readGceMetadata(CLUSTER_NAME_PATH)) .put("namespace_id", readEnvVar(NAMESPACE_ID_ENV)) .put("instance_id", readGceMetadata(INSTANCE_ID_PATH)) .put("pod_id", readEnvVar(POD_ID_ENV)) .put("container_name", readEnvVar(CONTAINER_NAME_ENV)) .put("zone", zone) .build(); } }