Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set default time zone for WCOW UVM #1192

Merged
merged 1 commit into from
Nov 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
601 changes: 328 additions & 273 deletions cmd/containerd-shim-runhcs-v1/options/runhcs.pb.go

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion cmd/containerd-shim-runhcs-v1/options/runhcs.proto
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,13 @@ message Options {
// The typical example is if Containerd has restarted but is expected to come back online. A 0 for this field is interpreted as an infinite
// timeout.
int32 io_retry_timeout_in_sec = 17;

// default_container_annotations specifies a set of annotations that should be set for every workload container
map<string, string> default_container_annotations = 18;

// no_inherit_host_timezone specifies to skip inheriting the hosts time zone for WCOW UVMs and instead default to
// UTC.
bool no_inherit_host_timezone = 19;
}

// ProcessDetails contains additional information about a process. This is the additional
Expand Down
28 changes: 19 additions & 9 deletions internal/gcs/guestconnection.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
"net"
Expand All @@ -16,9 +15,11 @@ import (
"github.com/Microsoft/go-winio/pkg/guid"
"github.com/Microsoft/hcsshim/internal/cow"
"github.com/Microsoft/hcsshim/internal/hcs/schema1"
hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2"
"github.com/Microsoft/hcsshim/internal/log"
"github.com/Microsoft/hcsshim/internal/logfields"
"github.com/Microsoft/hcsshim/internal/oc"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"go.opencensus.io/trace"
)
Expand All @@ -45,6 +46,11 @@ func HvsockIoListen(vmID guid.GUID) IoListenFunc {
}
}

type InitialGuestState struct {
// Timezone is only honored for Windows guests.
Timezone *hcsschema.TimeZoneInformation
}

