|
@@ -31,37 +31,99 @@ const (
|
|
|
type OutputHandler func(io.Reader)
|
|
|
|
|
|
const (
|
|
|
- initrdFile = "initrd.img"
|
|
|
- vhdFile = "rootfs.vhd"
|
|
|
+ // InitrdFile is the default file name for an initrd.img used to boot LCOW.
|
|
|
+ InitrdFile = "initrd.img"
|
|
|
+ // VhdFile is the default file name for a rootfs.vhd used to boot LCOW.
|
|
|
+ VhdFile = "rootfs.vhd"
|
|
|
+ // KernelFile is the default file name for a kernel used to boot LCOW.
|
|
|
+ KernelFile = "kernel"
|
|
|
+ // UncompressedKernelFile is the default file name for an uncompressed
|
|
|
+ // kernel used to boot LCOW with KernelDirect.
|
|
|
+ UncompressedKernelFile = "vmlinux"
|
|
|
)
|
|
|
|
|
|
// OptionsLCOW are the set of options passed to CreateLCOW() to create a utility vm.
|
|
|
type OptionsLCOW struct {
|
|
|
*Options
|
|
|
|
|
|
- BootFilesPath string // Folder in which kernel and root file system reside. Defaults to \Program Files\Linux Containers
|
|
|
- KernelFile string // Filename under BootFilesPath for the kernel. Defaults to `kernel`
|
|
|
- KernelDirect bool // Skip UEFI and boot directly to `kernel`
|
|
|
- RootFSFile string // Filename under BootFilesPath for the UVMs root file system. Defaults are `initrd.img` or `rootfs.vhd` based on `PreferredRootFSType`.
|
|
|
- KernelBootOptions string // Additional boot options for the kernel
|
|
|
- EnableGraphicsConsole bool // If true, enable a graphics console for the utility VM
|
|
|
- ConsolePipe string // The named pipe path to use for the serial console. eg \\.\pipe\vmpipe
|
|
|
- SCSIControllerCount *uint32 // The number of SCSI controllers. Defaults to 1 if omitted. Currently we only support 0 or 1.
|
|
|
- UseGuestConnection *bool // Whether the HCS should connect to the UVM's GCS. Defaults to true
|
|
|
- ExecCommandLine string // The command line to exec from init. Defaults to GCS
|
|
|
- ForwardStdout *bool // Whether stdout will be forwarded from the executed program. Defaults to false
|
|
|
- ForwardStderr *bool // Whether stderr will be forwarded from the executed program. Defaults to true
|
|
|
- OutputHandler *OutputHandler // Controls how output received over HVSocket from the UVM is handled. Defaults to parsing output as logrus messages
|
|
|
-
|
|
|
- // Number of VPMem devices. Limit at 128. If booting UVM from VHD, device 0 is taken. LCOW Only. io.microsoft.virtualmachine.devices.virtualpmem.maximumcount
|
|
|
- VPMemDeviceCount *uint32
|
|
|
-
|
|
|
- // Size of the VPMem devices. LCOW Only. Defaults to 4GB. io.microsoft.virtualmachine.devices.virtualpmem.maximumsizebytes
|
|
|
- VPMemSizeBytes *uint64
|
|
|
-
|
|
|
- // Controls searching for the RootFSFile. Defaults to initrd (0). Can be set to VHD (1). io.microsoft.virtualmachine.lcow.preferredrootfstype
|
|
|
- // Note this uses an arbitrary annotation strict which has no direct mapping to the HCS schema.
|
|
|
- PreferredRootFSType *PreferredRootFSType
|
|
|
+ BootFilesPath string // Folder in which kernel and root file system reside. Defaults to \Program Files\Linux Containers
|
|
|
+ KernelFile string // Filename under `BootFilesPath` for the kernel. Defaults to `kernel`
|
|
|
+ KernelDirect bool // Skip UEFI and boot directly to `kernel`
|
|
|
+ RootFSFile string // Filename under `BootFilesPath` for the UVMs root file system. Defaults to `InitrdFile`
|
|
|
+ KernelBootOptions string // Additional boot options for the kernel
|
|
|
+ EnableGraphicsConsole bool // If true, enable a graphics console for the utility VM
|
|
|
+ ConsolePipe string // The named pipe path to use for the serial console. eg \\.\pipe\vmpipe
|
|
|
+ SCSIControllerCount uint32 // The number of SCSI controllers. Defaults to 1. Currently we only support 0 or 1.
|
|
|
+ UseGuestConnection bool // Whether the HCS should connect to the UVM's GCS. Defaults to true
|
|
|
+ ExecCommandLine string // The command line to exec from init. Defaults to GCS
|
|
|
+ ForwardStdout bool // Whether stdout will be forwarded from the executed program. Defaults to false
|
|
|
+ ForwardStderr bool // Whether stderr will be forwarded from the executed program. Defaults to true
|
|
|
+ OutputHandler OutputHandler `json:"-"` // Controls how output received over HVSocket from the UVM is handled. Defaults to parsing output as logrus messages
|
|
|
+ VPMemDeviceCount uint32 // Number of VPMem devices. Defaults to `DefaultVPMEMCount`. Limit at 128. If booting UVM from VHD, device 0 is taken.
|
|
|
+ VPMemSizeBytes uint64 // Size of the VPMem devices. Defaults to `DefaultVPMemSizeBytes`.
|
|
|
+ PreferredRootFSType PreferredRootFSType // If `KernelFile` is `InitrdFile` use `PreferredRootFSTypeInitRd`. If `KernelFile` is `VhdFile` use `PreferredRootFSTypeVHD`
|
|
|
+}
|
|
|
+
|
|
|
+// NewDefaultOptionsLCOW creates the default options for a bootable version of
|
|
|
+// LCOW.
|
|
|
+//
|
|
|
+// `id` the ID of the compute system. If not passed will generate a new GUID.
|
|
|
+//
|
|
|
+// `owner` the owner of the compute system. If not passed will use the
|
|
|
+// executable files name.
|
|
|
+func NewDefaultOptionsLCOW(id, owner string) *OptionsLCOW {
|
|
|
+ // Use KernelDirect boot by default on all builds that support it.
|
|
|
+ kernelDirectSupported := osversion.Get().Build >= 18286
|
|
|
+ opts := &OptionsLCOW{
|
|
|
+ Options: &Options{
|
|
|
+ ID: id,
|
|
|
+ Owner: owner,
|
|
|
+ MemorySizeInMB: 1024,
|
|
|
+ AllowOvercommit: true,
|
|
|
+ EnableDeferredCommit: false,
|
|
|
+ ProcessorCount: defaultProcessorCount(),
|
|
|
+ },
|
|
|
+ BootFilesPath: filepath.Join(os.Getenv("ProgramFiles"), "Linux Containers"),
|
|
|
+ KernelFile: KernelFile,
|
|
|
+ KernelDirect: kernelDirectSupported,
|
|
|
+ RootFSFile: InitrdFile,
|
|
|
+ KernelBootOptions: "",
|
|
|
+ EnableGraphicsConsole: false,
|
|
|
+ ConsolePipe: "",
|
|
|
+ SCSIControllerCount: 1,
|
|
|
+ UseGuestConnection: true,
|
|
|
+ ExecCommandLine: fmt.Sprintf("/bin/gcs -log-format json -loglevel %s", logrus.StandardLogger().Level.String()),
|
|
|
+ ForwardStdout: false,
|
|
|
+ ForwardStderr: true,
|
|
|
+ OutputHandler: parseLogrus,
|
|
|
+ VPMemDeviceCount: DefaultVPMEMCount,
|
|
|
+ VPMemSizeBytes: DefaultVPMemSizeBytes,
|
|
|
+ PreferredRootFSType: PreferredRootFSTypeInitRd,
|
|
|
+ }
|
|
|
+
|
|
|
+ if opts.ID == "" {
|
|
|
+ opts.ID = guid.New().String()
|
|
|
+ }
|
|
|
+ if opts.Owner == "" {
|
|
|
+ opts.Owner = filepath.Base(os.Args[0])
|
|
|
+ }
|
|
|
+
|
|
|
+ if _, err := os.Stat(filepath.Join(opts.BootFilesPath, VhdFile)); err == nil {
|
|
|
+ // We have a rootfs.vhd in the boot files path. Use it over an initrd.img
|
|
|
+ opts.RootFSFile = VhdFile
|
|
|
+ opts.PreferredRootFSType = PreferredRootFSTypeVHD
|
|
|
+ }
|
|
|
+
|
|
|
+ if kernelDirectSupported {
|
|
|
+ // KernelDirect supports uncompressed kernel if the kernel is present.
|
|
|
+ // Default to uncompressed if on box. NOTE: If `kernel` is already
|
|
|
+ // uncompressed and simply named 'kernel' it will still be used
|
|
|
+ // uncompressed automatically.
|
|
|
+ if _, err := os.Stat(filepath.Join(opts.BootFilesPath, UncompressedKernelFile)); err == nil {
|
|
|
+ opts.KernelFile = UncompressedKernelFile
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return opts
|
|
|
}
|
|
|
|
|
|
const linuxLogVsockPort = 109
|
|
@@ -70,91 +132,41 @@ const linuxLogVsockPort = 109
|
|
|
func CreateLCOW(opts *OptionsLCOW) (_ *UtilityVM, err error) {
|
|
|
logrus.Debugf("uvm::CreateLCOW %+v", opts)
|
|
|
|
|
|
- if opts.Options == nil {
|
|
|
- opts.Options = &Options{}
|
|
|
+ // We dont serialize OutputHandler so if it is missing we need to put it back to the default.
|
|
|
+ if opts.OutputHandler == nil {
|
|
|
+ opts.OutputHandler = parseLogrus
|
|
|
}
|
|
|
|
|
|
uvm := &UtilityVM{
|
|
|
id: opts.ID,
|
|
|
owner: opts.Owner,
|
|
|
operatingSystem: "linux",
|
|
|
- scsiControllerCount: 1,
|
|
|
- vpmemMaxCount: DefaultVPMEMCount,
|
|
|
- vpmemMaxSizeBytes: DefaultVPMemSizeBytes,
|
|
|
+ scsiControllerCount: opts.SCSIControllerCount,
|
|
|
+ vpmemMaxCount: opts.VPMemDeviceCount,
|
|
|
+ vpmemMaxSizeBytes: opts.VPMemSizeBytes,
|
|
|
}
|
|
|
|
|
|
- // Defaults if omitted by caller.
|
|
|
- // TODO: Change this. Don't auto generate ID if omitted. Avoids the chicken-and-egg problem
|
|
|
- if uvm.id == "" {
|
|
|
- uvm.id = guid.New().String()
|
|
|
+ kernelFullPath := filepath.Join(opts.BootFilesPath, opts.KernelFile)
|
|
|
+ if _, err := os.Stat(kernelFullPath); os.IsNotExist(err) {
|
|
|
+ return nil, fmt.Errorf("kernel: '%s' not found", kernelFullPath)
|
|
|
}
|
|
|
- if uvm.owner == "" {
|
|
|
- uvm.owner = filepath.Base(os.Args[0])
|
|
|
- }
|
|
|
- if opts.UseGuestConnection == nil {
|
|
|
- val := true
|
|
|
- opts.UseGuestConnection = &val
|
|
|
+ rootfsFullPath := filepath.Join(opts.BootFilesPath, opts.RootFSFile)
|
|
|
+ if _, err := os.Stat(rootfsFullPath); os.IsNotExist(err) {
|
|
|
+ return nil, fmt.Errorf("boot file: '%s' not found", rootfsFullPath)
|
|
|
}
|
|
|
|
|
|
- if opts.BootFilesPath == "" {
|
|
|
- opts.BootFilesPath = filepath.Join(os.Getenv("ProgramFiles"), "Linux Containers")
|
|
|
- }
|
|
|
- if opts.KernelFile == "" {
|
|
|
- opts.KernelFile = "kernel"
|
|
|
+ if opts.SCSIControllerCount > 1 {
|
|
|
+ return nil, fmt.Errorf("SCSI controller count must be 0 or 1") // Future extension here for up to 4
|
|
|
}
|
|
|
- if _, err := os.Stat(filepath.Join(opts.BootFilesPath, opts.KernelFile)); os.IsNotExist(err) {
|
|
|
- return nil, fmt.Errorf("kernel '%s' not found", filepath.Join(opts.BootFilesPath, opts.KernelFile))
|
|
|
- }
|
|
|
- if opts.PreferredRootFSType == nil {
|
|
|
- v := PreferredRootFSTypeInitRd
|
|
|
- opts.PreferredRootFSType = &v
|
|
|
- }
|
|
|
- if opts.RootFSFile == "" {
|
|
|
- switch *opts.PreferredRootFSType {
|
|
|
- case PreferredRootFSTypeInitRd:
|
|
|
- opts.RootFSFile = initrdFile
|
|
|
- case PreferredRootFSTypeVHD:
|
|
|
- opts.RootFSFile = "rootfs.vhd"
|
|
|
- }
|
|
|
- }
|
|
|
- if opts.ForwardStdout == nil {
|
|
|
- val := false
|
|
|
- opts.ForwardStdout = &val
|
|
|
- }
|
|
|
- if opts.ForwardStderr == nil {
|
|
|
- val := true
|
|
|
- opts.ForwardStderr = &val
|
|
|
- }
|
|
|
- if opts.OutputHandler == nil {
|
|
|
- val := OutputHandler(parseLogrus)
|
|
|
- opts.OutputHandler = &val
|
|
|
- }
|
|
|
-
|
|
|
- if _, err := os.Stat(filepath.Join(opts.BootFilesPath, opts.RootFSFile)); os.IsNotExist(err) {
|
|
|
- return nil, fmt.Errorf("%s not found under %s", opts.RootFSFile, opts.BootFilesPath)
|
|
|
- }
|
|
|
-
|
|
|
- if opts.SCSIControllerCount != nil {
|
|
|
- if *opts.SCSIControllerCount > 1 {
|
|
|
- return nil, fmt.Errorf("SCSI controller count must be 0 or 1") // Future extension here for up to 4
|
|
|
- }
|
|
|
- uvm.scsiControllerCount = *opts.SCSIControllerCount
|
|
|
- }
|
|
|
- if opts.VPMemDeviceCount != nil {
|
|
|
- if *opts.VPMemDeviceCount > MaxVPMEMCount {
|
|
|
- return nil, fmt.Errorf("vpmem device count cannot be greater than %d", MaxVPMEMCount)
|
|
|
- }
|
|
|
- uvm.vpmemMaxCount = *opts.VPMemDeviceCount
|
|
|
+ if opts.VPMemDeviceCount > MaxVPMEMCount {
|
|
|
+ return nil, fmt.Errorf("vpmem device count cannot be greater than %d", MaxVPMEMCount)
|
|
|
}
|
|
|
if uvm.vpmemMaxCount > 0 {
|
|
|
- if opts.VPMemSizeBytes != nil {
|
|
|
- if *opts.VPMemSizeBytes%4096 != 0 {
|
|
|
- return nil, fmt.Errorf("opts.VPMemSizeBytes must be a multiple of 4096")
|
|
|
- }
|
|
|
- uvm.vpmemMaxSizeBytes = *opts.VPMemSizeBytes
|
|
|
+ if opts.VPMemSizeBytes%4096 != 0 {
|
|
|
+ return nil, fmt.Errorf("opts.VPMemSizeBytes must be a multiple of 4096")
|
|
|
}
|
|
|
} else {
|
|
|
- if *opts.PreferredRootFSType == PreferredRootFSTypeVHD {
|
|
|
+ if opts.PreferredRootFSType == PreferredRootFSTypeVHD {
|
|
|
return nil, fmt.Errorf("PreferredRootFSTypeVHD requires at least one VPMem device")
|
|
|
}
|
|
|
}
|
|
@@ -167,17 +179,16 @@ func CreateLCOW(opts *OptionsLCOW) (_ *UtilityVM, err error) {
|
|
|
SchemaVersion: schemaversion.SchemaV21(),
|
|
|
ShouldTerminateOnLastHandleClosed: true,
|
|
|
VirtualMachine: &hcsschema.VirtualMachine{
|
|
|
- Chipset: &hcsschema.Chipset{},
|
|
|
+ StopOnReset: true,
|
|
|
+ Chipset: &hcsschema.Chipset{},
|
|
|
ComputeTopology: &hcsschema.Topology{
|
|
|
Memory: &hcsschema.Memory2{
|
|
|
- SizeInMB: normalizeMemory(opts.MemorySizeInMB),
|
|
|
- // AllowOvercommit `true` by default if not passed.
|
|
|
- AllowOvercommit: opts.AllowOvercommit == nil || *opts.AllowOvercommit,
|
|
|
- // EnableDeferredCommit `false` by default if not passed.
|
|
|
- EnableDeferredCommit: opts.EnableDeferredCommit != nil && *opts.EnableDeferredCommit,
|
|
|
+ SizeInMB: opts.MemorySizeInMB,
|
|
|
+ AllowOvercommit: opts.AllowOvercommit,
|
|
|
+ EnableDeferredCommit: opts.EnableDeferredCommit,
|
|
|
},
|
|
|
Processor: &hcsschema.Processor2{
|
|
|
- Count: normalizeProcessors(opts.ProcessorCount),
|
|
|
+ Count: opts.ProcessorCount,
|
|
|
},
|
|
|
},
|
|
|
Devices: &hcsschema.Devices{
|
|
@@ -192,30 +203,13 @@ func CreateLCOW(opts *OptionsLCOW) (_ *UtilityVM, err error) {
|
|
|
},
|
|
|
}
|
|
|
|
|
|
- if *opts.UseGuestConnection {
|
|
|
+ if opts.UseGuestConnection {
|
|
|
doc.VirtualMachine.GuestConnection = &hcsschema.GuestConnection{
|
|
|
UseVsock: true,
|
|
|
UseConnectedSuspend: true,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- if !opts.KernelDirect {
|
|
|
- doc.VirtualMachine.Devices.VirtualSmb = &hcsschema.VirtualSmb{
|
|
|
- Shares: []hcsschema.VirtualSmbShare{
|
|
|
- {
|
|
|
- Name: "os",
|
|
|
- Path: opts.BootFilesPath,
|
|
|
- Options: &hcsschema.VirtualSmbShareOptions{
|
|
|
- ReadOnly: true,
|
|
|
- TakeBackupPrivilege: true,
|
|
|
- CacheIo: true,
|
|
|
- ShareRead: true,
|
|
|
- },
|
|
|
- },
|
|
|
- },
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
if uvm.scsiControllerCount > 0 {
|
|
|
// TODO: JTERRY75 - this should enumerate scsicount and add an entry per value.
|
|
|
doc.VirtualMachine.Devices.Scsi = map[string]hcsschema.Scsi{
|
|
@@ -232,7 +226,7 @@ func CreateLCOW(opts *OptionsLCOW) (_ *UtilityVM, err error) {
|
|
|
}
|
|
|
|
|
|
var kernelArgs string
|
|
|
- switch *opts.PreferredRootFSType {
|
|
|
+ switch opts.PreferredRootFSType {
|
|
|
case PreferredRootFSTypeInitRd:
|
|
|
if !opts.KernelDirect {
|
|
|
kernelArgs = "initrd=/" + opts.RootFSFile
|
|
@@ -246,13 +240,13 @@ func CreateLCOW(opts *OptionsLCOW) (_ *UtilityVM, err error) {
|
|
|
}
|
|
|
doc.VirtualMachine.Devices.VirtualPMem.Devices = map[string]hcsschema.VirtualPMemDevice{
|
|
|
"0": {
|
|
|
- HostPath: filepath.Join(opts.BootFilesPath, opts.RootFSFile),
|
|
|
+ HostPath: rootfsFullPath,
|
|
|
ReadOnly: true,
|
|
|
ImageFormat: imageFormat,
|
|
|
},
|
|
|
}
|
|
|
- if err := wclayer.GrantVmAccess(uvm.id, filepath.Join(opts.BootFilesPath, opts.RootFSFile)); err != nil {
|
|
|
- return nil, fmt.Errorf("failed to grantvmaccess to %s: %s", filepath.Join(opts.BootFilesPath, opts.RootFSFile), err)
|
|
|
+ if err := wclayer.GrantVmAccess(uvm.id, rootfsFullPath); err != nil {
|
|
|
+ return nil, fmt.Errorf("failed to grantvmaccess to %s: %s", rootfsFullPath, err)
|
|
|
}
|
|
|
// Add to our internal structure
|
|
|
uvm.vpmemDevices[0] = vpmemInfo{
|
|
@@ -296,21 +290,15 @@ func CreateLCOW(opts *OptionsLCOW) (_ *UtilityVM, err error) {
|
|
|
// created below in order to forward guest logs to logrus.
|
|
|
initArgs := "/bin/vsockexec"
|
|
|
|
|
|
- if *opts.ForwardStdout {
|
|
|
+ if opts.ForwardStdout {
|
|
|
initArgs += fmt.Sprintf(" -o %d", linuxLogVsockPort)
|
|
|
}
|
|
|
|
|
|
- if *opts.ForwardStderr {
|
|
|
+ if opts.ForwardStderr {
|
|
|
initArgs += fmt.Sprintf(" -e %d", linuxLogVsockPort)
|
|
|
}
|
|
|
|
|
|
- initArgs += " "
|
|
|
- if opts.ExecCommandLine != "" {
|
|
|
- initArgs += opts.ExecCommandLine
|
|
|
- } else {
|
|
|
- // Default to running GCS when another command isn't specified.
|
|
|
- initArgs += fmt.Sprintf("/bin/gcs -log-format json -loglevel %s", logrus.StandardLogger().Level.String())
|
|
|
- }
|
|
|
+ initArgs += " " + opts.ExecCommandLine
|
|
|
|
|
|
if vmDebugging {
|
|
|
// Launch a shell on the console.
|
|
@@ -322,18 +310,19 @@ func CreateLCOW(opts *OptionsLCOW) (_ *UtilityVM, err error) {
|
|
|
if !opts.KernelDirect {
|
|
|
doc.VirtualMachine.Chipset.Uefi = &hcsschema.Uefi{
|
|
|
BootThis: &hcsschema.UefiBootEntry{
|
|
|
- DevicePath: `\` + opts.KernelFile,
|
|
|
- DeviceType: "VmbFs",
|
|
|
- OptionalData: kernelArgs,
|
|
|
+ DevicePath: `\` + opts.KernelFile,
|
|
|
+ DeviceType: "VmbFs",
|
|
|
+ VmbFsRootPath: opts.BootFilesPath,
|
|
|
+ OptionalData: kernelArgs,
|
|
|
},
|
|
|
}
|
|
|
} else {
|
|
|
doc.VirtualMachine.Chipset.LinuxKernelDirect = &hcsschema.LinuxKernelDirect{
|
|
|
- KernelFilePath: filepath.Join(opts.BootFilesPath, opts.KernelFile),
|
|
|
+ KernelFilePath: kernelFullPath,
|
|
|
KernelCmdLine: kernelArgs,
|
|
|
}
|
|
|
- if *opts.PreferredRootFSType == PreferredRootFSTypeInitRd {
|
|
|
- doc.VirtualMachine.Chipset.LinuxKernelDirect.InitRdPath = filepath.Join(opts.BootFilesPath, opts.RootFSFile)
|
|
|
+ if opts.PreferredRootFSType == PreferredRootFSTypeInitRd {
|
|
|
+ doc.VirtualMachine.Chipset.LinuxKernelDirect.InitRdPath = rootfsFullPath
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -357,8 +346,8 @@ func CreateLCOW(opts *OptionsLCOW) (_ *UtilityVM, err error) {
|
|
|
|
|
|
// Create a socket that the executed program can send to. This is usually
|
|
|
// used by GCS to send log data.
|
|
|
- if *opts.ForwardStdout || *opts.ForwardStderr {
|
|
|
- uvm.outputHandler = *opts.OutputHandler
|
|
|
+ if opts.ForwardStdout || opts.ForwardStderr {
|
|
|
+ uvm.outputHandler = opts.OutputHandler
|
|
|
uvm.outputProcessingDone = make(chan struct{})
|
|
|
uvm.outputListener, err = uvm.listenVsock(linuxLogVsockPort)
|
|
|
if err != nil {
|