1
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
43 metadataWaitTime = 120 * time.Second
44
45
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
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
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
110 type Metadata struct {
111 Results []map[string]interface{}
112 }
113
114
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 }
177
View as plain text