Skip to content

Commit b5678ad

Browse files
committed
Guest shutdown support in endpointVM
This adds neat shutdown support in the endpointVM: * cleans up Views, Managers and sessions in portlayer * cleans up session in personality * cleans up and purges all active sessions in vicadmin Updates the way tether waits for sessions to exit. Updates vic-machine to use guest shutdown for endpointVM
1 parent dc99ffe commit b5678ad

File tree

32 files changed

+452
-87
lines changed

32 files changed

+452
-87
lines changed

cmd/docker/main.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package main
1616

1717
import (
18+
"context"
1819
"crypto/tls"
1920
"crypto/x509"
2021
"flag"
@@ -122,12 +123,16 @@ func main() {
122123
serveAPIWait := make(chan error)
123124
go api.Wait(serveAPIWait)
124125

126+
// signal.Trap explicitly calls os.Exit so any exit logic has to be rolled in here
125127
signal.Trap(func() {
128+
log.Info("Closing down docker personality")
129+
126130
api.Close()
131+
plEventMonitor.Stop()
132+
vicbackends.Finalize(context.Background())
127133
})
128134

129135
<-serveAPIWait
130-
plEventMonitor.Stop()
131136
}
132137

133138
func handleFlags() bool {

cmd/port-layer-server/main.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,9 @@ func main() {
149149
go func() {
150150
<-sig
151151

152+
// if vspc server fails to shut down in a timely manner then the clean endpointVM shutdown will fail
153+
// this failure to shut down cleanly can be observed in the "/var/log/vic/init.log" or
154+
// "[datastore] endpointVM/tether.debug" log files as vic-init will report that it's waiting for the portlayer to exit.
152155
vspc.Stop()
153156
dnsserver.Stop()
154157
restapi.StopAPIServers()

cmd/tether/main_linux.go

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,16 +121,30 @@ func halt() {
121121
syscall.Reboot(syscall.LINUX_REBOOT_CMD_POWER_OFF)
122122
}
123123

124+
// This code is mirrored in cmd/vic-init/main_linux.go and should be de-duped
124125
func startSignalHandler() {
125126
sigs := make(chan os.Signal, 1)
126-
signal.Notify(sigs, syscall.SIGHUP)
127+
// currently using a really basic set of signal behaviours, reflected from here:
128+
// https://github.com/troglobit/finit/blob/master/docs/signals.md
129+
// itself sourced from here:
130+
// https://unix.stackexchange.com/a/191875
131+
// This set was chosen because of suitably limited scope vs the complexity of systemd or similar.
132+
signal.Notify(sigs, syscall.SIGHUP, syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGPWR, syscall.SIGTERM, syscall.SIGINT)
127133

128134
go func() {
129135
for s := range sigs {
130136
switch s {
131137
case syscall.SIGHUP:
132-
log.Infof("Reloading tether configuration")
138+
log.Infof("Reloading tether configuration via signal %s", s.String())
133139
tthr.Reload()
140+
case syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGPWR:
141+
log.Infof("Stopping tether via signal %s", s.String())
142+
tthr.Stop()
143+
case syscall.SIGTERM, syscall.SIGINT:
144+
log.Infof("Stopping system in lieu of restart handling via signal %s", s.String())
145+
// TODO: update this to adjust power off handling for reboot
146+
// this should be in guest reboot rather than power cycle
147+
tthr.Stop()
134148
default:
135149
log.Infof("%s signal not defined", s.String())
136150
}

cmd/tether/main_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ func (m *mockery) Stop() error {
135135
return nil
136136
}
137137

138+
func (m *mockery) Wait(ctx context.Context) error {
139+
return nil
140+
}
141+
138142
func (m *mockery) Register(name string, config tether.Extension) {
139143
}
140144

cmd/vic-init/main_linux.go

Lines changed: 87 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,10 @@
1515
package main
1616

1717
import (
18+
"context"
19+
"errors"
1820
"os"
21+
"os/signal"
1922
"runtime/debug"
2023
"syscall"
2124
"time"
@@ -25,6 +28,7 @@ import (
2528

2629
"github.com/vmware/govmomi/toolbox"
2730
"github.com/vmware/vic/lib/tether"
31+
"github.com/vmware/vic/lib/tether/shared"
2832
viclog "github.com/vmware/vic/pkg/log"
2933
"github.com/vmware/vic/pkg/log/syslog"
3034
"github.com/vmware/vic/pkg/logmgr"
@@ -79,6 +83,8 @@ func main() {
7983
extraconfig.Decode(src, &config)
8084
debugLevel = config.Diagnostics.DebugLevel
8185

86+
startSignalHandler()
87+
8288
logcfg := viclog.NewLoggingConfig()
8389
if debugLevel > 0 {
8490
logcfg.Level = log.DebugLevel
@@ -136,33 +142,80 @@ func main() {
136142
log.Info("Clean exit from init")
137143
}
138144

145+
// exitTether signals the current process, which triggers tether.Stop and the killing of its children.
146+
// NOTE: I don't like having this here and it really needs to be moved into an interface that
147+
// can be provided to toolbox for system callbacks. While this could be part of the Operations
148+
// interface I think I'd rather have a separate one specifically for the possible toolbox interactions.
149+
func exitTether() error {
150+
defer trace.End(trace.Begin(""))
151+
152+
p, err := os.FindProcess(os.Getpid())
153+
if err != nil {
154+
return err
155+
}
156+
157+
if err = p.Signal(syscall.SIGUSR2); err != nil {
158+
return err
159+
}
160+
161+
return err
162+
}
163+
139164
// exit cleanly shuts down the system
140-
func halt() {
165+
func halt() error {
141166
log.Infof("Powering off the system")
142-
if debugLevel > 0 {
167+
168+
err := exitTether()
169+
if err != nil {
170+
log.Warn(err)
171+
}
172+
173+
if debugLevel > 2 {
143174
log.Info("Squashing power off for debug init")
144-
return
175+
return errors.New("debug config suppresses shutdown")
145176
}
146177

178+
timeout, cancel := context.WithTimeout(context.Background(), shared.GuestShutdownTimeout)
179+
err = tthr.Wait(timeout)
180+
cancel()
181+
147182
syscall.Sync()
148183
syscall.Reboot(syscall.LINUX_REBOOT_CMD_POWER_OFF)
184+
185+
return err
149186
}
150187

151-
func reboot() {
188+
func reboot() error {
152189
log.Infof("Rebooting the system")
153-
if debugLevel > 0 {
190+
191+
err := exitTether()
192+
if err != nil {
193+
log.Warn(err)
194+
}
195+
196+
if debugLevel > 2 {
154197
log.Info("Squashing reboot for debug init")
155-
return
198+
return errors.New("debug config suppresses reboot")
156199
}
157200

201+
timeout, cancel := context.WithTimeout(context.Background(), shared.GuestRebootTimeout)
202+
err = tthr.Wait(timeout)
203+
cancel()
204+
158205
syscall.Sync()
159206
syscall.Reboot(syscall.LINUX_REBOOT_CMD_RESTART)
207+
208+
return err
160209
}
161210

162211
func configureToolbox(t *tether.Toolbox) *tether.Toolbox {
163212
cmd := t.Service.Command
164213
cmd.ProcessStartCommand = startCommand
165214

215+
t.Power.Halt.Handler = halt
216+
t.Power.Reboot.Handler = reboot
217+
t.Power.Suspend.Handler = exitTether
218+
166219
return t
167220
}
168221

@@ -201,3 +254,31 @@ func defaultIP() string {
201254

202255
return toolbox.DefaultIP()
203256
}
257+
258+
// This code is mirrored in cmd/tether/main_linux.go and should be de-duped
259+
func startSignalHandler() {
260+
sigs := make(chan os.Signal, 1)
261+
signal.Notify(sigs, syscall.SIGHUP, syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGPWR, syscall.SIGTERM, syscall.SIGINT)
262+
263+
go func() {
264+
for s := range sigs {
265+
switch s {
266+
case syscall.SIGHUP:
267+
log.Infof("Reloading tether configuration")
268+
tthr.Reload()
269+
case syscall.SIGUSR1, syscall.SIGUSR2, syscall.SIGPWR:
270+
log.Infof("Stopping tether via signal %s", s.String())
271+
tthr.Stop()
272+
return
273+
case syscall.SIGTERM, syscall.SIGINT:
274+
log.Infof("Stopping system in lieu of restart handling via signal %s", s.String())
275+
// TODO: update this to adjust power off handling for reboot
276+
// this should be in guest reboot rather than power cycle
277+
tthr.Stop()
278+
return
279+
default:
280+
log.Infof("%s signal not defined", s.String())
281+
}
282+
}
283+
}()
284+
}

cmd/vicadmin/server.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -444,6 +444,8 @@ func (s *server) serve() error {
444444
func (s *server) stop() error {
445445
defer trace.End(trace.Begin(""))
446446

447+
s.uss.Destroy()
448+
447449
if s.l != nil {
448450
err := s.l.Close()
449451
s.l = nil

cmd/vicadmin/usersession.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,13 @@ func (u *UserSessionStore) Add(id string, config *session.Config, vs *session.Se
6969
func (u *UserSessionStore) Delete(id string) {
7070
u.mutex.Lock()
7171
defer u.mutex.Unlock()
72+
73+
us := u.sessions[id]
74+
if us != nil && us.vsphere != nil {
75+
log.Infof("Logging out vSphere session for %s", id)
76+
us.vsphere.Logout(context.Background())
77+
}
78+
7279
delete(u.sessions, id)
7380
}
7481

@@ -112,6 +119,13 @@ func (u *UserSessionStore) reaper() {
112119
}
113120
}
114121

122+
// Destroy will logout and delete all sessions in the store, irrespective of expiration
123+
func (u *UserSessionStore) Destroy() {
124+
for id := range u.sessions {
125+
u.Delete(id)
126+
}
127+
}
128+
115129
// NewUserSessionStore creates & initializes a UserSessionStore and starts a session reaper in the background
116130
func NewUserSessionStore() *UserSessionStore {
117131
u := &UserSessionStore{

cmd/vicadmin/vicadm.go

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -613,15 +613,9 @@ func main() {
613613
}
614614

615615
log.Infof("listening on %s", s.addr)
616-
signals := []syscall.Signal{
617-
syscall.SIGTERM,
618-
syscall.SIGINT,
619-
}
620616

621617
sigchan := make(chan os.Signal, 1)
622-
for _, signum := range signals {
623-
signal.Notify(sigchan, signum)
624-
}
618+
signal.Notify(sigchan, syscall.SIGTERM, syscall.SIGINT)
625619

626620
go func() {
627621
signal := <-sigchan

infra/scripts/replace-running-components.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ function replace-component() {
152152
if [[ $1 == "vic-init" ]]; then
153153
on-vch systemctl restart vic-init
154154
else
155-
on-vch kill -9 $pid
155+
on-vch kill -TERM $pid
156156
fi
157157
}
158158

lib/apiservers/engine/backends/backends.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,18 @@ func Init(portLayerAddr, product string, port uint, config *config.VirtualContai
154154
return nil
155155
}
156156

157+
// Finalize performs cleanup before a graceful exit of the API server.
158+
// In this case that means logging out the dynamic config session if any.
159+
func Finalize(ctx context.Context) error {
160+
log.Info("Shutting down docker API server backend")
161+
162+
if vchConfig != nil && vchConfig.sess != nil {
163+
vchConfig.sess.Logout(ctx)
164+
}
165+
166+
return nil
167+
}
168+
157169
func hydrateCaches(op trace.Operation) error {
158170
const waiters = 3
159171

0 commit comments

Comments
 (0)