Skip to content

Commit 372f3f6

Browse files
Merge pull request microsoft#2629 from shreyanshjain7174/parity/lcow-document-test
parity: add LCOW HCS document parity tests between legacy and v2 builders
2 parents 87708ff + 658785c commit 372f3f6

8 files changed

Lines changed: 356 additions & 6 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ jobs:
318318
run: ${{ env.GOTESTSUM_CMD }} -gcflags=all=-d=checkptr -tags admin -timeout=20m ./...
319319

320320
- name: Run non-functional tests
321-
run: ${{ env.GOTESTSUM_CMD }} -mod=mod -gcflags=all=-d=checkptr ./internal/... ./pkg/...
321+
run: ${{ env.GOTESTSUM_CMD }} -mod=mod -gcflags=all=-d=checkptr ./internal/... ./pkg/... ./parity/...
322322
working-directory: test
323323

324324
- name: Build and run containerd-shim-runhcs-v1 tests

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: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -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,7 +931,7 @@ 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

@@ -946,7 +946,7 @@ func CreateLCOW(ctx context.Context, opts *OptionsLCOW) (_ *UtilityVM, err error
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
@@ -589,7 +589,7 @@ func CreateWCOW(ctx context.Context, opts *OptionsWCOW) (_ *UtilityVM, err error
589589
}
590590
}()
591591

592-
if err := verifyOptions(ctx, opts); err != nil {
592+
if err := VerifyOptions(ctx, opts); err != nil {
593593
return nil, errors.Wrap(err, errBadUVMOpts.Error())
594594
}
595595

internal/uvm/types.go

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

156+
// NewUtilityVMForDoc creates a minimal UtilityVM with the fields needed by
157+
// MakeLCOWDoc for HCS document generation in parity tests.
158+
// UtilityVM fields are unexported, so this constructor must live in the uvm package.
159+
func NewUtilityVMForDoc(id, owner string, scsiControllerCount, vpmemMaxCount uint32, vpmemMaxSizeBytes uint64, vpmemMultiMapping bool) *UtilityVM {
160+
return &UtilityVM{
161+
id: id,
162+
owner: owner,
163+
operatingSystem: "linux",
164+
scsiControllerCount: scsiControllerCount,
165+
vpmemMaxCount: vpmemMaxCount,
166+
vpmemMaxSizeBytes: vpmemMaxSizeBytes,
167+
vpciDevices: make(map[VPCIDeviceID]*VPCIDevice),
168+
vpmemMultiMapping: vpmemMultiMapping,
169+
}
170+
}
171+
156172
type WCOWBootFilesType uint8
157173

158174
const (

test/parity/vm/doc.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
//go:build windows
2+
3+
// Package vmparity validates that the v2 modular VM document builders produce
4+
// HCS ComputeSystem documents equivalent to the legacy shim pipelines.
5+
//
6+
// Currently covers LCOW; WCOW parity will be added in a future PR.
7+
package vmparity
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 vmparity
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)