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

feat: propagate host timezone to guest #1931

Closed
wants to merge 1 commit into from

Conversation

lobshunter
Copy link
Contributor

This PR closes #1921, propagates host timezone to guest VM.

This doesn't work for template://alpine for it doesn't have pre-installed tzdata. And this PR doesn't use timedatectl because some distros might not be using systemd.

@balajiv113
Copy link
Member

@lobshunter Thanks for the PR.

Can you try out setting timezone via cloud-init (via pkg/cidata/)
https://cloudinit.readthedocs.io/en/latest/reference/modules.html#timezone

This way it will set for all distro supporting cloud-init. Also will be set early in boot

@jandubois
Copy link
Member

This doesn't work for template://alpine for it doesn't have pre-installed tzdata.

We can add tzdata to the image; it is just 3MB or so.

If you change the timezone via cloud-init, then we also have to update lima-init to support it, as we currently don't use cloud-init in our Alpine image.

@lobshunter lobshunter force-pushed the propagate-timezone branch 3 times, most recently from 1488564 to ca37cf0 Compare October 21, 2023 03:17
@lobshunter
Copy link
Contributor Author

Can you try out setting timezone via cloud-init (via pkg/cidata/)

Hi, I updated the cloud-init version in the second commit. PTAL which one do you prefer.

We can add tzdata to the image; it is just 3MB or so.

It's 3068 KiB for alpine:3.18.3. Should I update it here?

as we currently don't use cloud-init in our Alpine image

I am confused by this actually. Alpine doesn't have cloud-init installed, but it did run the new added 60-propagate-timezone.sh script.

Copy link
Member

@jandubois jandubois left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not happy that this PR cannot support timezones with fractional-hours offsets from GMT. E.g. India is GMT+5:30, and there are plenty more.

I wonder if there isn't a way to get an exact match to the host timezone?

pkg/cidata/cidata.TEMPLATE.d/boot/60-propagate-timezone.sh Outdated Show resolved Hide resolved
pkg/cidata/cidata.TEMPLATE.d/boot/60-propagate-timezone.sh Outdated Show resolved Hide resolved
pkg/cidata/cidata.TEMPLATE.d/lima.env Outdated Show resolved Hide resolved
pkg/cidata/cidata.go Outdated Show resolved Hide resolved
pkg/cidata/cidata.go Outdated Show resolved Hide resolved
@jandubois
Copy link
Member

Please also address linter errors!

@jandubois
Copy link
Member

I wonder if there isn't a way to get an exact match to the host timezone?

macOS sets /etc/localtime to a symlink too, so we could scrape it from there:

$ readlink /etc/localtime | sed 's#.*zoneinfo/##'
America/Vancouver

@jandubois
Copy link
Member

Hi, I updated the cloud-init version in the second commit.

I think this is not what @balajiv113 meant. cloud-init has support for setting timezone built-in. You just have to add a line to user-data, e.g.

timezone: US/Eastern

You would not need the 60-propagate-timezone.sh script because cloud-init would already do the work for you.

Please ignore my earlier review and use the proper cloud-init mechanism instead.

Alpine doesn't have cloud-init installed, but it did run the new added 60-propagate-timezone.sh script.

It uses lima-init, which implements just a subset of cloud-init using shell and awk. It doesn't have support for the timezone key in user-data, so that would have to be added. It does support running the boot script, which in turn runs the provisioning scripts.

@lobshunter lobshunter marked this pull request as draft October 21, 2023 04:14
@lobshunter
Copy link
Contributor Author

use the proper cloud-init mechanism instead.

Yes, will do.

I'm not happy that this PR cannot support timezones with fractional-hours offsets from GMT

Me neither, but I couldn't find a proper way after digging for half an hour. This is the simplest one I got.

I wonder if there isn't a way to get an exact match to the host timezone?

