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  }