github.com/vmware/govmomi@v0.43.0/simulator/container_virtual_machine_test.go (about) 1 /* 2 Copyright (c) 2023-2023 VMware, Inc. All Rights Reserved. 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 simulator 18 19 import ( 20 "bytes" 21 "context" 22 "fmt" 23 "log" 24 "os" 25 "os/exec" 26 "path/filepath" 27 "strings" 28 "testing" 29 30 "github.com/stretchr/testify/require" 31 32 "github.com/vmware/govmomi/find" 33 "github.com/vmware/govmomi/object" 34 "github.com/vmware/govmomi/vim25" 35 "github.com/vmware/govmomi/vim25/types" 36 ) 37 38 // takes a content string to serve from the container and returns ExtraConfig options 39 // to construct container 40 // content - the contents of index.html 41 // port - the port to forward to the container port 80 42 func constructNginxBacking(t *testing.T, content string, port int) []types.BaseOptionValue { 43 dir := t.TempDir() 44 // experience shows that a parent directory created as part of the TempDir call may not have 45 // o+rx, preventing use within a container that doesn't have the same uid 46 for dirpart := dir; dirpart != "/"; dirpart = filepath.Dir(dirpart) { 47 os.Chmod(dirpart, 0755) 48 stat, err := os.Stat(dirpart) 49 require.Nil(t, err, "must be able to check file and directory permissions") 50 require.NotZero(t, stat.Mode()&0005, "does not have o+rx permissions", dirpart) 51 } 52 53 fpath := filepath.Join(dir, "index.html") 54 err := os.WriteFile(fpath, []byte(content), 0644) 55 require.Nil(t, err, "Expected to cleanly write content to file: %s", err) 56 57 // just in case umask gets in the way 58 err = os.Chmod(fpath, 0644) 59 require.Nil(t, err, "Expected to cleanly set file permissions on content: %s", err) 60 61 args := fmt.Sprintf("-v '%s:/usr/share/nginx/html:ro' nginx", dir) 62 63 return []types.BaseOptionValue{ 64 &types.OptionValue{Key: ContainerBackingOptionKey, Value: args}, // run nginx 65 &types.OptionValue{Key: "RUN.port.80", Value: "8888"}, // test port remap 66 } 67 } 68 69 // validates the VM is serving the expected content on the expected ports 70 // pairs with constructNginxBacking 71 func validateNginxContainer(t *testing.T, vm *object.VirtualMachine, expected string, port int) error { 72 ip, _ := vm.WaitForIP(context.Background(), true) // Returns the docker container's IP 73 74 // Count the number of bytes in feature_test.go via nginx going direct to the container 75 cmd := exec.Command("docker", "run", "--rm", "curlimages/curl", "curl", "-f", fmt.Sprintf("http://%s:80", ip)) 76 var buf bytes.Buffer 77 cmd.Stdout = &buf 78 err := cmd.Run() 79 res := buf.String() 80 81 if err != nil || strings.TrimSpace(res) != expected { 82 // we use Fail not Fatal because we want to clean up 83 t.Fail() 84 t.Log(err, buf.String()) 85 fmt.Printf("%d diff", buf.Len()-len(expected)) 86 } 87 88 // Count the number of bytes in feature_test.go via nginx going via port remap on host 89 cmd = exec.Command("curl", "-f", fmt.Sprintf("http://localhost:%d", port)) 90 buf.Reset() 91 cmd.Stdout = &buf 92 err = cmd.Run() 93 res = buf.String() 94 if err != nil || strings.TrimSpace(res) != expected { 95 t.Fail() 96 t.Log(err, buf.String()) 97 fmt.Printf("%d diff", buf.Len()-len(expected)) 98 } 99 100 return nil 101 } 102 103 // 1. Construct ExtraConfig args for container backing 104 // 2. Create VM using that ExtraConfig 105 // 3. Confirm docker container present that matches expectations 106 func TestCreateVMWithContainerBacking(t *testing.T) { 107 Test(func(ctx context.Context, c *vim25.Client) { 108 if _, err := exec.LookPath("docker"); err != nil { 109 fmt.Println("0 diff") 110 t.Skip("docker client binary not on PATH") 111 return 112 } 113 114 finder := find.NewFinder(c) 115 pool, _ := finder.ResourcePool(ctx, "DC0_H0/Resources") 116 dc, err := finder.Datacenter(ctx, "DC0") 117 if err != nil { 118 log.Fatal(err) 119 } 120 121 content := "foo" 122 port := 8888 123 124 spec := types.VirtualMachineConfigSpec{ 125 Name: "nginx-container-backed-from-creation", 126 Files: &types.VirtualMachineFileInfo{ 127 VmPathName: "[LocalDS_0] nginx", 128 }, 129 ExtraConfig: constructNginxBacking(t, content, port), 130 } 131 132 f, _ := dc.Folders(ctx) 133 // Create a new VM 134 task, err := f.VmFolder.CreateVM(ctx, spec, pool, nil) 135 if err != nil { 136 log.Fatal(err) 137 } 138 139 info, err := task.WaitForResult(ctx, nil) 140 if err != nil { 141 log.Fatal(err) 142 } 143 144 vm := object.NewVirtualMachine(c, info.Result.(types.ManagedObjectReference)) 145 // PowerOn VM starts the nginx container 146 task, _ = vm.PowerOn(ctx) 147 err = task.Wait(ctx) 148 if err != nil { 149 log.Fatal(err) 150 } 151 152 err = validateNginxContainer(t, vm, content, port) 153 if err != nil { 154 log.Fatal(err) 155 } 156 157 spec2 := types.VirtualMachineConfigSpec{ 158 ExtraConfig: []types.BaseOptionValue{ 159 &types.OptionValue{Key: ContainerBackingOptionKey, Value: ""}, 160 }, 161 } 162 163 task, err = vm.Reconfigure(ctx, spec2) 164 if err != nil { 165 log.Fatal(err) 166 } 167 168 info, err = task.WaitForResult(ctx, nil) 169 if err != nil { 170 log.Fatal(info, err) 171 } 172 173 // PowerOff stops the container 174 task, _ = vm.PowerOff(ctx) 175 _ = task.Wait(ctx) 176 // Destroy deletes the container 177 task, _ = vm.Destroy(ctx) 178 _ = task.Wait(ctx) 179 }) 180 // Output: 0 diff 181 } 182 183 // 1. Create VM without ExtraConfig args for container backing 184 // 2. Construct ExtraConfig args for container backing 185 // 3. Update VM with ExtraConfig 186 // 4. Confirm docker container present that matches expectations 187 func TestUpdateVMAddContainerBacking(t *testing.T) { 188 Test(func(ctx context.Context, c *vim25.Client) { 189 if _, err := exec.LookPath("docker"); err != nil { 190 fmt.Println("0 diff") 191 t.Skip("docker client binary not on PATH") 192 return 193 } 194 195 finder := find.NewFinder(c) 196 pool, _ := finder.ResourcePool(ctx, "DC0_H0/Resources") 197 dc, err := finder.Datacenter(ctx, "DC0") 198 if err != nil { 199 log.Fatal(err) 200 } 201 202 content := "foo" 203 port := 8888 204 205 spec := types.VirtualMachineConfigSpec{ 206 Name: "nginx-container-after-reconfig", 207 Files: &types.VirtualMachineFileInfo{ 208 VmPathName: "[LocalDS_0] nginx", 209 }, 210 } 211 212 f, _ := dc.Folders(ctx) 213 // Create a new VM 214 task, err := f.VmFolder.CreateVM(ctx, spec, pool, nil) 215 if err != nil { 216 log.Fatal(err) 217 } 218 219 info, err := task.WaitForResult(ctx, nil) 220 if err != nil { 221 log.Fatal(err) 222 } 223 224 vm := object.NewVirtualMachine(c, info.Result.(types.ManagedObjectReference)) 225 // PowerOn VM starts the nginx container 226 task, _ = vm.PowerOn(ctx) 227 err = task.Wait(ctx) 228 if err != nil { 229 log.Fatal(err) 230 } 231 232 spec2 := types.VirtualMachineConfigSpec{ 233 ExtraConfig: constructNginxBacking(t, content, port), 234 } 235 236 task, err = vm.Reconfigure(ctx, spec2) 237 if err != nil { 238 log.Fatal(err) 239 } 240 241 info, err = task.WaitForResult(ctx, nil) 242 if err != nil { 243 log.Fatal(info, err) 244 } 245 246 err = validateNginxContainer(t, vm, content, port) 247 if err != nil { 248 log.Fatal(err) 249 } 250 251 // PowerOff stops the container 252 task, _ = vm.PowerOff(ctx) 253 _ = task.Wait(ctx) 254 // Destroy deletes the container 255 task, _ = vm.Destroy(ctx) 256 _ = task.Wait(ctx) 257 }) 258 // Output: 0 diff 259 }