Skip to content

Commit

Permalink
vcsim: container backing respects changes via reconfigure
Browse files Browse the repository at this point in the history
If "RUN.container" is added or removed on an existing VM, that change
is applied immediately if the VM is currently powered on.

Modifications to the value of the key do not have an effect unless the
continue needs to be recreated for some reason.

Switches to Go templates for formating docker command output

Includes additional error logging detail
  • Loading branch information
hickeng committed Aug 2, 2023
1 parent eee9f44 commit 548bdde
Show file tree
Hide file tree
Showing 5 changed files with 315 additions and 16 deletions.
11 changes: 6 additions & 5 deletions simulator/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,10 +288,10 @@ func getBridge(bridgeName string) (string, error) {
// if the underlay bridge already exists, return that
// we don't check for a specific label or similar so that it's possible to use a bridge created by other frameworks for composite testing
var bridge bridgeNet
cmd := exec.Command("docker", "network", "ls", "--format", "json", "-f", fmt.Sprintf("name=%s$", bridgeName))
cmd := exec.Command("docker", "network", "ls", "--format={{json .}}", "-f", fmt.Sprintf("name=%s$", bridgeName))
out, err := cmd.Output()
if err != nil {
log.Printf("vcsim %s: %s", cmd.Args, err)
log.Printf("vcsim %s: %s, %s", cmd.Args, err, out)
return "", err
}

Expand All @@ -306,7 +306,7 @@ func getBridge(bridgeName string) (string, error) {

err = json.Unmarshal([]byte(str), &bridge)
if err != nil {
log.Printf("vcsim %s: %s", cmd.Args, err)
log.Printf("vcsim %s: %s, %s", cmd.Args, err, str)
return "", err
}

Expand Down Expand Up @@ -586,15 +586,16 @@ func (c *container) remove(ctx *Context) error {
if lsverr != nil {
log.Printf("%s %s: %s", c.name, cmd.Args, lsverr)
}
log.Printf("%s volumes: %s", c.name, volumesToReap)

var rmverr error
if len(volumesToReap) > 0 {
run := []string{"volume", "rm", "-f"}
run = append(run, strings.Split(string(volumesToReap), "\n")...)
cmd = exec.Command("docker", run...)
rmverr = cmd.Run()
out, rmverr := cmd.Output()
if rmverr != nil {
log.Printf("%s %s: %s", c.name, cmd.Args, rmverr)
log.Printf("%s %s: %s, %s", c.name, cmd.Args, rmverr, out)
}
}

Expand Down
6 changes: 4 additions & 2 deletions simulator/container_virtual_machine.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ import (
"github.com/vmware/govmomi/vim25/types"
)

const ContainerBackingOptionKey = "RUN.container"

var (
toolsRunning = []types.PropertyChange{
{Name: "guest.toolsStatus", Val: types.VirtualMachineToolsStatusToolsOk},
Expand Down Expand Up @@ -63,7 +65,7 @@ func createSimulationVM(vm *VirtualMachine) *simVM {

for _, opt := range vm.Config.ExtraConfig {
val := opt.GetOptionValue()
if val.Key == "RUN.container" {
if val.Key == ContainerBackingOptionKey {
return svm
}
}
Expand Down Expand Up @@ -203,7 +205,7 @@ func (svm *simVM) start(ctx *Context) error {

for _, opt := range svm.vm.Config.ExtraConfig {
val := opt.GetOptionValue()
if val.Key == "RUN.container" {
if val.Key == ContainerBackingOptionKey {
run := val.Value.(string)
err := json.Unmarshal([]byte(run), &args)
if err != nil {
Expand Down
249 changes: 249 additions & 0 deletions simulator/container_virtual_machine_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,249 @@
/*
Copyright (c) 2023 VMware, Inc. All Rights Reserved.
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 simulator

import (
"bytes"
"context"
"fmt"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"testing"

"github.com/vmware/govmomi/find"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/vim25"
"github.com/vmware/govmomi/vim25/types"
)

// takes a content string to serve from the container and returns ExtraConfig options
// to construct container
// content - the contents of index.html
// port - the port to forward to the container port 80
func constructNginxBacking(t *testing.T, content string, port int) []types.BaseOptionValue {
dir := t.TempDir()
for dirpart := dir; dirpart != "/"; dirpart = filepath.Dir(dirpart) {
os.Chmod(dirpart, 0755)
}

fpath := filepath.Join(dir, "index.html")
os.WriteFile(fpath, []byte(content), 0644)
// just in case umask gets in the way
os.Chmod(fpath, 0644)

args := fmt.Sprintf("-v '%s:/usr/share/nginx/html:ro' nginx", dir)

return []types.BaseOptionValue{
&types.OptionValue{Key: ContainerBackingOptionKey, Value: args}, // run nginx
&types.OptionValue{Key: "RUN.port.80", Value: "8888"}, // test port remap
}
}

// validates the VM is serving the expected content on the expected ports
// pairs with constructNginxBacking
func validateNginxContainer(t *testing.T, vm *object.VirtualMachine, expected string, port int) error {
ip, _ := vm.WaitForIP(context.Background(), true) // Returns the docker container's IP

// Count the number of bytes in feature_test.go via nginx going direct to the container
cmd := exec.Command("docker", "run", "--rm", "curlimages/curl", "curl", "-f", fmt.Sprintf("http://%s:80", ip))
var buf bytes.Buffer
cmd.Stdout = &buf
err := cmd.Run()
res := buf.String()

if err != nil || strings.TrimSpace(res) != expected {
// we use Fail not Fatal because we want to clean up
t.Fail()
t.Log(err, buf.String())
fmt.Printf("%d diff", buf.Len()-len(expected))
}

// Count the number of bytes in feature_test.go via nginx going via port remap on host
cmd = exec.Command("curl", "-f", fmt.Sprintf("http://localhost:%d", port))
buf.Reset()
cmd.Stdout = &buf
err = cmd.Run()
res = buf.String()
if err != nil || strings.TrimSpace(res) != expected {
t.Fail()
t.Log(err, buf.String())
fmt.Printf("%d diff", buf.Len()-len(expected))
}

return nil
}

// 1. Construct ExtraConfig args for container backing
// 2. Create VM using that ExtraConfig
// 3. Confirm docker container present that matches expectations
func TestCreateVMWithContainerBacking(t *testing.T) {
Test(func(ctx context.Context, c *vim25.Client) {
if _, err := exec.LookPath("docker"); err != nil {
fmt.Println("0 diff")
t.Skip("docker client binary not on PATH")
return
}

finder := find.NewFinder(c)
pool, _ := finder.ResourcePool(ctx, "DC0_H0/Resources")
dc, err := finder.Datacenter(ctx, "DC0")
if err != nil {
log.Fatal(err)
}

content := "foo"
port := 8888

spec := types.VirtualMachineConfigSpec{
Name: "nginx-container-backed-from-creation",
Files: &types.VirtualMachineFileInfo{
VmPathName: "[LocalDS_0] nginx",
},
ExtraConfig: constructNginxBacking(t, content, port),
}

f, _ := dc.Folders(ctx)
// Create a new VM
task, err := f.VmFolder.CreateVM(ctx, spec, pool, nil)
if err != nil {
log.Fatal(err)
}

info, err := task.WaitForResult(ctx, nil)
if err != nil {
log.Fatal(err)
}

vm := object.NewVirtualMachine(c, info.Result.(types.ManagedObjectReference))
// PowerOn VM starts the nginx container
task, _ = vm.PowerOn(ctx)
err = task.Wait(ctx)
if err != nil {
log.Fatal(err)
}

err = validateNginxContainer(t, vm, content, port)
if err != nil {
log.Fatal(err)
}

spec2 := types.VirtualMachineConfigSpec{
ExtraConfig: []types.BaseOptionValue{
&types.OptionValue{Key: ContainerBackingOptionKey, Value: ""},
},
}

task, err = vm.Reconfigure(ctx, spec2)
if err != nil {
log.Fatal(err)
}

info, err = task.WaitForResult(ctx, nil)
if err != nil {
log.Fatal(info, err)
}

// PowerOff stops the container
task, _ = vm.PowerOff(ctx)
_ = task.Wait(ctx)
// Destroy deletes the container
task, _ = vm.Destroy(ctx)
_ = task.Wait(ctx)
})
// Output: 0 diff
}

// 1. Create VM without ExtraConfig args for container backing
// 2. Construct ExtraConfig args for container backing
// 3. Update VM with ExtraConfig
// 4. Confirm docker container present that matches expectations
func TestUpdateVMAddContainerBacking(t *testing.T) {
Test(func(ctx context.Context, c *vim25.Client) {
if _, err := exec.LookPath("docker"); err != nil {
fmt.Println("0 diff")
t.Skip("docker client binary not on PATH")
return
}

finder := find.NewFinder(c)
pool, _ := finder.ResourcePool(ctx, "DC0_H0/Resources")
dc, err := finder.Datacenter(ctx, "DC0")
if err != nil {
log.Fatal(err)
}

content := "foo"
port := 8888

spec := types.VirtualMachineConfigSpec{
Name: "nginx-container-after-reconfig",
Files: &types.VirtualMachineFileInfo{
VmPathName: "[LocalDS_0] nginx",
},
}

f, _ := dc.Folders(ctx)
// Create a new VM
task, err := f.VmFolder.CreateVM(ctx, spec, pool, nil)
if err != nil {
log.Fatal(err)
}

info, err := task.WaitForResult(ctx, nil)
if err != nil {
log.Fatal(err)
}

vm := object.NewVirtualMachine(c, info.Result.(types.ManagedObjectReference))
// PowerOn VM starts the nginx container
task, _ = vm.PowerOn(ctx)
err = task.Wait(ctx)
if err != nil {
log.Fatal(err)
}

spec2 := types.VirtualMachineConfigSpec{
ExtraConfig: constructNginxBacking(t, content, port),
}

task, err = vm.Reconfigure(ctx, spec2)
if err != nil {
log.Fatal(err)
}

info, err = task.WaitForResult(ctx, nil)
if err != nil {
log.Fatal(info, err)
}

err = validateNginxContainer(t, vm, content, port)
if err != nil {
log.Fatal(err)
}

// PowerOff stops the container
task, _ = vm.PowerOff(ctx)
_ = task.Wait(ctx)
// Destroy deletes the container
task, _ = vm.Destroy(ctx)
_ = task.Wait(ctx)
})
// Output: 0 diff
}
2 changes: 1 addition & 1 deletion simulator/feature_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ func Example_runContainer() {
}

// Count the number of bytes in feature_test.go via nginx going via port remap on host
cmd = exec.Command("docker", "run", "--rm", "--network=host", "curlimages/curl", "curl", "-f", fmt.Sprintf("http://%s", ip))
cmd = exec.Command("curl", "-f", "http://localhost:8888")
buf.Reset()
cmd.Stdout = &buf
err = cmd.Run()
Expand Down
Loading

0 comments on commit 548bdde

Please sign in to comment.