github.com/google/cadvisor@v0.49.1/integration/framework/framework.go (about) 1 // Copyright 2014 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 framework 16 17 import ( 18 "bytes" 19 "flag" 20 "fmt" 21 "os/exec" 22 "strings" 23 "testing" 24 "time" 25 26 "k8s.io/klog/v2" 27 28 "github.com/google/cadvisor/client" 29 v2 "github.com/google/cadvisor/client/v2" 30 ) 31 32 var host = flag.String("host", "localhost", "Address of the host being tested") 33 var port = flag.Int("port", 8080, "Port of the application on the host being tested") 34 var sshOptions = flag.String("ssh-options", "", "Command line options for ssh") 35 36 // Integration test framework. 37 type Framework interface { 38 // Clean the framework state. 39 Cleanup() 40 41 // The testing.T used by the framework and the current test. 42 T() *testing.T 43 44 // Returns the hostname being tested. 45 Hostname() HostnameInfo 46 47 // Returns the Docker actions for the test framework. 48 Docker() DockerActions 49 50 // Returns the shell actions for the test framework. 51 Shell() ShellActions 52 53 // Returns the cAdvisor actions for the test framework. 54 Cadvisor() CadvisorActions 55 } 56 57 // Instantiates a Framework. Cleanup *must* be called. Class is thread-compatible. 58 // All framework actions report fatal errors on the t specified at creation time. 59 // 60 // Typical use: 61 // 62 // func TestFoo(t *testing.T) { 63 // fm := framework.New(t) 64 // defer fm.Cleanup() 65 // ... actual test ... 66 // } 67 func New(t *testing.T) Framework { 68 // All integration tests are large. 69 if testing.Short() { 70 t.Skip("Skipping framework test in short mode") 71 } 72 73 // Try to see if non-localhost hosts are GCE instances. 74 fm := &realFramework{ 75 hostname: HostnameInfo{ 76 Host: *host, 77 Port: *port, 78 }, 79 t: t, 80 cleanups: make([]func(), 0), 81 } 82 fm.shellActions = shellActions{ 83 fm: fm, 84 } 85 fm.dockerActions = dockerActions{ 86 fm: fm, 87 } 88 89 return fm 90 } 91 92 const ( 93 Aufs string = "aufs" 94 Overlay string = "overlay" 95 Overlay2 string = "overlay2" 96 DeviceMapper string = "devicemapper" 97 Unknown string = "" 98 ) 99 100 type DockerActions interface { 101 // Run the no-op pause Docker container and return its ID. 102 RunPause() string 103 104 // Run the specified command in a Docker busybox container and return its ID. 105 RunBusybox(cmd ...string) string 106 107 // Runs a Docker container in the background. Uses the specified DockerRunArgs and command. 108 // Returns the ID of the new container. 109 // 110 // e.g.: 111 // Run(DockerRunArgs{Image: "busybox"}, "ping", "www.google.com") 112 // -> docker run busybox ping www.google.com 113 Run(args DockerRunArgs, cmd ...string) string 114 RunStress(args DockerRunArgs, cmd ...string) string 115 116 Version() []string 117 StorageDriver() string 118 } 119 120 type ShellActions interface { 121 // Runs a specified command and arguments. Returns the stdout and stderr. 122 Run(cmd string, args ...string) (string, string) 123 RunStress(cmd string, args ...string) (string, string) 124 } 125 126 type CadvisorActions interface { 127 // Returns a cAdvisor client to the machine being tested. 128 Client() *client.Client 129 ClientV2() *v2.Client 130 } 131 132 type realFramework struct { 133 hostname HostnameInfo 134 t *testing.T 135 cadvisorClient *client.Client 136 cadvisorClientV2 *v2.Client 137 138 shellActions shellActions 139 dockerActions dockerActions 140 141 // Cleanup functions to call on Cleanup() 142 cleanups []func() 143 } 144 145 type shellActions struct { 146 fm *realFramework 147 } 148 149 type dockerActions struct { 150 fm *realFramework 151 } 152 153 type HostnameInfo struct { 154 Host string 155 Port int 156 } 157 158 // Returns: http://<host>:<port>/ 159 func (h HostnameInfo) FullHostname() string { 160 return fmt.Sprintf("http://%s:%d/", h.Host, h.Port) 161 } 162 163 func (f *realFramework) T() *testing.T { 164 return f.t 165 } 166 167 func (f *realFramework) Hostname() HostnameInfo { 168 return f.hostname 169 } 170 171 func (f *realFramework) Shell() ShellActions { 172 return f.shellActions 173 } 174 175 func (f *realFramework) Docker() DockerActions { 176 return f.dockerActions 177 } 178 179 func (f *realFramework) Cadvisor() CadvisorActions { 180 return f 181 } 182 183 // Call all cleanup functions. 184 func (f *realFramework) Cleanup() { 185 for _, cleanupFunc := range f.cleanups { 186 cleanupFunc() 187 } 188 } 189 190 // Gets a client to the cAdvisor being tested. 191 func (f *realFramework) Client() *client.Client { 192 if f.cadvisorClient == nil { 193 cadvisorClient, err := client.NewClient(f.Hostname().FullHostname()) 194 if err != nil { 195 f.t.Fatalf("Failed to instantiate the cAdvisor client: %v", err) 196 } 197 f.cadvisorClient = cadvisorClient 198 } 199 return f.cadvisorClient 200 } 201 202 // Gets a v2 client to the cAdvisor being tested. 203 func (f *realFramework) ClientV2() *v2.Client { 204 if f.cadvisorClientV2 == nil { 205 cadvisorClientV2, err := v2.NewClient(f.Hostname().FullHostname()) 206 if err != nil { 207 f.t.Fatalf("Failed to instantiate the cAdvisor client: %v", err) 208 } 209 f.cadvisorClientV2 = cadvisorClientV2 210 } 211 return f.cadvisorClientV2 212 } 213 214 func (a dockerActions) RunPause() string { 215 return a.Run(DockerRunArgs{ 216 Image: "registry.k8s.io/pause", 217 }) 218 } 219 220 // Run the specified command in a Docker busybox container. 221 func (a dockerActions) RunBusybox(cmd ...string) string { 222 return a.Run(DockerRunArgs{ 223 Image: "registry.k8s.io/busybox", 224 }, cmd...) 225 } 226 227 type DockerRunArgs struct { 228 // Image to use. 229 Image string 230 231 // Arguments to the Docker CLI. 232 Args []string 233 234 InnerArgs []string 235 } 236 237 // TODO(vmarmol): Use the Docker remote API. 238 // TODO(vmarmol): Refactor a set of "RunCommand" actions. 239 // Runs a Docker container in the background. Uses the specified DockerRunArgs and command. 240 // 241 // e.g.: 242 // RunDockerContainer(DockerRunArgs{Image: "busybox"}, "ping", "www.google.com") 243 // 244 // -> docker run busybox ping www.google.com 245 func (a dockerActions) Run(args DockerRunArgs, cmd ...string) string { 246 dockerCommand := append(append([]string{"docker", "run", "-d"}, args.Args...), args.Image) 247 dockerCommand = append(dockerCommand, cmd...) 248 output, _ := a.fm.Shell().Run("sudo", dockerCommand...) 249 250 // The last line is the container ID. 251 elements := strings.Fields(output) 252 containerID := elements[len(elements)-1] 253 254 a.fm.cleanups = append(a.fm.cleanups, func() { 255 a.fm.Shell().Run("sudo", "docker", "rm", "-f", containerID) 256 }) 257 return containerID 258 } 259 func (a dockerActions) Version() []string { 260 dockerCommand := []string{"docker", "version", "-f", "'{{.Server.Version}}'"} 261 output, _ := a.fm.Shell().Run("sudo", dockerCommand...) 262 output = strings.TrimSpace(output) 263 ret := strings.Split(output, ".") 264 if len(ret) != 3 { 265 a.fm.T().Fatalf("invalid version %v", output) 266 } 267 return ret 268 } 269 270 func (a dockerActions) StorageDriver() string { 271 dockerCommand := []string{"docker", "info"} 272 output, _ := a.fm.Shell().Run("sudo", dockerCommand...) 273 if len(output) < 1 { 274 a.fm.T().Fatalf("failed to find docker storage driver - %v", output) 275 } 276 for _, line := range strings.Split(output, "\n") { 277 line = strings.TrimSpace(line) 278 if strings.HasPrefix(line, "Storage Driver: ") { 279 idx := strings.LastIndex(line, ": ") + 2 280 driver := line[idx:] 281 switch driver { 282 case Aufs, Overlay, Overlay2, DeviceMapper: 283 return driver 284 default: 285 return Unknown 286 } 287 } 288 } 289 a.fm.T().Fatalf("failed to find docker storage driver from info - %v", output) 290 return Unknown 291 } 292 293 func (a dockerActions) RunStress(args DockerRunArgs, cmd ...string) string { 294 dockerCommand := append(append(append(append([]string{"docker", "run", "-m=4M", "-d", "-t", "-i"}, args.Args...), args.Image), args.InnerArgs...), cmd...) 295 296 output, _ := a.fm.Shell().RunStress("sudo", dockerCommand...) 297 298 // The last line is the container ID. 299 if len(output) < 1 { 300 a.fm.T().Fatalf("need 1 arguments in output %v to get the name but have %v", output, len(output)) 301 } 302 elements := strings.Fields(output) 303 containerID := elements[len(elements)-1] 304 305 a.fm.cleanups = append(a.fm.cleanups, func() { 306 a.fm.Shell().Run("sudo", "docker", "rm", "-f", containerID) 307 }) 308 return containerID 309 } 310 311 func (a shellActions) wrapSSH(command string, args ...string) *exec.Cmd { 312 cmd := []string{a.fm.Hostname().Host, "--", "sh", "-c", "\"", command} 313 cmd = append(cmd, args...) 314 cmd = append(cmd, "\"") 315 if *sshOptions != "" { 316 cmd = append(strings.Split(*sshOptions, " "), cmd...) 317 } 318 return exec.Command("ssh", cmd...) 319 } 320 321 func (a shellActions) Run(command string, args ...string) (string, string) { 322 var cmd *exec.Cmd 323 if a.fm.Hostname().Host == "localhost" { 324 // Just run locally. 325 cmd = exec.Command(command, args...) 326 } else { 327 // We must SSH to the remote machine and run the command. 328 cmd = a.wrapSSH(command, args...) 329 } 330 var stdout bytes.Buffer 331 var stderr bytes.Buffer 332 cmd.Stdout = &stdout 333 cmd.Stderr = &stderr 334 klog.Infof("About to run - %v", cmd.Args) 335 err := cmd.Run() 336 if err != nil { 337 a.fm.T().Fatalf("Failed to run %q %v in %q with error: %q. Stdout: %q, Stderr: %s", command, args, a.fm.Hostname().Host, err, stdout.String(), stderr.String()) 338 return "", "" 339 } 340 return stdout.String(), stderr.String() 341 } 342 343 func (a shellActions) RunStress(command string, args ...string) (string, string) { 344 var cmd *exec.Cmd 345 if a.fm.Hostname().Host == "localhost" { 346 // Just run locally. 347 cmd = exec.Command(command, args...) 348 } else { 349 // We must SSH to the remote machine and run the command. 350 cmd = a.wrapSSH(command, args...) 351 } 352 var stdout bytes.Buffer 353 var stderr bytes.Buffer 354 cmd.Stdout = &stdout 355 cmd.Stderr = &stderr 356 err := cmd.Run() 357 if err != nil { 358 a.fm.T().Logf("Ran %q %v in %q and received error: %q. Stdout: %q, Stderr: %s", command, args, a.fm.Hostname().Host, err, stdout.String(), stderr.String()) 359 return stdout.String(), stderr.String() 360 } 361 return stdout.String(), stderr.String() 362 } 363 364 // Runs retryFunc until no error is returned. After dur time the last error is returned. 365 // Note that the function does not timeout the execution of retryFunc when the limit is reached. 366 func RetryForDuration(retryFunc func() error, dur time.Duration) error { 367 waitUntil := time.Now().Add(dur) 368 var err error 369 for time.Now().Before(waitUntil) { 370 err = retryFunc() 371 if err == nil { 372 return nil 373 } 374 } 375 return err 376 }