Skip to content

Commit

Permalink
Set default time zone for WCOW UVM
Browse files Browse the repository at this point in the history
For the v2 hcs code paths it seems the only time a time zone is set is if
a new field on the guest connection settings is present (which we don't have)
while using the internal guest connection (shim -> hcs -> gcs). Otherwise
the guest is just left without a time zone set, so things like tzutil or
the get-timezone powershell cmdlet will return an invalid time zone set.
We swapped to always using the external guest connection we maintain in the
shim so we need to set a time zone explicitly.

This change issues a request to the gcs to set a timezone via the same method that
hcs uses internally. It sets the guests time zone to whatever is present on
the host which is the docker behavior, and then all containers in the vm
should inherit this. Additionally expose an option to override this behavior and
just set the time zone to GMT. If the container wants to change its timezone
to something else, it is free to on supported images.

See https://docs.microsoft.com/en-us/virtualization/windowscontainers/manage-containers/virtual-time-zone

Signed-off-by: Daniel Canter <dcanter@microsoft.com>
  • Loading branch information
dcantah committed Nov 12, 2021
1 parent db9908f commit 78d7419
Show file tree
Hide file tree
Showing 28 changed files with 1,173 additions and 575 deletions.
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

0 comments on commit 78d7419

Please sign in to comment.