github.com/caesarxuchao/heapster@v1.1.0/integration/heapster_api_test.go (about) 1 // Copyright 2015 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package integration 16 17 import ( 18 "encoding/json" 19 "flag" 20 "fmt" 21 "io/ioutil" 22 "os" 23 "strings" 24 "testing" 25 "time" 26 27 "github.com/golang/glog" 28 "github.com/stretchr/testify/require" 29 api_v1 "k8s.io/heapster/metrics/api/v1/types" 30 metrics_api "k8s.io/heapster/metrics/apis/metrics/v1alpha1" 31 "k8s.io/heapster/metrics/core" 32 kube_api "k8s.io/kubernetes/pkg/api" 33 apiErrors "k8s.io/kubernetes/pkg/api/errors" 34 kube_api_unv "k8s.io/kubernetes/pkg/api/unversioned" 35 kube_v1 "k8s.io/kubernetes/pkg/api/v1" 36 ) 37 38 const ( 39 targetTags = "kubernetes-minion" 40 heapsterBuildDir = "../deploy/docker" 41 ) 42 43 var ( 44 testZone = flag.String("test_zone", "us-central1-b", "GCE zone where the test will be executed") 45 kubeVersions = flag.String("kube_versions", "", "Comma separated list of kube versions to test against. By default will run the test against an existing cluster") 46 heapsterControllerFile = flag.String("heapster_controller", "../deploy/kube-config/standalone-test/heapster-controller.yaml", "Path to heapster replication controller file.") 47 heapsterServiceFile = flag.String("heapster_service", "../deploy/kube-config/standalone-test/heapster-service.yaml", "Path to heapster service file.") 48 heapsterImage = flag.String("heapster_image", "heapster:e2e_test", "heapster docker image that needs to be tested.") 49 avoidBuild = flag.Bool("nobuild", false, "When true, a new heapster docker image will not be created and pushed to test cluster nodes.") 50 namespace = flag.String("namespace", "heapster-e2e-tests", "namespace to be used for testing, it will be deleted at the beginning of the test if exists") 51 maxRetries = flag.Int("retries", 20, "Number of attempts before failing this test.") 52 runForever = flag.Bool("run_forever", false, "If true, the tests are run in a loop forever.") 53 ) 54 55 func deleteAll(fm kubeFramework, ns string, service *kube_api.Service, rc *kube_api.ReplicationController) error { 56 glog.V(2).Infof("Deleting ns %s...", ns) 57 err := fm.DeleteNs(ns) 58 if err != nil { 59 glog.V(2).Infof("Failed to delete %s", ns) 60 return err 61 } 62 glog.V(2).Infof("Deleted ns %s.", ns) 63 return nil 64 } 65 66 func createAll(fm kubeFramework, ns string, service **kube_api.Service, rc **kube_api.ReplicationController) error { 67 glog.V(2).Infof("Creating ns %s...", ns) 68 namespace := kube_api.Namespace{ 69 TypeMeta: kube_api_unv.TypeMeta{ 70 Kind: "Namespace", 71 APIVersion: "v1", 72 }, 73 ObjectMeta: kube_api.ObjectMeta{ 74 Name: ns, 75 }, 76 } 77 if _, err := fm.CreateNs(&namespace); err != nil { 78 glog.V(2).Infof("Failed to create ns: %v", err) 79 return err 80 } 81 82 glog.V(2).Infof("Created ns %s.", ns) 83 84 glog.V(2).Infof("Creating rc %s/%s...", ns, (*rc).Name) 85 if newRc, err := fm.CreateRC(ns, *rc); err != nil { 86 glog.V(2).Infof("Failed to create rc: %v", err) 87 return err 88 } else { 89 *rc = newRc 90 } 91 glog.V(2).Infof("Created rc %s/%s.", ns, (*rc).Name) 92 93 glog.V(2).Infof("Creating service %s/%s...", ns, (*service).Name) 94 if newSvc, err := fm.CreateService(ns, *service); err != nil { 95 glog.V(2).Infof("Failed to create service: %v", err) 96 return err 97 } else { 98 *service = newSvc 99 } 100 glog.V(2).Infof("Created service %s/%s.", ns, (*service).Name) 101 102 return nil 103 } 104 105 func removeHeapsterImage(fm kubeFramework, zone string) error { 106 glog.V(2).Infof("Removing heapster image.") 107 if err := removeDockerImage(*heapsterImage); err != nil { 108 glog.Errorf("Failed to remove Heapster image: %v", err) 109 } else { 110 glog.V(2).Infof("Heapster image removed.") 111 } 112 if nodes, err := fm.GetNodes(); err == nil { 113 for _, node := range nodes { 114 host := strings.Split(node, ".")[0] 115 cleanupRemoteHost(host, zone) 116 } 117 } else { 118 glog.Errorf("failed to cleanup nodes - %v", err) 119 } 120 return nil 121 } 122 123 func buildAndPushHeapsterImage(hostnames []string, zone string) error { 124 glog.V(2).Info("Building and pushing Heapster image...") 125 curwd, err := os.Getwd() 126 if err != nil { 127 return err 128 } 129 if err := os.Chdir(heapsterBuildDir); err != nil { 130 return err 131 } 132 if err := buildDockerImage(*heapsterImage); err != nil { 133 return err 134 } 135 for _, host := range hostnames { 136 if err := copyDockerImage(*heapsterImage, host, zone); err != nil { 137 return err 138 } 139 } 140 glog.V(2).Info("Heapster image pushed.") 141 return os.Chdir(curwd) 142 } 143 144 func getHeapsterRcAndSvc(fm kubeFramework) (*kube_api.Service, *kube_api.ReplicationController, error) { 145 // Add test docker image 146 rc, err := fm.ParseRC(*heapsterControllerFile) 147 if err != nil { 148 return nil, nil, fmt.Errorf("failed to parse heapster controller - %v", err) 149 } 150 for i := range rc.Spec.Template.Spec.Containers { 151 rc.Spec.Template.Spec.Containers[i].Image = *heapsterImage 152 rc.Spec.Template.Spec.Containers[i].ImagePullPolicy = kube_api.PullNever 153 // increase logging level 154 rc.Spec.Template.Spec.Containers[i].Env = append(rc.Spec.Template.Spec.Containers[0].Env, kube_api.EnvVar{Name: "FLAGS", Value: "--vmodule=*=3"}) 155 } 156 157 svc, err := fm.ParseService(*heapsterServiceFile) 158 if err != nil { 159 return nil, nil, fmt.Errorf("failed to parse heapster service - %v", err) 160 } 161 162 return svc, rc, nil 163 } 164 165 func buildAndPushDockerImages(fm kubeFramework, zone string) error { 166 if *avoidBuild { 167 return nil 168 } 169 nodes, err := fm.GetNodes() 170 if err != nil { 171 return err 172 } 173 hostnames := []string{} 174 for _, node := range nodes { 175 hostnames = append(hostnames, strings.Split(node, ".")[0]) 176 } 177 178 return buildAndPushHeapsterImage(hostnames, zone) 179 } 180 181 const ( 182 metricsEndpoint = "/api/v1/metric-export" 183 metricsSchemaEndpoint = "/api/v1/metric-export-schema" 184 ) 185 186 func getTimeseries(fm kubeFramework, svc *kube_api.Service) ([]*api_v1.Timeseries, error) { 187 body, err := fm.Client().Get(). 188 Namespace(svc.Namespace). 189 Prefix("proxy"). 190 Resource("services"). 191 Name(svc.Name). 192 Suffix(metricsEndpoint). 193 Do().Raw() 194 if err != nil { 195 return nil, err 196 } 197 var timeseries []*api_v1.Timeseries 198 if err := json.Unmarshal(body, ×eries); err != nil { 199 glog.V(2).Infof("Timeseries error: %v %v", err, string(body)) 200 return nil, err 201 } 202 return timeseries, nil 203 } 204 205 func getSchema(fm kubeFramework, svc *kube_api.Service) (*api_v1.TimeseriesSchema, error) { 206 body, err := fm.Client().Get(). 207 Namespace(svc.Namespace). 208 Prefix("proxy"). 209 Resource("services"). 210 Name(svc.Name). 211 Suffix(metricsSchemaEndpoint). 212 Do().Raw() 213 if err != nil { 214 return nil, err 215 } 216 var timeseriesSchema api_v1.TimeseriesSchema 217 if err := json.Unmarshal(body, ×eriesSchema); err != nil { 218 glog.V(2).Infof("Metrics schema error: %v %v", err, string(body)) 219 return nil, err 220 } 221 return ×eriesSchema, nil 222 } 223 224 var expectedSystemContainers = map[string]struct{}{ 225 "machine": {}, 226 "kubelet": {}, 227 "kube-proxy": {}, 228 "system": {}, 229 "docker-daemon": {}, 230 } 231 232 func isContainerBaseImageExpected(ts *api_v1.Timeseries) bool { 233 _, exists := expectedSystemContainers[ts.Labels[core.LabelContainerName.Key]] 234 return !exists 235 } 236 237 func runMetricExportTest(fm kubeFramework, svc *kube_api.Service) error { 238 expectedPods, err := fm.GetRunningPodNames() 239 if err != nil { 240 return err 241 } 242 glog.V(0).Infof("Expected pods: %v", expectedPods) 243 244 expectedNodes, err := fm.GetNodes() 245 if err != nil { 246 return err 247 } 248 glog.V(0).Infof("Expected nodes: %v", expectedNodes) 249 250 timeseries, err := getTimeseries(fm, svc) 251 if err != nil { 252 return err 253 } 254 if len(timeseries) == 0 { 255 return fmt.Errorf("expected non zero timeseries") 256 } 257 schema, err := getSchema(fm, svc) 258 if err != nil { 259 return err 260 } 261 // Build a map of metric names to metric descriptors. 262 mdMap := map[string]*api_v1.MetricDescriptor{} 263 for idx := range schema.Metrics { 264 mdMap[schema.Metrics[idx].Name] = &schema.Metrics[idx] 265 } 266 actualPods := map[string]bool{} 267 actualNodes := map[string]bool{} 268 actualSystemContainers := map[string]map[string]struct{}{} 269 for _, ts := range timeseries { 270 // Verify the relevant labels are present. 271 // All common labels must be present. 272 podName, podMetric := ts.Labels[core.LabelPodName.Key] 273 274 for _, label := range core.CommonLabels() { 275 _, exists := ts.Labels[label.Key] 276 if !exists { 277 return fmt.Errorf("timeseries: %v does not contain common label: %v", ts, label) 278 } 279 } 280 if podMetric { 281 for _, label := range core.PodLabels() { 282 _, exists := ts.Labels[label.Key] 283 if !exists { 284 return fmt.Errorf("timeseries: %v does not contain pod label: %v", ts, label) 285 } 286 } 287 } 288 289 if podMetric { 290 actualPods[podName] = true 291 // Extra explicit check that the expecte metrics are there: 292 requiredLabels := []string{ 293 core.LabelPodNamespaceUID.Key, 294 core.LabelPodId.Key, 295 core.LabelHostID.Key, 296 // container name is checked later 297 } 298 for _, label := range requiredLabels { 299 _, exists := ts.Labels[label] 300 if !exists { 301 return fmt.Errorf("timeseries: %v does not contain required label: %v", ts, label) 302 } 303 } 304 305 } else { 306 if cName, ok := ts.Labels[core.LabelContainerName.Key]; ok { 307 hostname, ok := ts.Labels[core.LabelHostname.Key] 308 if !ok { 309 return fmt.Errorf("hostname label missing on container %+v", ts) 310 } 311 312 if cName == "machine" { 313 actualNodes[hostname] = true 314 } else { 315 for _, label := range core.ContainerLabels() { 316 if label == core.LabelContainerBaseImage && !isContainerBaseImageExpected(ts) { 317 continue 318 } 319 _, exists := ts.Labels[label.Key] 320 if !exists { 321 return fmt.Errorf("timeseries: %v does not contain container label: %v", ts, label) 322 } 323 } 324 } 325 326 if _, exists := expectedSystemContainers[cName]; exists { 327 if actualSystemContainers[cName] == nil { 328 actualSystemContainers[cName] = map[string]struct{}{} 329 } 330 actualSystemContainers[cName][hostname] = struct{}{} 331 } 332 } else { 333 return fmt.Errorf("container_name label missing on timeseries - %v", ts) 334 } 335 } 336 337 // Explicitely check for resource id 338 explicitRequirement := map[string][]string{ 339 core.MetricFilesystemUsage.MetricDescriptor.Name: {core.LabelResourceID.Key}, 340 core.MetricFilesystemLimit.MetricDescriptor.Name: {core.LabelResourceID.Key}, 341 core.MetricFilesystemAvailable.Name: {core.LabelResourceID.Key}} 342 343 for metricName, points := range ts.Metrics { 344 md, exists := mdMap[metricName] 345 if !exists { 346 return fmt.Errorf("unexpected metric %q", metricName) 347 } 348 349 for _, point := range points { 350 for _, label := range md.Labels { 351 _, exists := point.Labels[label.Key] 352 if !exists { 353 return fmt.Errorf("metric %q point %v does not contain metric label: %v", metricName, point, label) 354 } 355 } 356 } 357 358 required := explicitRequirement[metricName] 359 for _, label := range required { 360 for _, point := range points { 361 _, exists := point.Labels[label] 362 if !exists { 363 return fmt.Errorf("metric %q point %v does not contain metric label: %v", metricName, point, label) 364 } 365 366 } 367 } 368 } 369 } 370 // Validate that system containers are running on all the nodes. 371 // This test could fail if one of the containers was down while the metrics sample was collected. 372 for cName, hosts := range actualSystemContainers { 373 for _, host := range expectedNodes { 374 if _, ok := hosts[host]; !ok { 375 return fmt.Errorf("System container %q not found on host: %q - %v", cName, host, actualSystemContainers) 376 } 377 } 378 } 379 380 if err := expectedItemsExist(expectedPods, actualPods); err != nil { 381 return fmt.Errorf("expected pods don't exist %v.\nExpected: %v\nActual:%v", err, expectedPods, actualPods) 382 } 383 if err := expectedItemsExist(expectedNodes, actualNodes); err != nil { 384 return fmt.Errorf("expected nodes don't exist %v.\nExpected: %v\nActual:%v", err, expectedNodes, actualNodes) 385 } 386 387 return nil 388 } 389 390 func expectedItemsExist(expectedItems []string, actualItems map[string]bool) error { 391 for _, item := range expectedItems { 392 if _, found := actualItems[item]; !found { 393 return fmt.Errorf("missing %s", item) 394 } 395 } 396 return nil 397 } 398 399 func getErrorCauses(err error) string { 400 serr, ok := err.(*apiErrors.StatusError) 401 if !ok { 402 return "" 403 } 404 var causes []string 405 for _, c := range serr.ErrStatus.Details.Causes { 406 causes = append(causes, c.Message) 407 } 408 return strings.Join(causes, ", ") 409 } 410 411 func getDataFromProxy(fm kubeFramework, svc *kube_api.Service, url string) ([]byte, error) { 412 glog.V(2).Infof("Querying heapster: %s", url) 413 return fm.Client().Get(). 414 Namespace(svc.Namespace). 415 Prefix("proxy"). 416 Resource("services"). 417 Name(svc.Name). 418 Suffix(url). 419 Do().Raw() 420 } 421 422 func getMetricResultList(fm kubeFramework, svc *kube_api.Service, url string) (*api_v1.MetricResultList, error) { 423 body, err := getDataFromProxy(fm, svc, url) 424 if err != nil { 425 return nil, err 426 } 427 var data api_v1.MetricResultList 428 if err := json.Unmarshal(body, &data); err != nil { 429 glog.V(2).Infof("response body: %v", string(body)) 430 return nil, err 431 } 432 if err := checkMetricResultListSanity(&data); err != nil { 433 return nil, err 434 } 435 return &data, nil 436 } 437 438 func getMetricResult(fm kubeFramework, svc *kube_api.Service, url string) (*api_v1.MetricResult, error) { 439 body, err := getDataFromProxy(fm, svc, url) 440 if err != nil { 441 return nil, err 442 } 443 var data api_v1.MetricResult 444 if err := json.Unmarshal(body, &data); err != nil { 445 glog.V(2).Infof("response body: %v", string(body)) 446 return nil, err 447 } 448 if err := checkMetricResultSanity(&data); err != nil { 449 return nil, err 450 } 451 return &data, nil 452 } 453 454 func getStringResult(fm kubeFramework, svc *kube_api.Service, url string) ([]string, error) { 455 body, err := getDataFromProxy(fm, svc, url) 456 if err != nil { 457 return nil, err 458 } 459 var data []string 460 if err := json.Unmarshal(body, &data); err != nil { 461 glog.V(2).Infof("response body: %v", string(body)) 462 return nil, err 463 } 464 if len(data) == 0 { 465 return nil, fmt.Errorf("empty string array") 466 } 467 return data, nil 468 } 469 470 func checkMetricResultSanity(metrics *api_v1.MetricResult) error { 471 bytes, err := json.Marshal(*metrics) 472 if err != nil { 473 return err 474 } 475 stringVersion := string(bytes) 476 477 if len(metrics.Metrics) == 0 { 478 return fmt.Errorf("empty metrics: %s", stringVersion) 479 } 480 // There should be recent metrics in the response. 481 if time.Now().Sub(metrics.LatestTimestamp).Seconds() > 120 { 482 return fmt.Errorf("corrupted last timestamp: %s", stringVersion) 483 } 484 // Metrics don't have to be sorted, so the oldest one can be first. 485 if time.Now().Sub(metrics.Metrics[0].Timestamp).Hours() > 1 { 486 return fmt.Errorf("corrupted timestamp: %s", stringVersion) 487 } 488 if metrics.Metrics[0].Value > 10000 { 489 return fmt.Errorf("value too big: %s", stringVersion) 490 } 491 return nil 492 } 493 494 func checkMetricResultListSanity(metrics *api_v1.MetricResultList) error { 495 if len(metrics.Items) == 0 { 496 return fmt.Errorf("empty metrics") 497 } 498 for _, item := range metrics.Items { 499 err := checkMetricResultSanity(&item) 500 if err != nil { 501 return err 502 } 503 } 504 return nil 505 } 506 507 func runModelTest(fm kubeFramework, svc *kube_api.Service) error { 508 podList, err := fm.GetRunningPods() 509 if err != nil { 510 return err 511 } 512 if len(podList) == 0 { 513 return fmt.Errorf("empty pod list") 514 } 515 nodeList, err := fm.GetNodes() 516 if err != nil { 517 return err 518 } 519 if len(nodeList) == 0 { 520 return fmt.Errorf(("empty node list")) 521 } 522 podNamesList := make([]string, 0, len(podList)) 523 for _, pod := range podList { 524 podNamesList = append(podNamesList, fmt.Sprintf("%s/%s", pod.Namespace, pod.Name)) 525 } 526 527 glog.V(0).Infof("Expected nodes:\n%s", strings.Join(nodeList, "\n")) 528 glog.V(0).Infof("Expected pods:\n%s", strings.Join(podNamesList, "\n")) 529 allkeys, err := getStringResult(fm, svc, "/api/v1/model/debug/allkeys") 530 if err != nil { 531 return fmt.Errorf("Failed to get debug information about keys: %v", err) 532 } 533 glog.V(0).Infof("Available Heapster metric sets:\n%s", strings.Join(allkeys, "\n")) 534 535 metricUrlsToCheck := []string{} 536 batchMetricsUrlsToCheck := []string{} 537 stringUrlsToCheck := []string{} 538 539 /* TODO: enable once cluster aggregator is added. 540 metricUrlsToCheck = append(metricUrlsToCheck, 541 fmt.Sprintf("/api/v1/model/metrics/%s", "cpu-usage"), 542 ) 543 */ 544 545 /* TODO: add once Cluster metrics aggregator is added. 546 "/api/v1/model/metrics", 547 "/api/v1/model/" 548 */ 549 stringUrlsToCheck = append(stringUrlsToCheck) 550 551 for _, node := range nodeList { 552 metricUrlsToCheck = append(metricUrlsToCheck, 553 fmt.Sprintf("/api/v1/model/nodes/%s/metrics/%s", node, "cpu/usage_rate"), 554 fmt.Sprintf("/api/v1/model/nodes/%s/metrics/%s", node, "cpu-usage"), 555 ) 556 557 stringUrlsToCheck = append(stringUrlsToCheck, 558 fmt.Sprintf("/api/v1/model/nodes/%s/metrics", node), 559 ) 560 } 561 562 for _, pod := range podList { 563 containerName := pod.Spec.Containers[0].Name 564 565 metricUrlsToCheck = append(metricUrlsToCheck, 566 fmt.Sprintf("/api/v1/model/namespaces/%s/pods/%s/metrics/%s", pod.Namespace, pod.Name, "cpu/usage_rate"), 567 fmt.Sprintf("/api/v1/model/namespaces/%s/pods/%s/metrics/%s", pod.Namespace, pod.Name, "cpu-usage"), 568 fmt.Sprintf("/api/v1/model/namespaces/%s/pods/%s/containers/%s/metrics/%s", pod.Namespace, pod.Name, containerName, "cpu/usage_rate"), 569 fmt.Sprintf("/api/v1/model/namespaces/%s/pods/%s/containers/%s/metrics/%s", pod.Namespace, pod.Name, containerName, "cpu-usage"), 570 fmt.Sprintf("/api/v1/model/namespaces/%s/metrics/%s", pod.Namespace, "cpu/usage_rate"), 571 fmt.Sprintf("/api/v1/model/namespaces/%s/metrics/%s", pod.Namespace, "cpu-usage"), 572 ) 573 574 batchMetricsUrlsToCheck = append(batchMetricsUrlsToCheck, 575 fmt.Sprintf("/api/v1/model/namespaces/%s/pod-list/%s,%s/metrics/%s", pod.Namespace, pod.Name, pod.Name, "cpu/usage_rate"), 576 fmt.Sprintf("/api/v1/model/namespaces/%s/pod-list/%s,%s/metrics/%s", pod.Namespace, pod.Name, pod.Name, "cpu-usage"), 577 ) 578 579 stringUrlsToCheck = append(stringUrlsToCheck, 580 fmt.Sprintf("/api/v1/model/namespaces/%s/metrics", pod.Namespace), 581 fmt.Sprintf("/api/v1/model/namespaces/%s/pods/%s/metrics", pod.Namespace, pod.Name), 582 fmt.Sprintf("/api/v1/model/namespaces/%s/pods/%s/containers/%s/metrics", pod.Namespace, pod.Name, containerName), 583 ) 584 } 585 586 for _, url := range metricUrlsToCheck { 587 _, err := getMetricResult(fm, svc, url) 588 if err != nil { 589 return fmt.Errorf("error while querying %s: %v", url, err) 590 } 591 } 592 593 for _, url := range batchMetricsUrlsToCheck { 594 _, err := getMetricResultList(fm, svc, url) 595 if err != nil { 596 return fmt.Errorf("error while querying %s: %v", url, err) 597 } 598 } 599 600 for _, url := range stringUrlsToCheck { 601 _, err := getStringResult(fm, svc, url) 602 if err != nil { 603 return fmt.Errorf("error while querying %s: %v", url, err) 604 } 605 } 606 return nil 607 } 608 609 func checkUsage(res kube_v1.ResourceList) error { 610 if _, found := res[kube_v1.ResourceCPU]; !found { 611 return fmt.Errorf("Cpu not found") 612 } 613 if _, found := res[kube_v1.ResourceMemory]; !found { 614 return fmt.Errorf("Memory not found") 615 } 616 return nil 617 } 618 619 func getPodMetrics(fm kubeFramework, svc *kube_api.Service, pod kube_api.Pod) (*metrics_api.PodMetrics, error) { 620 url := "apis/metrics/v1alpha1/namespaces/" + pod.Namespace + "/pods/" + pod.Name 621 body, err := getDataFromProxy(fm, svc, url) 622 if err != nil { 623 return nil, err 624 } 625 var data metrics_api.PodMetrics 626 if err := json.Unmarshal(body, &data); err != nil { 627 glog.V(2).Infof("response body: %v", string(body)) 628 return nil, err 629 } 630 return &data, nil 631 } 632 633 func checkPodsInMetricsApi(fm kubeFramework, svc *kube_api.Service, pods []kube_api.Pod) error { 634 for _, pod := range pods { 635 metrics, err := getPodMetrics(fm, svc, pod) 636 if err != nil { 637 return err 638 } 639 if metrics.Name != pod.Name { 640 return fmt.Errorf("Wrong pod name: expected %v, got %v", pod.Name, metrics.Name) 641 } 642 if metrics.Namespace != pod.Namespace { 643 return fmt.Errorf("Wrong pod namespace: expected %v, got %v", pod.Namespace, metrics.Namespace) 644 } 645 if len(pod.Spec.Containers) != len(metrics.Containers) { 646 return fmt.Errorf("Wrong number of containers in returned metrics: expected %v, got %v", len(pod.Spec.Containers), len(metrics.Containers)) 647 } 648 for _, c := range metrics.Containers { 649 if err := checkUsage(c.Usage); err != nil { 650 return err 651 } 652 } 653 } 654 return nil 655 } 656 657 func getNodeMetrics(fm kubeFramework, svc *kube_api.Service, node string) (*metrics_api.NodeMetrics, error) { 658 url := "apis/metrics/v1alpha1/nodes/" + node 659 body, err := getDataFromProxy(fm, svc, url) 660 if err != nil { 661 return nil, err 662 } 663 var data metrics_api.NodeMetrics 664 if err := json.Unmarshal(body, &data); err != nil { 665 glog.V(2).Infof("response body: %v", string(body)) 666 return nil, err 667 } 668 return &data, nil 669 } 670 671 func checkNodesInMetricsApi(fm kubeFramework, svc *kube_api.Service, nodes []string) error { 672 for _, node := range nodes { 673 metrics, err := getNodeMetrics(fm, svc, node) 674 if err != nil { 675 return err 676 } 677 if metrics.Name != node { 678 return fmt.Errorf("Wrong node name: expected %v, got %v", node, metrics.Name) 679 } 680 if err := checkUsage(metrics.Usage); err != nil { 681 return err 682 } 683 } 684 return nil 685 } 686 687 func runMetricsApiTest(fm kubeFramework, svc *kube_api.Service) error { 688 podList, err := fm.GetRunningPods() 689 if err != nil { 690 return err 691 } 692 if len(podList) == 0 { 693 return fmt.Errorf("empty pod list") 694 } 695 if err := checkPodsInMetricsApi(fm, svc, podList); err != nil { 696 return err 697 } 698 699 nodeList, err := fm.GetNodes() 700 if err != nil { 701 return err 702 } 703 if len(nodeList) == 0 { 704 return fmt.Errorf(("empty node list")) 705 } 706 return checkNodesInMetricsApi(fm, svc, nodeList) 707 } 708 709 func apiTest(kubeVersion string, zone string) error { 710 fm, err := newKubeFramework(kubeVersion) 711 if err != nil { 712 return err 713 } 714 if err := buildAndPushDockerImages(fm, zone); err != nil { 715 return err 716 } 717 // Create heapster pod and service. 718 svc, rc, err := getHeapsterRcAndSvc(fm) 719 if err != nil { 720 return err 721 } 722 ns := *namespace 723 if err := deleteAll(fm, ns, svc, rc); err != nil { 724 return err 725 } 726 if err := createAll(fm, ns, &svc, &rc); err != nil { 727 return err 728 } 729 if err := fm.WaitUntilPodRunning(ns, rc.Spec.Template.Labels, time.Minute); err != nil { 730 return err 731 } 732 if err := fm.WaitUntilServiceActive(svc, time.Minute); err != nil { 733 return err 734 } 735 testFuncs := []func() error{ 736 func() error { 737 glog.V(2).Infof("Heapster metric export test...") 738 err := runMetricExportTest(fm, svc) 739 if err == nil { 740 glog.V(2).Infof("Heapster metric export test: OK") 741 } else { 742 glog.V(2).Infof("Heapster metric export test error: %v", err) 743 } 744 return err 745 }, 746 func() error { 747 glog.V(2).Infof("Model test") 748 err := runModelTest(fm, svc) 749 if err == nil { 750 glog.V(2).Infof("Model test: OK") 751 } else { 752 glog.V(2).Infof("Model test error: %v", err) 753 } 754 return err 755 }, 756 func() error { 757 glog.V(2).Infof("Metrics API test") 758 err := runMetricsApiTest(fm, svc) 759 if err == nil { 760 glog.V(2).Infof("Metrics API test: OK") 761 } else { 762 glog.V(2).Infof("Metrics API test error: %v", err) 763 } 764 return err 765 }, 766 } 767 attempts := *maxRetries 768 glog.Infof("Starting tests") 769 for { 770 var err error 771 for _, testFunc := range testFuncs { 772 if err = testFunc(); err != nil { 773 break 774 } 775 } 776 if *runForever { 777 continue 778 } 779 if err == nil { 780 glog.V(2).Infof("All tests passed.") 781 break 782 } 783 if attempts == 0 { 784 glog.V(2).Info("Too many attempts.") 785 return err 786 } 787 glog.V(2).Infof("Some tests failed. Retrying.") 788 attempts-- 789 time.Sleep(time.Second * 10) 790 } 791 deleteAll(fm, ns, svc, rc) 792 removeHeapsterImage(fm, zone) 793 return nil 794 } 795 796 func runApiTest() error { 797 tempDir, err := ioutil.TempDir("", "deploy") 798 if err != nil { 799 return nil 800 } 801 defer os.RemoveAll(tempDir) 802 if *kubeVersions == "" { 803 return apiTest("", *testZone) 804 } 805 kubeVersionsList := strings.Split(*kubeVersions, ",") 806 for _, kubeVersion := range kubeVersionsList { 807 if err := apiTest(kubeVersion, *testZone); err != nil { 808 return err 809 } 810 } 811 return nil 812 } 813 814 func TestHeapster(t *testing.T) { 815 if testing.Short() { 816 t.Skip("skipping heapster kubernetes integration test.") 817 } 818 require.NoError(t, runApiTest()) 819 }