/* Copyright 2018 The Kubernetes 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 gce import ( "fmt" "os/exec" "time" "k8s.io/node-problem-detector/test/e2e/lib/ssh" . "github.com/onsi/gomega" compute "google.golang.org/api/compute/v1" ) // Instance represents a GCE instance. type Instance struct { Name string Zone string Project string MachineType string ExternalIP string SshKey string SshUser string ComputeService *compute.Service } // CreateInstance creates a GCE instance with provided spec. func CreateInstance(instance Instance, imageName string, imageProject string) (Instance, error) { if instance.MachineType == "" { instance.MachineType = "n1-standard-1" } p, err := instance.ComputeService.Projects.Get(instance.Project).Do() if err != nil { return instance, fmt.Errorf("failed to get project info %q: %v", instance.Project, err) } i := &compute.Instance{ Name: instance.Name, MachineType: fmt.Sprintf("zones/%s/machineTypes/%s", instance.Zone, instance.MachineType), NetworkInterfaces: []*compute.NetworkInterface{ { AccessConfigs: []*compute.AccessConfig{ { Type: "ONE_TO_ONE_NAT", Name: "External NAT", }, }}, }, Disks: []*compute.AttachedDisk{ { AutoDelete: true, Boot: true, Type: "PERSISTENT", InitializeParams: &compute.AttachedDiskInitializeParams{ SourceImage: fmt.Sprintf("projects/%s/global/images/%s", imageProject, imageName), DiskSizeGb: 20, }, }, }, ServiceAccounts: []*compute.ServiceAccount{ { Email: p.DefaultServiceAccount, Scopes: []string{ "https://www.googleapis.com/auth/cloud-platform", }, }, }, } if _, err := instance.ComputeService.Instances.Get(instance.Project, instance.Zone, instance.Name).Do(); err != nil { op, err := instance.ComputeService.Instances.Insert(instance.Project, instance.Zone, i).Do() if err != nil { ret := fmt.Sprintf("could not create instance %s: API error: %v", instance.Name, err) if op != nil { ret = fmt.Sprintf("%s: %v", ret, op.Error) } return instance, fmt.Errorf(ret) } else if op.Error != nil { return instance, fmt.Errorf("could not create instance %s: %+v", instance.Name, op.Error) } } instanceRunning := false // Waiting for the instance to be SSH-able by retrying SSH with timeout of 5 min (15*20sec) for i := 0; i < 15 && !instanceRunning; i++ { if i > 0 { time.Sleep(time.Second * 20) } if instance.ExternalIP == "" { instance.populateExternalIP() } result := ssh.Run("pwd", instance.ExternalIP, instance.SshUser, instance.SshKey) if result.SSHError != nil { err = fmt.Errorf("SSH to instance %s failed: %s", instance.Name, result.SSHError) continue } instanceRunning = true } if !instanceRunning { return instance, err } return instance, nil } func (ins *Instance) populateExternalIP() { gceInstance, err := ins.ComputeService.Instances.Get(ins.Project, ins.Zone, ins.Name).Do() if err != nil { return } for i := range gceInstance.NetworkInterfaces { ni := gceInstance.NetworkInterfaces[i] for j := range ni.AccessConfigs { ac := ni.AccessConfigs[j] if len(ac.NatIP) > 0 { ins.ExternalIP = ac.NatIP return } } } } // RunCommand runs a command on the GCE instance and returns the command result. func (ins *Instance) RunCommand(cmd string) ssh.Result { if ins.ExternalIP == "" { ins.populateExternalIP() } return ssh.Run(cmd, ins.ExternalIP, ins.SshUser, ins.SshKey) } // RunCommand runs a command on the GCE instance and returns the command result, and fails the test when the command failed. func (ins *Instance) RunCommandOrFail(cmd string) ssh.Result { result := ins.RunCommand(cmd) Expect(result.SSHError).ToNot(HaveOccurred(), "SSH-ing to the instance failed: %v\n", result) Expect(result.Code).To(Equal(0), "Running command failed: %v\n", result) return result } // PushFile pushes a local file to a GCE instance. func (ins *Instance) PushFile(srcPath, destPath string) error { if ins.ExternalIP == "" { ins.populateExternalIP() } output, err := exec.Command("scp", "-o", "StrictHostKeyChecking no", "-i", ins.SshKey, srcPath, fmt.Sprintf("%s@%s:%s", ins.SshUser, ins.ExternalIP, destPath)).CombinedOutput() if err != nil { return fmt.Errorf("Error running scp: %v.\nHere is the output for the command: %v", err, string(output)) } return nil } // DeleteInstance deletes a GCE instance. func (ins *Instance) DeleteInstance() error { if _, err := ins.ComputeService.Instances.Get(ins.Project, ins.Zone, ins.Name).Do(); err != nil { return err } if _, err := ins.ComputeService.Instances.Delete(ins.Project, ins.Zone, ins.Name).Do(); err != nil { return err } return nil }