github.com/vmware/govmomi@v0.51.0/simulator/container_virtual_machine_test.go (about)

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