From 1ff0f2a26a1a73114aa786b43e4db61c4f8beb59 Mon Sep 17 00:00:00 2001 From: Rahul Patel Date: Mon, 9 Mar 2020 09:30:42 -0700 Subject: [PATCH] add resource type. (#528) * add resource type. * sort attributes in test to fix ci. * add resource keys and update test. --- sdk/resource/resource.go | 72 +++++++++++++++++ sdk/resource/resource_test.go | 126 +++++++++++++++++++++++++++++ sdk/resource/resourcekeys/const.go | 80 ++++++++++++++++++ 3 files changed, 278 insertions(+) create mode 100644 sdk/resource/resource.go create mode 100644 sdk/resource/resource_test.go create mode 100644 sdk/resource/resourcekeys/const.go diff --git a/sdk/resource/resource.go b/sdk/resource/resource.go new file mode 100644 index 00000000000..8e78ed80358 --- /dev/null +++ b/sdk/resource/resource.go @@ -0,0 +1,72 @@ +// Copyright 2020, OpenTelemetry 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 resource provides functionality for resource, which capture +// identifying information about the entities for which signals are exported. +package resource + +import ( + "go.opentelemetry.io/otel/api/core" +) + +// Resource describes an entity about which identifying information and metadata is exposed. +type Resource struct { + labels map[core.Key]core.Value +} + +// New creates a resource from a set of attributes. +// If there are duplicates keys then the first value of the key is preserved. +func New(kvs ...core.KeyValue) *Resource { + res := &Resource{ + labels: map[core.Key]core.Value{}, + } + for _, kv := range kvs { + if _, ok := res.labels[kv.Key]; !ok { + res.labels[kv.Key] = kv.Value + } + } + return res +} + +// Merge creates a new resource by combining resource a and b. +// If there are common key between resource a and b then value from resource a is preserved. +// If one of the resources is nil then the other resource is returned without creating a new one. +func Merge(a, b *Resource) *Resource { + if a == nil { + return b + } + if b == nil { + return a + } + res := &Resource{ + labels: map[core.Key]core.Value{}, + } + for k, v := range b.labels { + res.labels[k] = v + } + // labels from resource a overwrite labels from resource b. + for k, v := range a.labels { + res.labels[k] = v + } + return res +} + +// Attributes returns a copy of attributes from the resource. +func (r Resource) Attributes() []core.KeyValue { + attrs := make([]core.KeyValue, 0, len(r.labels)) + for k, v := range r.labels { + attrs = append(attrs, core.KeyValue{Key: k, Value: v}) + } + return attrs +} diff --git a/sdk/resource/resource_test.go b/sdk/resource/resource_test.go new file mode 100644 index 00000000000..b0f2e6778ee --- /dev/null +++ b/sdk/resource/resource_test.go @@ -0,0 +1,126 @@ +// Copyright 2020, OpenTelemetry 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 resource_test + +import ( + "fmt" + "sort" + "testing" + + "github.com/google/go-cmp/cmp" + + "go.opentelemetry.io/otel/api/core" + "go.opentelemetry.io/otel/sdk/resource" +) + +var ( + kv11 = core.Key("k1").String("v11") + kv12 = core.Key("k1").String("v12") + kv21 = core.Key("k2").String("v21") + kv31 = core.Key("k3").String("v31") + kv41 = core.Key("k4").String("v41") +) + +func TestNew(t *testing.T) { + cases := []struct { + name string + in []core.KeyValue + want []core.KeyValue + }{ + { + name: "New with common key order1", + in: []core.KeyValue{kv11, kv12, kv21}, + want: []core.KeyValue{kv11, kv21}, + }, + { + name: "New with common key order2", + in: []core.KeyValue{kv12, kv11, kv21}, + want: []core.KeyValue{kv12, kv21}, + }, + { + name: "New with nil", + in: nil, + want: []core.KeyValue{}, + }, + } + for _, c := range cases { + t.Run(fmt.Sprintf("case-%s", c.name), func(t *testing.T) { + res := resource.New(c.in...) + if diff := cmp.Diff( + sortedAttributes(res.Attributes()), + sortedAttributes(c.want), + cmp.AllowUnexported(core.Value{})); diff != "" { + t.Fatalf("unwanted result: diff %+v,", diff) + } + }) + } +} + +func TestMerge(t *testing.T) { + cases := []struct { + name string + a, b *resource.Resource + want []core.KeyValue + }{ + { + name: "Merge with no overlap, no nil", + a: resource.New(kv11, kv31), + b: resource.New(kv21, kv41), + want: []core.KeyValue{kv11, kv21, kv31, kv41}, + }, + { + name: "Merge with common key order1", + a: resource.New(kv11), + b: resource.New(kv12, kv21), + want: []core.KeyValue{kv21, kv11}, + }, + { + name: "Merge with common key order2", + a: resource.New(kv12, kv21), + b: resource.New(kv11), + want: []core.KeyValue{kv12, kv21}, + }, + { + name: "Merge with first resource nil", + a: nil, + b: resource.New(kv21), + want: []core.KeyValue{kv21}, + }, + { + name: "Merge with second resource nil", + a: resource.New(kv11), + b: nil, + want: []core.KeyValue{kv11}, + }, + } + for _, c := range cases { + t.Run(fmt.Sprintf("case-%s", c.name), func(t *testing.T) { + res := resource.Merge(c.a, c.b) + if diff := cmp.Diff( + sortedAttributes(res.Attributes()), + sortedAttributes(c.want), + cmp.AllowUnexported(core.Value{})); diff != "" { + t.Fatalf("unwanted result: diff %+v,", diff) + } + }) + } +} + +func sortedAttributes(attrs []core.KeyValue) []core.KeyValue { + sort.Slice(attrs[:], func(i, j int) bool { + return attrs[i].Key < attrs[j].Key + }) + return attrs +} diff --git a/sdk/resource/resourcekeys/const.go b/sdk/resource/resourcekeys/const.go new file mode 100644 index 00000000000..32cefc57d6f --- /dev/null +++ b/sdk/resource/resourcekeys/const.go @@ -0,0 +1,80 @@ +// Copyright 2020, OpenTelemetry 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 resourcekeys contains well known type and label keys for resources. +package resourcekeys // import "go.opentelemetry.io/otel/sdk/resource/resourcekeys" + +// Constants for Service resources. +const ( + // A uniquely identifying name for a Service. + ServiceKeyName = "service.name" + ServiceKeyNamespace = "service.namespace" + ServiceKeyInstanceID = "service.instance.id" + ServiceKeyVersion = "service.version" +) + +// Constants for Library resources. +const ( + // A uniquely identifying name for a Library. + LibraryKeyName = "library.name" + LibraryKeyLanguage = "library.language" + LibraryKeyVersion = "library.version" +) + +// Constants for Kubernetes resources. +const ( + // A uniquely identifying name for the Kubernetes cluster. Kubernetes + // does not have cluster names as an internal concept so this may be + // set to any meaningful value within the environment. For example, + // GKE clusters have a name which can be used for this label. + K8SKeyClusterName = "k8s.cluster.name" + K8SKeyNamespaceName = "k8s.namespace.name" + K8SKeyPodName = "k8s.pod.name" + K8SKeyDeploymentName = "k8s.deployment.name" +) + +// Constants for Container resources. +const ( + // A uniquely identifying name for the Container. + ContainerKeyName = "container.name" + ContainerKeyImageName = "container.image.name" + ContainerKeyImageTag = "container.image.tag" +) + +// Constants for Cloud resources. +const ( + CloudKeyProvider = "cloud.provider" + CloudKeyAccountID = "cloud.account.id" + CloudKeyRegion = "cloud.region" + CloudKeyZone = "cloud.zone" + + // Cloud Providers + CloudProviderAWS = "aws" + CloudProviderGCP = "gcp" + CloudProviderAZURE = "azure" +) + +// Constants for Host resources. +const ( + // A uniquely identifying name for the host. + HostKeyName = "host.name" + + // A hostname as returned by the 'hostname' command on host machine. + HostKeyHostName = "host.hostname" + HostKeyID = "host.id" + HostKeyType = "host.type" + HostKeyImageName = "host.image.name" + HostKeyImageID = "host.image.id" + HostKeyImageVersion = "host.image.version" +)