Skip to content

Commit

Permalink
Introduce hcsshim.ConvertToBaseLayer
Browse files Browse the repository at this point in the history
This API allows turning any collection of files into a WCOW base layer.

It will create the necessary files in Files/ for
hcsshim.ProcessBaseLayer to function, validate the necessary files for
hcsshim.ProcessUtilityVMImage if UtilityVM/ exists, and then call those
two APIs to complete the process.

Calling this on a directory containing an untarred base layer OCI
tarball, gives a very similar outcome to passing the tar stream through
ociwclayer.ImportLayer.

The new API is used in `TestSCSIAddRemoveWCOW` to create nearly-empty
base layers for the scratch layers attached and removed from the utility
VM.

A wclayer command is also introduced: `makebaselayer` for testing and
validation purposes.

Signed-off-by: Paul "TBBle" Hampson <Paul.Hampson@Pobox.com>
  • Loading branch information
TBBle committed Dec 12, 2020
1 parent ecb0129 commit cff971b
Show file tree
Hide file tree
Showing 11 changed files with 213 additions and 8 deletions.
24 changes: 24 additions & 0 deletions cmd/wclayer/makebaselayer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package main

import (
"path/filepath"

"github.com/Microsoft/hcsshim"
"github.com/Microsoft/hcsshim/internal/appargs"
"github.com/urfave/cli"
)