// GuestConnectionConfig contains options for creating a guest connection.
type GuestConnectionConfig struct {
// Conn specifies the connection to use for the bridge. It will be closed
Expand All @@ -54,6 +60,8 @@ type GuestConnectionConfig struct {
Log *logrus.Entry
// IoListen is the function to use to create listeners for the stdio connections.
IoListen IoListenFunc
// InitGuestState specifies settings to apply to the guest on creation/start. This includes things such as the timezone for the VM.
InitGuestState *InitialGuestState
}

// Connect establishes a GCS connection. `gcc.Conn` will be closed by this function.
Expand All @@ -73,7 +81,7 @@ func (gcc *GuestConnectionConfig) Connect(ctx context.Context, isColdStart bool)
_ = gc.brdg.Wait()
gc.clearNotifies()
}()
err = gc.connect(ctx, isColdStart)
err = gc.connect(ctx, isColdStart, gcc.InitGuestState)
if err != nil {
gc.Close()
return nil, err
Expand Down Expand Up @@ -108,7 +116,7 @@ func (gc *GuestConnection) Protocol() uint32 {
// isColdStart should be true when the UVM is being connected to for the first time post-boot.
// It should be false for subsequent connections (e.g. when connecting to a UVM that has
// been cloned).
func (gc *GuestConnection) connect(ctx context.Context, isColdStart bool) (err error) {
func (gc *GuestConnection) connect(ctx context.Context, isColdStart bool, initGuestState *InitialGuestState) (err error) {
req := negotiateProtocolRequest{
MinimumVersion: protocolVersion,
MaximumVersion: protocolVersion,
Expand All @@ -127,11 +135,15 @@ func (gc *GuestConnection) connect(ctx context.Context, isColdStart bool) (err e
gc.os = "windows"
}
if isColdStart && resp.Capabilities.SendHostCreateMessage {
conf := &uvmConfig{
SystemType: "Container",
}
if initGuestState != nil && initGuestState.Timezone != nil {
conf.TimeZoneInformation = initGuestState.Timezone
}
createReq := containerCreate{
requestBase: makeRequest(ctx, nullContainerID),
ContainerConfig: anyInString{&uvmConfig{
SystemType: "Container",
}},
requestBase: makeRequest(ctx, nullContainerID),
ContainerConfig: anyInString{conf},
}
var createResp responseBase
err = gc.brdg.RPC(ctx, rpcCreate, &createReq, &createResp, true)
Expand Down Expand Up @@ -173,9 +185,7 @@ func (gc *GuestConnection) DumpStacks(ctx context.Context) (response string, err
req := dumpStacksRequest{
requestBase: makeRequest(ctx, nullContainerID),
}

var resp dumpStacksResponse

err = gc.brdg.RPC(ctx, rpcDumpStacks, &req, &resp, false)
return resp.GuestStacks, err
}
Expand Down
3 changes: 2 additions & 1 deletion internal/gcs/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,8 @@ type containerCreate struct {
}

type uvmConfig struct {
SystemType string // must be "Container"
SystemType string // must be "Container"
TimeZoneInformation *hcsschema.TimeZoneInformation
}

type containerNotification struct {
Expand Down
28 changes: 28 additions & 0 deletions internal/hcs/schema2/system_time.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* HCS API
*
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
*
* API version: 2.1
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
*/

package hcsschema

type SystemTime struct {
Year int32 `json:"Year,omitempty"`

Month int32 `json:"Month,omitempty"`

DayOfWeek int32 `json:"DayOfWeek,omitempty"`

Day int32 `json:"Day,omitempty"`

Hour int32 `json:"Hour,omitempty"`

Minute int32 `json:"Minute,omitempty"`

Second int32 `json:"Second,omitempty"`

Milliseconds int32 `json:"Milliseconds,omitempty"`
}
26 changes: 26 additions & 0 deletions internal/hcs/schema2/time_zone_information.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* HCS API
*
* No description provided (generated by Swagger Codegen https://github.com/swagger-api/swagger-codegen)
*
* API version: 2.1
* Generated by: Swagger Codegen (https://github.com/swagger-api/swagger-codegen.git)
*/

package hcsschema

type TimeZoneInformation struct {
Bias int32 `json:"Bias,omitempty"`

StandardName string `json:"StandardName,omitempty"`

StandardDate *SystemTime `json:"StandardDate,omitempty"`

StandardBias int32 `json:"StandardBias,omitempty"`

DaylightName string `json:"DaylightName,omitempty"`

DaylightDate *SystemTime `json:"DaylightDate,omitempty"`

DaylightBias int32 `json:"DaylightBias,omitempty"`
}
1 change: 1 addition & 0 deletions internal/oci/uvm.go
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,7 @@ func SpecToUVMCreateOpts(ctx context.Context, s *specs.Spec, id, owner string) (
wopts.NetworkConfigProxy = parseAnnotationsString(s.Annotations, annotations.NetworkConfigProxy, wopts.NetworkConfigProxy)
wopts.NoDirectMap = parseAnnotationsBool(ctx, s.Annotations, annotations.VSMBNoDirectMap, wopts.NoDirectMap)
wopts.ProcessDumpLocation = parseAnnotationsString(s.Annotations, annotations.ContainerProcessDumpLocation, wopts.ProcessDumpLocation)
wopts.NoInheritHostTimezone = parseAnnotationsBool(ctx, s.Annotations, annotations.NoInheritHostTimezone, wopts.NoInheritHostTimezone)
handleAnnotationFullyPhysicallyBacked(ctx, s.Annotations, wopts)
if err := handleCloneAnnotations(ctx, s.Annotations, wopts); err != nil {
return nil, err
Expand Down
4 changes: 4 additions & 0 deletions internal/uvm/create_wcow.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ type OptionsWCOW struct {

// NoDirectMap specifies that no direct mapping should be used for any VSMBs added to the UVM
NoDirectMap bool

// NoInheritHostTimezone specifies whether to not inherit the hosts timezone for the UVM. UTC will be set as the default for the VM instead.
NoInheritHostTimezone bool
}

// NewDefaultOptionsWCOW creates the default options for a bootable version of
Expand Down Expand Up @@ -249,6 +252,7 @@ func CreateWCOW(ctx context.Context, opts *OptionsWCOW) (_ *UtilityVM, err error
vsmbDirShares: make(map[string]*VSMBShare),
vsmbFileShares: make(map[string]*VSMBShare),
vpciDevices: make(map[VPCIDeviceKey]*VPCIDevice),
noInheritHostTimezone: opts.NoInheritHostTimezone,
physicallyBacked: !opts.AllowOvercommit,
devicesPhysicallyBacked: opts.FullyPhysicallyBacked,
vsmbNoDirectMap: opts.NoDirectMap,
Expand Down
26 changes: 23 additions & 3 deletions internal/uvm/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,11 +229,31 @@ func (uvm *UtilityVM) Start(ctx context.Context) (err error) {
if err != nil {
return fmt.Errorf("failed to connect to GCS: %s", err)
}

var initGuestState *gcs.InitialGuestState
if uvm.OS() == "windows" {
// Default to setting the time zone in the UVM to the hosts time zone unless the client asked to avoid this behavior. If so, assign
// to UTC.
if uvm.noInheritHostTimezone {
initGuestState = &gcs.InitialGuestState{
Timezone: utcTimezone,
}
} else {
tz, err := getTimezone()
if err != nil {
return err
}
initGuestState = &gcs.InitialGuestState{
Timezone: tz,
}
}
}
// Start the GCS protocol.
gcc := &gcs.GuestConnectionConfig{
Conn: conn,
Log: log.G(ctx).WithField(logfields.UVMID, uvm.id),
IoListen: gcs.HvsockIoListen(uvm.runtimeID),
Conn: conn,
Log: log.G(ctx).WithField(logfields.UVMID, uvm.id),
IoListen: gcs.HvsockIoListen(uvm.runtimeID),
InitGuestState: initGuestState,
}
uvm.gc, err = gcc.Connect(ctx, !uvm.IsClone)
if err != nil {
Expand Down
58 changes: 58 additions & 0 deletions internal/uvm/timezone.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package uvm

import (
"fmt"

hcsschema "github.com/Microsoft/hcsshim/internal/hcs/schema2"
"golang.org/x/sys/windows"
)

// UTC has everything set to 0's. Just need to fill in the pointer fields and string identifiers.
var utcTimezone = &hcsschema.TimeZoneInformation{
StandardName: "Coordinated Universal Time",
DaylightName: "Coordinated Universal Time",
StandardDate: &hcsschema.SystemTime{},
DaylightDate: &hcsschema.SystemTime{},
}

// getTimezone returns the hosts timezone in an HCS TimeZoneInformation structure and an error if there
// is one.
func getTimezone() (*hcsschema.TimeZoneInformation, error) {
var tz windows.Timezoneinformation
_, err := windows.GetTimeZoneInformation(&tz)
if err != nil {
return nil, fmt.Errorf("failed to get time zone information: %w", err)
}
return tziToHCSSchema(&tz), nil
}

// TZIToHCSSchema converts a windows.TimeZoneInformation (TIME_ZONE_INFORMATION) to the hcs schema equivalent.
func tziToHCSSchema(tzi *windows.Timezoneinformation) *hcsschema.TimeZoneInformation {
return &hcsschema.TimeZoneInformation{
Bias: tzi.Bias,
StandardName: windows.UTF16ToString(tzi.StandardName[:]),
StandardDate: &hcsschema.SystemTime{
Year: int32(tzi.StandardDate.Year),
Month: int32(tzi.StandardDate.Month),
DayOfWeek: int32(tzi.StandardDate.DayOfWeek),
Day: int32(tzi.StandardDate.Day),
Hour: int32(tzi.StandardDate.Hour),
Second: int32(tzi.StandardDate.Second),
Minute: int32(tzi.StandardDate.Minute),
Milliseconds: int32(tzi.StandardDate.Milliseconds),
},
StandardBias: tzi.StandardBias,
DaylightName: windows.UTF16ToString(tzi.DaylightName[:]),
DaylightDate: &hcsschema.SystemTime{
Year: int32(tzi.DaylightDate.Year),
Month: int32(tzi.DaylightDate.Month),
DayOfWeek: int32(tzi.DaylightDate.DayOfWeek),
Day: int32(tzi.DaylightDate.Day),
Hour: int32(tzi.DaylightDate.Hour),
Second: int32(tzi.DaylightDate.Second),
Minute: int32(tzi.DaylightDate.Minute),
Milliseconds: int32(tzi.DaylightDate.Milliseconds),
},
DaylightBias: tzi.DaylightBias,
}
}
4 changes: 4 additions & 0 deletions internal/uvm/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,4 +135,8 @@ type UtilityVM struct {
// networkSetup handles the logic for setting up and tearing down any network configuration
// for the Utility VM.
networkSetup NetworkSetup

// noInheritHostTimezone specifies whether to not inherit the hosts timezone for the UVM. UTC will be set as the default instead.
// This only applies for WCOW.
noInheritHostTimezone bool
}
4 changes: 4 additions & 0 deletions pkg/annotations/annotations.go
Original file line number Diff line number Diff line change
Expand Up @@ -261,4 +261,8 @@ const (
// AnnotationDisableLCOWTimeSyncService is used to disable the chronyd time
// synchronization service inside the LCOW UVM.
DisableLCOWTimeSyncService = "io.microsoft.virtualmachine.lcow.timesync.disable"

// NoInheritHostTimezone specifies for the hosts timezone to not be inherited by the WCOW UVM. The UVM will be set to UTC time
// as a default.
NoInheritHostTimezone = "io.microsoft.virtualmachine.wcow.timezone.noinherit"
)
3 changes: 2 additions & 1 deletion test/cri-containerd/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,10 @@ const (
imageLcowK8sPause = "k8s.gcr.io/pause:3.1"
imageLcowAlpine = "docker.io/library/alpine:latest"
imageLcowAlpineCoreDump = "cplatpublic.azurecr.io/stackoverflow-alpine:latest"
imageWindowsProcessDump = "cplatpublic.azurecr.io/crashdump:latest"
imageLcowCosmos = "cosmosarno/spark-master:2.4.1_2019-04-18_8e864ce"
imageLcowCustomUser = "cplatpublic.azurecr.io/linux_custom_user:latest"
imageWindowsProcessDump = "cplatpublic.azurecr.io/crashdump:latest"
imageWindowsTimezone = "cplatpublic.azurecr.io/timezone:latest"
imageJobContainerHNS = "cplatpublic.azurecr.io/jobcontainer_hns:latest"
imageJobContainerETW = "cplatpublic.azurecr.io/jobcontainer_etw:latest"
imageJobContainerVHD = "cplatpublic.azurecr.io/jobcontainer_vhd:latest"
Expand Down
Loading