Skip to content

Commit b6a131c

Browse files
author
Shreyansh Sancheti
committed
parity: restructure LCOW HCS document parity tests
Address review feedback from PR #2629. Major changes: Source changes (internal/uvm/): - Export VerifyOptions, MakeLCOWDoc, MakeLCOWSecurityDoc as public functions so they can be called directly from the test package. - Remove MakeLCOWDocument composite wrapper — test assembles the pipeline itself using the exported primitives. - Add NewUtilityVMForDoc constructor in types.go. This is needed because MakeLCOWDoc takes a *UtilityVM parameter and reads its unexported fields (scsiControllerCount, vpmemMaxCount, etc). The test cannot set these fields directly from outside the package, so NewUtilityVMForDoc creates a minimal UtilityVM with only the fields needed for document generation. - Original source code comments preserved — only function names changed from lowercase to uppercase. Test changes (test/parity/vm/): - Moved from test/parity/ to test/parity/vm/ for future WCOW support. - Removed functional build tag — these are not functional tests. - Removed all normalization (nil-vs-empty, map sorting, owner zeroing). cmp.Diff handles maps natively. Real differences are not masked. - Merged pipeline helpers into hcs_document_creator_test.go. - Removed helpers_test.go — setupBootFiles and jsonToString moved inline. - Generic doc.go that covers both LCOW and future WCOW. - All test cases explicitly populate every field (CPU, memory, MMIO, QoS, CPUGroupID) so comparison always checks populated values, not defaults. - Use maps.Clone for annotation copying. - Use testing.Verbose() to gate debug logging. - Descriptive error messages throughout. All 8 tests pass (3 document parity + 5 field parity). Signed-off-by: Shreyansh Sancheti <shsancheti@microsoft.com>
1 parent 3b9a4af commit b6a131c

7 files changed

Lines changed: 352 additions & 7 deletions

File tree

internal/uvm/create.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ func verifyWCOWBootFiles(bootFiles *WCOWBootFiles) error {
160160
}
161161