var makeBaseLayerCommand = cli.Command{
Name: "makebaselayer",
Usage: "converts a directory containing 'Files/' into a base layer",
ArgsUsage: "<layer path>",
Before: appargs.Validate(appargs.NonEmptyString),
Action: func(context *cli.Context) error {
path, err := filepath.Abs(context.Args().First())
if err != nil {
return err
}

return hcsshim.ConvertToBaseLayer(path)
},
}
1 change: 1 addition & 0 deletions cmd/wclayer/wclayer.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func main() {
createCommand,
exportCommand,
importCommand,
makeBaseLayerCommand,
mountCommand,
removeCommand,
unmountCommand,
Expand Down
164 changes: 164 additions & 0 deletions internal/wclayer/converttobaselayer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
package wclayer

import (
"context"
"os"
"path/filepath"
"strings"
"syscall"

"github.com/Microsoft/hcsshim/internal/hcserror"
"github.com/Microsoft/hcsshim/internal/oc"
"github.com/Microsoft/hcsshim/internal/safefile"
"github.com/Microsoft/hcsshim/internal/winapi"
"github.com/pkg/errors"
"go.opencensus.io/trace"
)

var hiveNames = []string{"DEFAULT", "SAM", "SECURITY", "SOFTWARE", "SYSTEM"}

// Ensure the given file exists as an ordinary file, and create a zero-length file if not.
func ensureFile(path string, root *os.File) error {
stat, err := safefile.LstatRelative(path, root)
if err != nil && os.IsNotExist(err) {
// Do I need to restore directory-modifcation times on the rest of the path?
// That doesn't seem necessary since the directory _is_ being modified now.
// See `func (w *baseLayerWriter) Close()` for context
newFile, err := safefile.OpenRelative(path, root, 0, syscall.FILE_SHARE_WRITE, winapi.FILE_CREATE, 0)
if err != nil {
return err
}
return newFile.Close()
}

if err != nil {
return err
}

if !stat.Mode().IsRegular() {
fullPath := filepath.Join(root.Name(), path)
return errors.Errorf("%s has unexpected file mode %s", fullPath, stat.Mode().String())
}

return nil
}

// safefileMkdirAll works like os.MkdirAll but with safefile
// TODO: Add this to safefile?
func safefileMkdirAllRelative(path string, root *os.File) error {
pathParts := strings.Split(path, (string)(filepath.Separator))
for index := range pathParts {

partialPath := filepath.Join(pathParts[0 : index+1]...)
stat, err := safefile.LstatRelative(partialPath, root)

if err != nil && os.IsNotExist(err) {
if err := safefile.MkdirRelative(partialPath, root); err != nil {
return err
}
continue
}

if err != nil {
return err
}

if !stat.IsDir() {
fullPath := filepath.Join(root.Name(), partialPath)
return errors.Errorf("%s has unexpected file mode %s", fullPath, stat.Mode().String())
}
}

return nil
}

func ensureBaseLayer(root *os.File) (hasUtilityVM bool, err error) {
hasUtilityVM = false

// The base layer registry hives will be copied from here
hiveSourcePath := "Files\\Windows\\System32\\config"
if err = safefileMkdirAllRelative(hiveSourcePath, root); err != nil {
return
}

for _, hiveName := range hiveNames {
hivePath := filepath.Join(hiveSourcePath, hiveName)
if err = ensureFile(hivePath, root); err != nil {
return
}
}

stat, err := safefile.LstatRelative(utilityVMFilesPath, root)

if err != nil && os.IsNotExist(err) {
return false, nil
}

if err != nil {
return
}

if !stat.Mode().IsDir() {
fullPath := filepath.Join(root.Name(), utilityVMFilesPath)
return false, errors.Errorf("%s has unexpected file mode %s", fullPath, stat.Mode().String())
}

// Just check that this exists as a regular file. If it exists but is not a valid registry hive,
// ProcessUtilityVMImage will complain:
// "The registry could not read in, or write out, or flush, one of the files that contain the system's image of the registry."
bcdPath := filepath.Join(utilityVMFilesPath, "EFI\\Microsoft\\Boot\\BCD")

stat, err = safefile.LstatRelative(bcdPath, root)
if err != nil {
return false, err
}

if !stat.Mode().IsRegular() {
fullPath := filepath.Join(root.Name(), bcdPath)
return false, errors.Errorf("%s has unexpected file mode %s", fullPath, stat.Mode().String())
}

return true, nil
}

func convertToBaseLayer(ctx context.Context, root *os.File) error {
hasUtilityVM, err := ensureBaseLayer(root)

if err != nil {
return err
}

if err := ProcessBaseLayer(ctx, root.Name()); err != nil {
return err
}

if !hasUtilityVM {
return nil
}

err = safefile.EnsureNotReparsePointRelative(utilityVMPath, root)
if err != nil {
return err
}

utilityVMPath := filepath.Join(root.Name(), utilityVMPath)
return ProcessUtilityVMImage(ctx, utilityVMPath)
}

// ConvertToBaseLayer processes a candidate base layer, i.e. a directory
// containing the desired file content under Files/, and optionally the
// desired file content for a UtilityVM under UtilityMV/Files/
func ConvertToBaseLayer(ctx context.Context, path string) (err error) {
title := "hcsshim::ConvertToBaseLayer"
ctx, span := trace.StartSpan(ctx, title)
defer span.End()
defer func() { oc.SetSpanStatus(span, err) }()
span.AddAttributes(trace.StringAttribute("path", path))

if root, err := safefile.OpenRoot(path); err != nil {
return hcserror.New(err, title+" - failed", "")
} else if err = convertToBaseLayer(ctx, root); err != nil {
return hcserror.New(err, title+" - failed", "")
}
return nil
}
3 changes: 3 additions & 0 deletions layer.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ func ProcessUtilityVMImage(path string) error {
func UnprepareLayer(info DriverInfo, layerId string) error {
return wclayer.UnprepareLayer(context.Background(), layerPath(&info, layerId))
}
func ConvertToBaseLayer(path string) error {
return wclayer.ConvertToBaseLayer(context.Background(), path)
}

type DriverInfo struct {
Flavour int
Expand Down
6 changes: 3 additions & 3 deletions test/functional/utilities/createuvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import (

// CreateWCOWUVM creates a WCOW utility VM with all default options. Returns the
// UtilityVM object; folder used as its scratch
func CreateWCOWUVM(ctx context.Context, t *testing.T, id, image string) (*uvm.UtilityVM, []string, string) {
func CreateWCOWUVM(ctx context.Context, t *testing.T, id, image string) (*uvm.UtilityVM, string) {
return CreateWCOWUVMFromOptsWithImage(ctx, t, uvm.NewDefaultOptionsWCOW(id, ""), image)

}
Expand All @@ -35,7 +35,7 @@ func CreateWCOWUVMFromOpts(ctx context.Context, t *testing.T, opts *uvm.OptionsW
// CreateWCOWUVMFromOptsWithImage creates a WCOW utility VM with the passed opts
// builds the LayerFolders based on `image`. Returns the UtilityVM object;
// folder used as its scratch
func CreateWCOWUVMFromOptsWithImage(ctx context.Context, t *testing.T, opts *uvm.OptionsWCOW, image string) (*uvm.UtilityVM, []string, string) {
func CreateWCOWUVMFromOptsWithImage(ctx context.Context, t *testing.T, opts *uvm.OptionsWCOW, image string) (*uvm.UtilityVM, string) {
if opts == nil {
t.Fatal("opts must be set")
}
Expand All @@ -51,7 +51,7 @@ func CreateWCOWUVMFromOptsWithImage(ctx context.Context, t *testing.T, opts *uvm
opts.LayerFolders = append(opts.LayerFolders, uvmLayers...)
opts.LayerFolders = append(opts.LayerFolders, scratchDir)

return CreateWCOWUVMFromOpts(ctx, t, opts), uvmLayers, scratchDir
return CreateWCOWUVMFromOpts(ctx, t, opts), scratchDir
}

// CreateLCOWUVM with all default options.
Expand Down
11 changes: 11 additions & 0 deletions test/functional/utilities/scratch.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,17 @@ func init() {
}
}

// CreateWCOWBlankBaseLayer creates an as-blank-as-possible base WCOW layer, which
// can be used as the base of a WCOW RW layer when it's not going to be the container's
// scratch mount.
func CreateWCOWBlankBaseLayer(ctx context.Context, t *testing.T) []string {
tempDir := CreateTempDir(t)
if err := wclayer.ConvertToBaseLayer(context.Background(), tempDir); err != nil {
t.Fatalf("Failed ConvertToBaseLayer: %s", err)
}
return []string{tempDir}
}

// CreateWCOWBlankRWLayer uses HCS to create a temp test directory containing a
// read-write layer containing a disk that can be used as a containers scratch
// space. The VHD is created with VM group access
Expand Down
2 changes: 1 addition & 1 deletion test/functional/uvm_mem_backingtype_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func runMemStartLCOWTest(t *testing.T, opts *uvm.OptionsLCOW) {
}

func runMemStartWCOWTest(t *testing.T, opts *uvm.OptionsWCOW) {
u, _, scratchDir := testutilities.CreateWCOWUVMFromOptsWithImage(context.Background(), t, opts, "microsoft/nanoserver")
u, scratchDir := testutilities.CreateWCOWUVMFromOptsWithImage(context.Background(), t, opts, "microsoft/nanoserver")
defer os.RemoveAll(scratchDir)
u.Close()
}
Expand Down
2 changes: 1 addition & 1 deletion test/functional/uvm_memory_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func TestUVMMemoryUpdateWCOW(t *testing.T) {
opts := uvm.NewDefaultOptionsWCOW(t.Name(), "")
opts.MemorySizeInMB = 1024 * 2

u, _, uvmScratchDir := testutilities.CreateWCOWUVMFromOptsWithImage(ctx, t, opts, "mcr.microsoft.com/windows/nanoserver:1909")
u, uvmScratchDir := testutilities.CreateWCOWUVMFromOptsWithImage(ctx, t, opts, "mcr.microsoft.com/windows/nanoserver:1909")
defer os.RemoveAll(uvmScratchDir)
defer u.Close()

Expand Down
2 changes: 1 addition & 1 deletion test/functional/uvm_properties_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func TestPropertiesGuestConnection_LCOW(t *testing.T) {

func TestPropertiesGuestConnection_WCOW(t *testing.T) {
testutilities.RequiresBuild(t, osversion.RS5)
uvm, _, uvmScratchDir := testutilities.CreateWCOWUVM(context.Background(), t, t.Name(), "microsoft/nanoserver")
uvm, uvmScratchDir := testutilities.CreateWCOWUVM(context.Background(), t, t.Name(), "microsoft/nanoserver")
defer os.RemoveAll(uvmScratchDir)
defer uvm.Close()

Expand Down
4 changes: 3 additions & 1 deletion test/functional/uvm_scsi_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,11 @@ func TestSCSIAddRemoveLCOW(t *testing.T) {
func TestSCSIAddRemoveWCOW(t *testing.T) {
testutilities.RequiresBuild(t, osversion.RS5)
// TODO make the image configurable to the build we're testing on
u, layers, uvmScratchDir := testutilities.CreateWCOWUVM(context.Background(), t, t.Name(), "mcr.microsoft.com/windows/nanoserver:1903")
u, uvmScratchDir := testutilities.CreateWCOWUVM(context.Background(), t, t.Name(), "mcr.microsoft.com/windows/nanoserver:1903")
defer os.RemoveAll(uvmScratchDir)
defer u.Close()
layers := testutilities.CreateWCOWBlankBaseLayer(context.Background(), t)
defer os.RemoveAll(layers[0])

testSCSIAddRemoveSingle(t, u, `c:\`, "windows", layers)
}
Expand Down
2 changes: 1 addition & 1 deletion test/functional/uvm_vsmb_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import (
// TestVSMB tests adding/removing VSMB layers from a v2 Windows utility VM
func TestVSMB(t *testing.T) {
testutilities.RequiresBuild(t, osversion.RS5)
uvm, _, uvmScratchDir := testutilities.CreateWCOWUVM(context.Background(), t, t.Name(), "microsoft/nanoserver")
uvm, uvmScratchDir := testutilities.CreateWCOWUVM(context.Background(), t, t.Name(), "microsoft/nanoserver")
defer os.RemoveAll(uvmScratchDir)
defer uvm.Close()

Expand Down

0 comments on commit cff971b

Please sign in to comment.