k8s.io/kubernetes@v1.29.3/test/e2e/instrumentation/monitoring/stackdriver_metadata_agent.go (about) 1 /* 2 Copyright 2017 The Kubernetes 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 monitoring 18 19 import ( 20 "context" 21 "encoding/json" 22 "fmt" 23 "io" 24 "reflect" 25 "time" 26 27 v1 "k8s.io/api/core/v1" 28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 29 clientset "k8s.io/client-go/kubernetes" 30 "k8s.io/kubernetes/test/e2e/feature" 31 "k8s.io/kubernetes/test/e2e/framework" 32 e2epod "k8s.io/kubernetes/test/e2e/framework/pod" 33 e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper" 34 instrumentation "k8s.io/kubernetes/test/e2e/instrumentation/common" 35 admissionapi "k8s.io/pod-security-admission/api" 36 37 "github.com/onsi/ginkgo/v2" 38 "golang.org/x/oauth2/google" 39 ) 40 41 const ( 42 // Time to wait after a pod creation for it's metadata to be exported 43 metadataWaitTime = 120 * time.Second 44 45 // MonitoringScope is the scope for Stackdriver Metadata API 46 MonitoringScope = "https://www.googleapis.com/auth/monitoring" 47 ) 48 49 var _ = instrumentation.SIGDescribe("Stackdriver Monitoring", func() { 50 ginkgo.BeforeEach(func() { 51 e2eskipper.SkipUnlessProviderIs("gce", "gke") 52 }) 53 54 f := framework.NewDefaultFramework("stackdriver-monitoring") 55 f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged 56 var kubeClient clientset.Interface 57 58 f.It("should run Stackdriver Metadata Agent", feature.StackdriverMetadataAgent, func(ctx context.Context) { 59 kubeClient = f.ClientSet 60 testAgent(ctx, f, kubeClient) 61 }) 62 }) 63 64 func testAgent(ctx context.Context, f *framework.Framework, kubeClient clientset.Interface) { 65 projectID := framework.TestContext.CloudConfig.ProjectID 66 resourceType := "k8s_container" 67 uniqueContainerName := fmt.Sprintf("test-container-%v", time.Now().Unix()) 68 endpoint := fmt.Sprintf( 69 "https://stackdriver.googleapis.com/v1beta2/projects/%v/resourceMetadata?filter=resource.type%%3D%v+AND+resource.label.container_name%%3D%v", 70 projectID, 71 resourceType, 72 uniqueContainerName) 73 74 oauthClient, err := google.DefaultClient(ctx, MonitoringScope) 75 if err != nil { 76 framework.Failf("Failed to create oauth client: %s", err) 77 } 78 79 // Create test pod with unique name. 80 _ = e2epod.CreateExecPodOrFail(ctx, kubeClient, f.Namespace.Name, uniqueContainerName, func(pod *v1.Pod) { 81 pod.Spec.Containers[0].Name = uniqueContainerName 82 }) 83 ginkgo.DeferCleanup(kubeClient.CoreV1().Pods(f.Namespace.Name).Delete, uniqueContainerName, metav1.DeleteOptions{}) 84 85 // Wait a short amount of time for Metadata Agent to be created and metadata to be exported 86 time.Sleep(metadataWaitTime) 87 88 resp, err := oauthClient.Get(endpoint) 89 if err != nil { 90 framework.Failf("Failed to call Stackdriver Metadata API %s", err) 91 } 92 if resp.StatusCode != 200 { 93 framework.Failf("Stackdriver Metadata API returned error status: %s", resp.Status) 94 } 95 metadataAPIResponse, err := io.ReadAll(resp.Body) 96 if err != nil { 97 framework.Failf("Failed to read response from Stackdriver Metadata API: %s", err) 98 } 99 100 exists, err := verifyPodExists(metadataAPIResponse, uniqueContainerName) 101 if err != nil { 102 framework.Failf("Failed to process response from Stackdriver Metadata API: %s", err) 103 } 104 if !exists { 105 framework.Failf("Missing Metadata for container %q", uniqueContainerName) 106 } 107 } 108 109 // Metadata has the information fetched from Stackdriver metadata API. 110 type Metadata struct { 111 Results []map[string]interface{} 112 } 113 114 // Resource contains the resource type and labels from Stackdriver metadata API. 115 type Resource struct { 116 resourceType string 117 resourceLabels map[string]string 118 } 119 120 func verifyPodExists(response []byte, containerName string) (bool, error) { 121 var metadata Metadata 122 err := json.Unmarshal(response, &metadata) 123 if err != nil { 124 return false, fmt.Errorf("Failed to unmarshall: %w", err) 125 } 126 127 for _, result := range metadata.Results { 128 rawResource, ok := result["resource"] 129 if !ok { 130 return false, fmt.Errorf("No resource entry in response from Stackdriver Metadata API") 131 } 132 resource, err := parseResource(rawResource) 133 if err != nil { 134 return false, fmt.Errorf("No 'resource' label: %w", err) 135 } 136 if resource.resourceType == "k8s_container" && 137 resource.resourceLabels["container_name"] == containerName { 138 return true, nil 139 } 140 } 141 return false, nil 142 } 143 144 func parseResource(resource interface{}) (*Resource, error) { 145 labels := map[string]string{} 146 resourceMap, ok := resource.(map[string]interface{}) 147 if !ok { 148 return nil, fmt.Errorf("Resource entry is of type %s, expected map[string]interface{}", reflect.TypeOf(resource)) 149 } 150 resourceType, ok := resourceMap["type"] 151 if !ok { 152 return nil, fmt.Errorf("Resource entry doesn't have a type specified") 153 } 154 resourceTypeName, ok := resourceType.(string) 155 if !ok { 156 return nil, fmt.Errorf("Resource type is of type %s, expected string", reflect.TypeOf(resourceType)) 157 } 158 resourceLabels, ok := resourceMap["labels"] 159 if !ok { 160 return nil, fmt.Errorf("Resource entry doesn't have any labels specified") 161 } 162 resourceLabelMap, ok := resourceLabels.(map[string]interface{}) 163 if !ok { 164 return nil, fmt.Errorf("Resource labels entry is of type %s, expected map[string]interface{}", reflect.TypeOf(resourceLabels)) 165 } 166 for label, val := range resourceLabelMap { 167 labels[label], ok = val.(string) 168 if !ok { 169 return nil, fmt.Errorf("Resource label %q is of type %s, expected string", label, reflect.TypeOf(val)) 170 } 171 } 172 return &Resource{ 173 resourceType: resourceTypeName, 174 resourceLabels: labels, 175 }, nil 176 }