Skip to content

Commit 81f2a91

Browse files
authored
Merge pull request #4217 from norio-nomura/minimize-affects-by-restarting-guestagent
Minimize effects caused by restarting guestagent
2 parents ec4491b + e9494d6 commit 81f2a91

File tree

11 files changed

+255
-60
lines changed

11 files changed

+255
-60
lines changed

cmd/lima-guestagent/daemon_linux.go

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import (
77
"errors"
88
"net"
99
"os"
10+
"os/signal"
11+
"syscall"
1012
"time"
1113

1214
"github.com/mdlayher/vsock"
@@ -26,6 +28,7 @@ func newDaemonCommand() *cobra.Command {
2628
Short: "Run the daemon",
2729
RunE: daemonAction,
2830
}
31+
daemonCommand.Flags().String("runtime-dir", "/run/lima-guestagent", "Directory to store runtime state")
2932
daemonCommand.Flags().Duration("tick", 3*time.Second, "Tick for polling events")
3033
daemonCommand.Flags().Int("vsock-port", 0, "Use vsock server instead a UNIX socket")
3134
daemonCommand.Flags().String("virtio-port", "", "Use virtio server instead a UNIX socket")
@@ -34,6 +37,13 @@ func newDaemonCommand() *cobra.Command {
3437

3538
func daemonAction(cmd *cobra.Command, _ []string) error {
3639
ctx := cmd.Context()
40+
runtimeDir, err := cmd.Flags().GetString("runtime-dir")
41+
if err != nil {
42+
return err
43+
}
44+
if err := os.MkdirAll(runtimeDir, 0o755); err != nil {
45+
return err
46+
}
3747
socket := "/run/lima-guestagent.sock"
3848
tick, err := cmd.Flags().GetDuration("tick")
3949
if err != nil {
@@ -66,11 +76,16 @@ func daemonAction(cmd *cobra.Command, _ []string) error {
6676
tickerInst = ticker.NewCompoundTicker(simpleTicker, ebpfTicker)
6777
}
6878

69-
agent, err := guestagent.New(ctx, tickerInst)
79+
ctx, stop := signal.NotifyContext(ctx, syscall.SIGTERM)
80+
defer stop()
81+
go func() {
82+
<-ctx.Done()
83+
logrus.Debug("Received SIGTERM, shutting down the guest agent")
84+
}()
85+
agent, err := guestagent.New(ctx, tickerInst, runtimeDir)
7086
if err != nil {
7187
return err
7288
}
73-
defer agent.Close()
7489

7590
err = os.RemoveAll(socket)
7691
if err != nil {
@@ -104,5 +119,6 @@ func daemonAction(cmd *cobra.Command, _ []string) error {
104119
l = socketL
105120
logrus.Infof("serving the guest agent on %q", socket)
106121
}
107-
return server.StartServer(l, &server.GuestServer{Agent: agent, TunnelS: portfwdserver.NewTunnelServer()})
122+
defer logrus.Debug("exiting lima-guestagent daemon")
123+
return server.StartServer(ctx, l, &server.GuestServer{Agent: agent, TunnelS: portfwdserver.NewTunnelServer()})
108124
}

cmd/lima-guestagent/install_systemd_linux.go

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
package main
55

66
import (
7+
"bytes"
78
_ "embed"
89
"errors"
910
"fmt"
1011
"os"
1112
"os/exec"
1213
"path/filepath"
14+
"slices"
1315
"strings"
1416

1517
"github.com/sirupsen/logrus"
@@ -24,13 +26,18 @@ func newInstallSystemdCommand() *cobra.Command {
2426
Short: "Install a systemd unit (user)",
2527
RunE: installSystemdAction,
2628
}
29+
installSystemdCommand.Flags().Bool("guestagent-updated", false, "Indicate that the guest agent has been updated")
2730
installSystemdCommand.Flags().Int("vsock-port", 0, "Use vsock server on specified port")
2831
installSystemdCommand.Flags().String("virtio-port", "", "Use virtio server instead a UNIX socket")
2932
return installSystemdCommand
3033
}
3134

3235
func installSystemdAction(cmd *cobra.Command, _ []string) error {
3336
ctx := cmd.Context()
37+
guestAgentUpdated, err := cmd.Flags().GetBool("guestagent-updated")
38+
if err != nil {
39+
return err
40+
}
3441
vsockPort, err := cmd.Flags().GetInt("vsock-port")
3542
if err != nil {
3643
return err
@@ -48,24 +55,42 @@ func installSystemdAction(cmd *cobra.Command, _ []string) error {
4855
return err
4956
}
5057
unitPath := "/etc/systemd/system/lima-guestagent.service"
58+
unitFileChanged := true
5159
if _, err := os.Stat(unitPath); !errors.Is(err, os.ErrNotExist) {
52-
logrus.Infof("File %q already exists, overwriting", unitPath)
60+
if existingUnit, err := os.ReadFile(unitPath); err == nil && bytes.Equal(unit, existingUnit) {
61+
logrus.Infof("File %q is up-to-date", unitPath)
62+
unitFileChanged = false
63+
} else {
64+
logrus.Infof("File %q needs update", unitPath)
65+
}
5366
} else {
5467
unitDir := filepath.Dir(unitPath)
5568
if err := os.MkdirAll(unitDir, 0o755); err != nil {
5669
return err
5770
}
5871
}
59-
if err := os.WriteFile(unitPath, unit, 0o644); err != nil {
60-
return err
72+
if unitFileChanged {
73+
if err := os.WriteFile(unitPath, unit, 0o644); err != nil {
74+
return err
75+
}
76+
logrus.Infof("Written file %q", unitPath)
77+
} else if !guestAgentUpdated {
78+
logrus.Info("lima-guestagent.service already up-to-date")
79+
return nil
6180
}
62-
logrus.Infof("Written file %q", unitPath)
63-
args := [][]string{
64-
{"daemon-reload"},
65-
{"enable", "lima-guestagent.service"},
66-
{"start", "lima-guestagent.service"},
67-
{"try-restart", "lima-guestagent.service"},
81+
// unitFileChanged || guestAgentUpdated
82+
args := make([][]string, 0, 4)
83+
if unitFileChanged {
84+
args = append(args, []string{"daemon-reload"})
6885
}
86+
args = slices.Concat(
87+
args,
88+
[][]string{
89+
{"enable", "lima-guestagent.service"},
90+
{"try-restart", "lima-guestagent.service"}, // try-restart: restart if running, otherwise do nothing
91+
{"start", "lima-guestagent.service"}, // start: start if not running, otherwise do nothing
92+
},
93+
)
6994
for _, args := range args {
7095
cmd := exec.CommandContext(ctx, "systemctl", append([]string{"--system"}, args...)...)
7196
cmd.Stdout = os.Stdout

cmd/lima-guestagent/lima-guestagent.TEMPLATE.service

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Description=lima-guestagent
33

44
[Service]
5-
ExecStart={{.Binary}} daemon {{.Args}}
5+
ExecStart={{.Binary}} daemon {{.Args}} --runtime-dir="%t/%N"
66
Type=simple
77
Restart=on-failure
88
OOMPolicy=continue

cmd/lima-guestagent/main_linux.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ import (
1111

1212
"github.com/lima-vm/lima/v2/cmd/yq"
1313
"github.com/lima-vm/lima/v2/pkg/debugutil"
14+
"github.com/lima-vm/lima/v2/pkg/osutil"
1415
"github.com/lima-vm/lima/v2/pkg/version"
1516
)
1617

1718
func main() {
1819
yq.MaybeRunYQ()
1920
if err := newApp().Execute(); err != nil {
21+
osutil.HandleExitError(err)
2022
logrus.Fatal(err)
2123
}
2224
}
Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/bin/sh
1+
#!/bin/bash
22

33
# SPDX-FileCopyrightText: Copyright The Lima Authors
44
# SPDX-License-Identifier: Apache-2.0
@@ -19,46 +19,66 @@ fi
1919

2020
# Install or update the guestagent binary
2121
mkdir -p "${LIMA_CIDATA_GUEST_INSTALL_PREFIX}"/bin
22-
install -m 755 "${LIMA_CIDATA_MNT}"/lima-guestagent "${LIMA_CIDATA_GUEST_INSTALL_PREFIX}"/bin/lima-guestagent
22+
guestagent_updated=false
23+
if diff -q "${LIMA_CIDATA_MNT}"/lima-guestagent "${LIMA_CIDATA_GUEST_INSTALL_PREFIX}"/bin/lima-guestagent 2>/dev/null; then
24+
echo "${LIMA_CIDATA_GUEST_INSTALL_PREFIX}/bin/lima-guestagent is up-to-date"
25+
else
26+
install -m 755 "${LIMA_CIDATA_MNT}"/lima-guestagent "${LIMA_CIDATA_GUEST_INSTALL_PREFIX}"/bin/lima-guestagent
27+
guestagent_updated=true
28+
fi
2329

2430
# Launch the guestagent service
2531
if [ -f /sbin/openrc-run ]; then
26-
# Convert .env to conf.d by wrapping values in double quotes.
27-
# Split the variable and value at the first "=" to handle cases where the value contains additional "=" characters.
28-
sed -E 's/^([^=]+)=(.*)/\1="\2"/' "${LIMA_CIDATA_MNT}/lima.env" >"/etc/conf.d/lima-guestagent"
29-
# Install the openrc lima-guestagent service script
30-
cat >/etc/init.d/lima-guestagent <<'EOF'
31-
#!/sbin/openrc-run
32-
supervisor=supervise-daemon
32+
print_config() {
33+
# Convert .env to conf.d by wrapping values in double quotes.
34+
# Split the variable and value at the first "=" to handle cases where the value contains additional "=" characters.
35+
sed -E 's/^([^=]+)=(.*)/\1="\2"/' "${LIMA_CIDATA_MNT}/lima.env"
36+
}
37+
print_script() {
38+
# the openrc lima-guestagent service script
39+
cat <<-'EOF'
40+
#!/sbin/openrc-run
41+
supervisor=supervise-daemon
3342
34-
log_file="${log_file:-/var/log/${RC_SVCNAME}.log}"
35-
err_file="${err_file:-${log_file}}"
36-
log_mode="${log_mode:-0644}"
37-
log_owner="${log_owner:-root:root}"
43+
log_file="${log_file:-/var/log/${RC_SVCNAME}.log}"
44+
err_file="${err_file:-${log_file}}"
45+
log_mode="${log_mode:-0644}"
46+
log_owner="${log_owner:-root:root}"
3847
39-
supervise_daemon_args="${supervise_daemon_opts:---stderr \"${err_file}\" --stdout \"${log_file}\"}"
48+
supervise_daemon_args="${supervise_daemon_opts:---stderr \"${err_file}\" --stdout \"${log_file}\"}"
4049
41-
name="lima-guestagent"
42-
description="Forward ports to the lima-hostagent"
50+
name="lima-guestagent"
51+
description="Forward ports to the lima-hostagent"
52+
53+
command=${LIMA_CIDATA_GUEST_INSTALL_PREFIX}/bin/lima-guestagent
54+
command_args="daemon --debug=${LIMA_CIDATA_DEBUG} --vsock-port \"${LIMA_CIDATA_VSOCK_PORT}\" --virtio-port \"${LIMA_CIDATA_VIRTIO_PORT}\""
55+
command_background=true
56+
pidfile="/run/lima-guestagent.pid"
57+
EOF
58+
}
59+
if [ "${guestagent_updated}" = "false" ] &&
60+
diff -q <(print_config) /etc/conf.d/lima-guestagent 2>/dev/null &&
61+
diff -q <(print_script) /etc/init.d/lima-guestagent 2>/dev/null; then
62+
echo "lima-guestagent service already up-to-date"
63+
exit 0
64+
fi
4365

44-
command=${LIMA_CIDATA_GUEST_INSTALL_PREFIX}/bin/lima-guestagent
45-
command_args="daemon --debug=${LIMA_CIDATA_DEBUG} --vsock-port \"${LIMA_CIDATA_VSOCK_PORT}\" --virtio-port \"${LIMA_CIDATA_VIRTIO_PORT}\""
46-
command_background=true
47-
pidfile="/run/lima-guestagent.pid"
48-
EOF
66+
print_config >/etc/conf.d/lima-guestagent
67+
print_script >/etc/init.d/lima-guestagent
4968
chmod 755 /etc/init.d/lima-guestagent
5069

5170
rc-update add lima-guestagent default
52-
rc-service lima-guestagent start
71+
rc-service --ifstarted lima-guestagent restart # restart if running, otherwise do nothing
72+
rc-service --ifstopped lima-guestagent start # start if not running, otherwise do nothing
5373
else
5474
# Remove legacy systemd service
5575
rm -f "${LIMA_CIDATA_HOME}/.config/systemd/user/lima-guestagent.service"
5676

5777
if [ "${LIMA_CIDATA_VSOCK_PORT}" != "0" ]; then
58-
sudo "${LIMA_CIDATA_GUEST_INSTALL_PREFIX}"/bin/lima-guestagent install-systemd --debug="${LIMA_CIDATA_DEBUG}" --vsock-port "${LIMA_CIDATA_VSOCK_PORT}"
78+
sudo "${LIMA_CIDATA_GUEST_INSTALL_PREFIX}"/bin/lima-guestagent install-systemd --debug="${LIMA_CIDATA_DEBUG}" --guestagent-updated="${guestagent_updated}" --vsock-port "${LIMA_CIDATA_VSOCK_PORT}"
5979
elif [ "${LIMA_CIDATA_VIRTIO_PORT}" != "" ]; then
60-
sudo "${LIMA_CIDATA_GUEST_INSTALL_PREFIX}"/bin/lima-guestagent install-systemd --debug="${LIMA_CIDATA_DEBUG}" --virtio-port "${LIMA_CIDATA_VIRTIO_PORT}"
80+
sudo "${LIMA_CIDATA_GUEST_INSTALL_PREFIX}"/bin/lima-guestagent install-systemd --debug="${LIMA_CIDATA_DEBUG}" --guestagent-updated="${guestagent_updated}" --virtio-port "${LIMA_CIDATA_VIRTIO_PORT}"
6181
else
62-
sudo "${LIMA_CIDATA_GUEST_INSTALL_PREFIX}"/bin/lima-guestagent install-systemd --debug="${LIMA_CIDATA_DEBUG}"
82+
sudo "${LIMA_CIDATA_GUEST_INSTALL_PREFIX}"/bin/lima-guestagent install-systemd --debug="${LIMA_CIDATA_DEBUG}" --guestagent-updated="${guestagent_updated}"
6383
fi
6484
fi

pkg/guestagent/api/server/server.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"context"
88
"net"
99

10+
"github.com/sirupsen/logrus"
1011
"google.golang.org/grpc"
1112
"google.golang.org/grpc/keepalive"
1213
"google.golang.org/protobuf/types/known/emptypb"
@@ -16,7 +17,7 @@ import (
1617
"github.com/lima-vm/lima/v2/pkg/portfwdserver"
1718
)
1819

19-
func StartServer(lis net.Listener, guest *GuestServer) error {
20+
func StartServer(ctx context.Context, lis net.Listener, guest *GuestServer) error {
2021
server := grpc.NewServer(
2122
grpc.InitialWindowSize(512<<20),
2223
grpc.InitialConnWindowSize(512<<20),
@@ -26,7 +27,19 @@ func StartServer(lis net.Listener, guest *GuestServer) error {
2627
grpc.KeepaliveParams(keepalive.ServerParameters{Time: 0, Timeout: 0, MaxConnectionIdle: 0}),
2728
)
2829
api.RegisterGuestServiceServer(server, guest)
29-
return server.Serve(lis)
30+
go func() {
31+
<-ctx.Done()
32+
logrus.Debug("Stopping the gRPC server")
33+
server.GracefulStop()
34+
logrus.Debug("Closing the listener used by the gRPC server")
35+
lis.Close()
36+
}()
37+
err := server.Serve(lis)
38+
// grpc.Server.Serve() expects to return a non-nil error caused by lis.Accept()
39+
if opErr, ok := err.(*net.OpError); ok && opErr.Err.Error() == "use of closed network connection" {
40+
return nil
41+
}
42+
return err
3043
}
3144

3245
type GuestServer struct {
@@ -41,6 +54,7 @@ func (s *GuestServer) GetInfo(ctx context.Context, _ *emptypb.Empty) (*api.Info,
4154

4255
func (s *GuestServer) GetEvents(_ *emptypb.Empty, stream api.GuestService_GetEventsServer) error {
4356
responses := make(chan *api.Event)
57+
// expects Events() to close the channel when stream.Context() is done or ticker stops
4458
go s.Agent.Events(stream.Context(), responses)
4559
for response := range responses {
4660
err := stream.Send(response)

0 commit comments

Comments
 (0)