I did try, but the problem is Lima currently supports multiple host platforms (Linux/MacOS/Windows(that's WSL2 IIUC)). Especially for Windows, I didn't find a portable way to get the host timezone. Using Golang's built-in function would be the most portable one, but it doesn't provide the get timezone in geographic location format.

I noticed location timezone can be retrieved in /etc/localtime on MacOS and /etc/timezone on Linux(Ubuntu & Manjaro). But didn't have a Windows setup for testing. Also using platform-specific approach reduces the testability of code. I'll do more study on this.

@jandubois
Copy link
Member

Especially for Windows, I didn't find a portable way to get the host timezone.

Yeah, it is almost impossible to map Windows time zones to IANA (tzdata) time zone names. There is a Python app that seems to implement it.

However, I think WSL2 already sets this up by itself, so we may not need this. Well, I saw it set up correctly in Ubuntu, but not in Alpine. I wonder if this would work in Alpine if the tzdata package was installed, but my Windows machine is in a data centre in a different country and seems to have network issues connecting to the Alpine package CDN right now.

I don't have time to research this further right now, but if Windows doesn't need to be solved, then the solution might turn out to be simple.

@jandubois
Copy link
Member

I wonder if this would work in Alpine if the tzdata package was installed

I gave it one more try, and it did indeed work. After apk add tzdata I shut down the distro. When I started it up again, the following 2 files appeared automatically:

/ # cat /etc/timezone
America/Los_Angeles
/ # ls -l /etc/localtime
lrwxrwxrwx    1 root     0               39 Oct 20 22:19 /etc/localtime -> /usr/share/zoneinfo/America/Los_Angeles

Which means we don't need to propagate the host timezone to the guest on WSL2.

logrus.Debugf("cannot get timezone from /etc/timezone: %s", err)

// 3. try to get timezone from /etc/localtime soft link
// base on the assumption that <zoneinfo_basedir>/zone.tab file exists
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Haven't found any official documents about zoneinfo data file hierarchy, just assume zone.tab file exists.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know either. Right now it seems like a safe assumption, but there are 2 comments in the file that might want us to look for safer alternatives:

# tzdb timezone descriptions (deprecated version)
[...]
# This file is intended as a backward-compatibility aid for older programs.

Maybe we should look for a directory named Antarctica (or a file named Etc/UTC) instead?

@jandubois
Copy link
Member

@lobshunter I see you updated this PR, but it is still marked as a draft. Not sure if you are still working on it or not. When you think it is ready for another review, please move it out of draft mode and re-request a review!

Signed-off-by: pengyu <lobshunter86@gmail.com>
@lobshunter lobshunter marked this pull request as ready for review November 5, 2023 13:27
Copy link
Member

@jandubois jandubois left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I strongly feel that we don't want to look at TZ here, plus some additional comments.

Comment on lines +383 to +388
tzEnv := os.Getenv("TZ")
if tzEnv != "" &&
// skip if TZ is in "std offset dst offset, rule" format
!strings.Contains(tzEnv, " ") {
return tzEnv
}
Copy link
Member

@jandubois jandubois Nov 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think trying to parse TZ is futile; there are just too many different possible formats, and different C runtime libraries implement them differently.

Neither of them show any spaces in the "std offset dst offset" format, and I believe spaces are not even allowed optionally.

Furthermore, cloud-init only allows settings corresponding to zoneinfo entries:

timezone: (string) The timezone to use as represented in /usr/share/zoneinfo

If we want to support importing the TZ environment variable, then I think it should be optional, and added to /etc/environment. There is some precedent for doing this (we have logic for proxy settings), but this should be a separate issue, if we decide to do this at all. For this PR I think you should concentrate on synchronizing the /etc/localtime and /etc/timezone settings, and nothing else.

logrus.Debugf("cannot get timezone from /etc/timezone: %s", err)

// 3. try to get timezone from /etc/localtime soft link
// base on the assumption that <zoneinfo_basedir>/zone.tab file exists
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know either. Right now it seems like a safe assumption, but there are 2 comments in the file that might want us to look for safer alternatives:

# tzdb timezone descriptions (deprecated version)
[...]
# This file is intended as a backward-compatibility aid for older programs.

Maybe we should look for a directory named Antarctica (or a file named Etc/UTC) instead?

Comment on lines +410 to +414
tz := strings.TrimPrefix(tzFilePath, baseDir+"/")
_, err = time.LoadLocation(tz)
if err == nil {
return tz
}
Copy link
Member

@jandubois jandubois Nov 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think the check if time.LoadLocation succeeds is useful. It is the setting that has been configured, so if the datafile somehow got corrupted or deleted, it is not our problem. The interesting question would be if the data file exists inside the VM, and we can't answer that anyways.

If we kept the check, then I would still argue that we need to return "" in the failure case, and not to keep looking in higher levels of the directory tree; that seems to be wrong either way.

Suggested change
tz := strings.TrimPrefix(tzFilePath, baseDir+"/")
_, err = time.LoadLocation(tz)
if err == nil {
return tz
}
return strings.TrimPrefix(tzFilePath, baseDir+"/")

Comment on lines +417 to +419
logrus.Debugf("cannot get host timezone from /etc/localtime: %s", err)

logrus.Warnf("cannot get host timezone")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have only one log message, not 2 at different levels. Since we no longer have the err value, we should drop the first one.

@AkihiroSuda
Copy link
Member

@lobshunter Could you check comments from @jandubois ?

@AkihiroSuda AkihiroSuda added this to the v0.19.0 milestone Nov 17, 2023
@@ -136,6 +137,7 @@ func GenerateISO9660(instDir, name string, y *limayaml.LimaYAML, udpDNSLocalPort
VMType: *y.VMType,
VSockPort: vsockPort,
Plain: *y.Plain,
Timezone: getHostTimezone(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Timezone shouldn't be set when *y.Plain is true

@AkihiroSuda AkihiroSuda modified the milestones: v0.19.0, v0.20.0 Nov 30, 2023
@lobshunter
Copy link
Contributor Author

Thanks for reviewing and sorry for late response. I've been too occupied to work on this PR. All review comments make sense but I won't be able to update and test the code in the short future. It should be better to close this PR :-).

@lobshunter lobshunter closed this Nov 30, 2023
@AkihiroSuda AkihiroSuda removed this from the v0.20.0 milestone Nov 30, 2023
@@ -370,6 +372,54 @@ func GenerateISO9660(instDir, name string, y *limayaml.LimaYAML, udpDNSLocalPort
return iso9660util.Write(filepath.Join(instDir, filenames.CIDataISO), "cidata", layout)
}

// getHostTimezone tries to get host timezone from env or specific files
func getHostTimezone() string {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably we could just use https://pkg.go.dev/time#Time.Zone

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Tried but it didn't work; time.Now().Zone() returns "JST" but it is missing in /usr/share/zoneinfo/)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Propagate timezone from the host by default (unless --plain is specified)
4 participants