162162
// Verifies that the final UVM options are correct and supported.
163-
func verifyOptions(_ context.Context, options interface{}) error {
163+
func VerifyOptions(_ context.Context, options interface{}) error {
164164
switch opts := options.(type) {
165165
case *OptionsLCOW:
166166
if opts.EnableDeferredCommit && !opts.AllowOvercommit {

internal/uvm/create_lcow.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ func makeLCOWVMGSDoc(ctx context.Context, opts *OptionsLCOW, uvm *UtilityVM) (_
458458
// This is done prior to json seriaisation and sending to the HCS layer to actually do the work of creating the VM.
459459
// Many details are quite different (see the typical JSON examples), in particular it boots from a VMGS file
460460
// which contains both the kernel and initrd as well as kernel boot options.
461-
func makeLCOWSecurityDoc(ctx context.Context, opts *OptionsLCOW, uvm *UtilityVM) (_ *hcsschema.ComputeSystem, err error) {
461+
func MakeLCOWSecurityDoc(ctx context.Context, opts *OptionsLCOW, uvm *UtilityVM) (_ *hcsschema.ComputeSystem, err error) {
462462
doc, vmgsErr := makeLCOWVMGSDoc(ctx, opts, uvm)
463463
if vmgsErr != nil {
464464
return nil, vmgsErr
@@ -537,7 +537,7 @@ Example JSON document produced once the hcsschema.ComputeSytem returned by makeL
537537
*/
538538

539539
// Make the ComputeSystem document object that will be serialized to json to be presented to the HCS api.
540-
func makeLCOWDoc(ctx context.Context, opts *OptionsLCOW, uvm *UtilityVM) (_ *hcsschema.ComputeSystem, err error) {
540+
func MakeLCOWDoc(ctx context.Context, opts *OptionsLCOW, uvm *UtilityVM) (_ *hcsschema.ComputeSystem, err error) {
541541
if logrus.IsLevelEnabled(logrus.TraceLevel) {
542542
log.G(ctx).WithField("options", log.Format(ctx, opts)).Trace("makeLCOWDoc")
543543
}
@@ -931,22 +931,22 @@ func CreateLCOW(ctx context.Context, opts *OptionsLCOW) (_ *UtilityVM, err error
931931
uvm.scsiControllerCount = 4
932932
}
933933

934-
if err = verifyOptions(ctx, opts); err != nil {
934+
if err = VerifyOptions(ctx, opts); err != nil {
935935
return nil, errors.Wrap(err, errBadUVMOpts.Error())
936936
}
937937

938938
// HCS config for SNP isolated vm is quite different to the usual case
939939
var doc *hcsschema.ComputeSystem
940940
if opts.SecurityPolicyEnabled {
941-
doc, err = makeLCOWSecurityDoc(ctx, opts, uvm)
941+
doc, err = MakeLCOWSecurityDoc(ctx, opts, uvm)
942942
if logrus.IsLevelEnabled(logrus.TraceLevel) {
943943
log.G(ctx).WithFields(logrus.Fields{
944944
"doc": log.Format(ctx, doc),
945945
logrus.ErrorKey: err,
946946
}).Trace("create_lcow::CreateLCOW makeLCOWSecurityDoc result")
947947
}
948948
} else {
949-
doc, err = makeLCOWDoc(ctx, opts, uvm)
949+
doc, err = MakeLCOWDoc(ctx, opts, uvm)
950950
if logrus.IsLevelEnabled(logrus.TraceLevel) {
951951
log.G(ctx).WithFields(logrus.Fields{
952952
"doc": log.Format(ctx, doc),

internal/uvm/create_wcow.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -586,7 +586,7 @@ func CreateWCOW(ctx context.Context, opts *OptionsWCOW) (_ *UtilityVM, err error
586586
}
587587
}()
588588

589-
if err := verifyOptions(ctx, opts); err != nil {
589+
if err := VerifyOptions(ctx, opts); err != nil {
590590
return nil, errors.Wrap(err, errBadUVMOpts.Error())
591591
}
592592

internal/uvm/types.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,22 @@ func (uvm *UtilityVM) ScratchEncryptionEnabled() bool {
152152
return uvm.encryptScratch
153153
}
154154

155+
// NewUtilityVMForDoc creates a minimal UtilityVM with the fields needed by
156+
// MakeLCOWDoc and MakeLCOWSecurityDoc for HCS document generation. This is
157+
// not a runnable VM — it exists only for parity testing.
158+
func NewUtilityVMForDoc(id, owner string, scsiControllerCount, vpmemMaxCount uint32, vpmemMaxSizeBytes uint64, vpmemMultiMapping bool) *UtilityVM {
159+
return &UtilityVM{
160+
id: id,
161+
owner: owner,
162+
operatingSystem: "linux",
163+
scsiControllerCount: scsiControllerCount,
164+
vpmemMaxCount: vpmemMaxCount,
165+
vpmemMaxSizeBytes: vpmemMaxSizeBytes,
166+
vpciDevices: make(map[VPCIDeviceID]*VPCIDevice),
167+
vpmemMultiMapping: vpmemMultiMapping,
168+
}
169+
}
170+
155171
type WCOWBootFilesType uint8
156172

157173
const (

test/parity/vm/doc.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
//go:build windows
2+
3+
// Package vm validates that the v2 VM document builders produce HCS
4+
// ComputeSystem documents equivalent to the legacy shim pipelines.
5+
//
6+
// Currently covers LCOW parity between:
7+
// - Legacy: OCI spec → oci.UpdateSpecFromOptions → oci.ProcessAnnotations →
8+
// oci.SpecToUVMCreateOpts → uvm.MakeLCOWDoc → *hcsschema.ComputeSystem
9+
// - V2: vm.Spec + runhcsopts.Options → lcow.BuildSandboxConfig →
10+
// *hcsschema.ComputeSystem + *SandboxOptions
11+
//
12+
// WCOW parity will be added in a future PR.
13+
package vm
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
//go:build windows
2+
3+
package vm
4+
5+
import (
6+
"context"
7+
"encoding/json"
8+
"fmt"
9+
"os"
10+
"path/filepath"
11+
"testing"
12+
13+
"github.com/opencontainers/runtime-spec/specs-go"
14+
15+
runhcsopts "github.com/Microsoft/hcsshim/cmd/containerd-shim-runhcs-v1/options"
16+
lcowbuilder "github.com/Microsoft/hcsshim/internal/builder/vm/lcow"
17+
hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2"
18+
"github.com/Microsoft/hcsshim/internal/oci"
19+
"github.com/Microsoft/hcsshim/internal/uvm"
20+
"github.com/Microsoft/hcsshim/internal/vm/vmutils"
21+
"github.com/Microsoft/hcsshim/osversion"
22+
vm "github.com/Microsoft/hcsshim/sandbox-spec/vm/v2"
23+
)
24+
25+
// buildLegacyLCOWDocument creates the HCS document for an LCOW VM using the
26+
// legacy shim pipeline. It runs the same sequence as createInternal → createPod
27+
// → CreateLCOW: annotation processing, spec conversion, option verification,
28+
// and document generation.
29+
func buildLegacyLCOWDocument(
30+
ctx context.Context,
31+
spec specs.Spec,
32+
shimOpts *runhcsopts.Options,
33+
bundle string,
34+
) (*hcsschema.ComputeSystem, *uvm.OptionsLCOW, error) {
35+
// Step 1: Merge shim options into the OCI spec annotations.
36+
spec = oci.UpdateSpecFromOptions(spec, shimOpts)
37+
38+
// Step 2: Expand annotation groups (e.g., security toggles).
39+
if err := oci.ProcessAnnotations(ctx, spec.Annotations); err != nil {
40+
return nil, nil, fmt.Errorf("failed to expand OCI annotations: %w", err)
41+
}
42+
43+
// Step 3: Convert OCI spec + annotations into OptionsLCOW.
44+
rawOpts, err := oci.SpecToUVMCreateOpts(ctx, &spec, "test-parity@vm", "test-owner")
45+
if err != nil {
46+
return nil, nil, fmt.Errorf("failed to convert OCI spec to UVM create options: %w", err)
47+
}
48+
opts := rawOpts.(*uvm.OptionsLCOW)
49+
opts.BundleDirectory = bundle
50+
51+
// Step 4: Verify options constraints (same as CreateLCOW).
52+
if err := uvm.VerifyOptions(ctx, opts); err != nil {
53+
return nil, nil, fmt.Errorf("option verification failed: %w", err)
54+
}
55+
56+
// Step 5: Build the temporary UtilityVM with fields that MakeLCOWDoc reads.
57+
scsiCount := opts.SCSIControllerCount
58+
if osversion.Build() >= osversion.RS5 && opts.VPMemDeviceCount == 0 {
59+
scsiCount = 4
60+
}
61+
tempUVM := uvm.NewUtilityVMForDoc(
62+
opts.ID, opts.Owner,
63+
scsiCount, opts.VPMemDeviceCount, opts.VPMemSizeBytes,
64+
!opts.VPMemNoMultiMapping,
65+
)
66+
67+
// Step 6: Generate the HCS document.
68+
doc, err := uvm.MakeLCOWDoc(ctx, opts, tempUVM)
69+
if err != nil {
70+
return nil, nil, fmt.Errorf("failed to generate legacy LCOW HCS document: %w", err)
71+
}
72+
73+
return doc, opts, nil
74+
}
75+
76+
// buildV2LCOWDocument creates the HCS document and sandbox options from the
77+
// provided VM spec and runhcs options using the v2 modular builder.
78+
// The returned document can be used to create a VM directly via HCS.
79+
func buildV2LCOWDocument(
80+
ctx context.Context,
81+
shimOpts *runhcsopts.Options,
82+
spec *vm.Spec,
83+
bundle string,
84+
) (*hcsschema.ComputeSystem, *lcowbuilder.SandboxOptions, error) {
85+
return lcowbuilder.BuildSandboxConfig(ctx, "test-owner", bundle, shimOpts, spec)
86+
}
87+
88+
// setupBootFiles creates a temporary directory containing the kernel and rootfs
89+
// files that both document builders probe during boot configuration resolution.
90+
func setupBootFiles(t *testing.T) string {
91+
t.Helper()
92+
dir := t.TempDir()
93+
for _, name := range []string{
94+
vmutils.KernelFile,
95+
vmutils.UncompressedKernelFile,
96+
vmutils.InitrdFile,
97+
vmutils.VhdFile,
98+
} {
99+
if err := os.WriteFile(filepath.Join(dir, name), []byte("test"), 0644); err != nil {
100+
t.Fatalf("failed to create boot file %s: %v", name, err)
101+
}
102+
}
103+
return dir
104+
}
105+
106+
// jsonToString serializes v to indented JSON for test log output.
107+
func jsonToString(v interface{}) string {
108+
b, err := json.MarshalIndent(v, "", " ")
109+
if err != nil {
110+
panic(err)
111+
}
112+
return string(b)
113+
}

0 commit comments

Comments
 (0)