Skip to content

Commit

Permalink
Implement virt-tail in golang
Browse files Browse the repository at this point in the history
Implement virt-tail in golang adding
custom logic to quickly terminate it
when the serial socket get closed
or a termination signalling file got
created by virt-launcher-monitor.

Signed-off-by: Simone Tiraboschi <[email protected]>
  • Loading branch information
tiraboschi committed Oct 1, 2023
1 parent ea26de6 commit ab47865
Show file tree
Hide file tree
Showing 49 changed files with 2,194 additions and 51 deletions.
1 change: 1 addition & 0 deletions cmd/virt-launcher-monitor/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ go_library(
importpath = "kubevirt.io/kubevirt/cmd/virt-launcher-monitor",
visibility = ["//visibility:private"],
deps = [
"//pkg/util:go_default_library",
"//staging/src/kubevirt.io/client-go/log:go_default_library",
"//vendor/github.com/spf13/pflag:go_default_library",
"//vendor/golang.org/x/sys/unix:go_default_library",
Expand Down
31 changes: 29 additions & 2 deletions cmd/virt-launcher-monitor/virt-launcher-monitor.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ import (

"golang.org/x/sys/unix"
"kubevirt.io/client-go/log"

"kubevirt.io/kubevirt/pkg/util"
)

const (
Expand All @@ -56,10 +58,34 @@ func cleanupContainerDiskDirectory(ephemeralDiskDir string) {
}
}

func cleanupSerialConsoleSock(uid string) {
// Create a file to quickly signal the shutdown to the guest-console-log container in the case the sigterm signal got
// missed and some client process is still connected to the serial console socket
const serialPort = 0
if len(uid) > 0 {
logSigPath := fmt.Sprintf("%s/%s/virt-serial%d-log-sigTerm", util.VirtPrivateDir, uid, serialPort)

if _, err := os.Stat(logSigPath); os.IsNotExist(err) {
file, err := os.Create(logSigPath)
if err != nil {
log.Log.Reason(err).Errorf("could not create up serial console term file: %s", logSigPath)
return
}
if err = file.Close(); err != nil {
log.Log.Reason(err).Errorf("could not create up serial console term file: %s", logSigPath)
return
}
log.Log.V(3).Infof("serial console term file created: %s", logSigPath)
}
}

}

func main() {

containerDiskDir := pflag.String("container-disk-dir", "/var/run/kubevirt/container-disks", "Base directory for container disk data")
keepAfterFailure := pflag.Bool("keep-after-failure", false, "virt-launcher will be kept alive after failure for debugging if set to true")
uid := pflag.String("uid", "", "UID of the VirtualMachineInstance")

// set new default verbosity, was set to 0 by glog
goflag.Set("v", "2")
Expand All @@ -79,7 +105,7 @@ func main() {
}
}

exitCode, err := RunAndMonitor(*containerDiskDir)
exitCode, err := RunAndMonitor(*containerDiskDir, *uid)
if *keepAfterFailure && (exitCode != 0 || err != nil) {
log.Log.Infof("keeping virt-launcher container alive since --keep-after-failure is set to true")
<-make(chan struct{})
Expand All @@ -95,7 +121,8 @@ func main() {

// RunAndMonitor run virt-launcher process and monitor it to give qemu an extra grace period to properly terminate
// in case of crashes
func RunAndMonitor(containerDiskDir string) (int, error) {
func RunAndMonitor(containerDiskDir, uid string) (int, error) {
defer cleanupSerialConsoleSock(uid)
defer cleanupContainerDiskDirectory(containerDiskDir)
defer terminateIstioProxy()
args := removeArg(os.Args[1:], "--keep-after-failure")
Expand Down
1 change: 1 addition & 0 deletions cmd/virt-launcher/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ pkg_tar(
"//cmd/virt-freezer",
"//cmd/virt-launcher-monitor",
"//cmd/virt-probe",
"//cmd/virt-tail",
],
package_dir = "/usr/bin",
)
Expand Down
5 changes: 3 additions & 2 deletions cmd/virt-launcher/virt-launcher.go
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,9 @@ func main() {
}
}

// Initialize local and shared directories
initializeDirs(*ephemeralDiskDir, *containerDiskDir, *hotplugDiskDir, *uid)

if *simulateCrash {
panic(fmt.Errorf("Simulated virt-launcher crash"))
}
Expand All @@ -387,8 +390,6 @@ func main() {

vmi := v1.NewVMIReferenceWithUUID(*namespace, *name, types.UID(*uid))

// Initialize local and shared directories
initializeDirs(*ephemeralDiskDir, *containerDiskDir, *hotplugDiskDir, *uid)
ephemeralDiskCreator := ephemeraldisk.NewEphemeralDiskCreator(filepath.Join(*ephemeralDiskDir, "disk-data"))
if err := ephemeralDiskCreator.Init(); err != nil {
panic(err)
Expand Down
21 changes: 21 additions & 0 deletions cmd/virt-tail/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")

go_library(
name = "go_default_library",
srcs = ["main.go"],
importpath = "kubevirt.io/kubevirt/cmd/virt-tail",
visibility = ["//visibility:private"],
deps = [
"//staging/src/kubevirt.io/client-go/log:go_default_library",
"//vendor/github.com/fsnotify/fsnotify:go_default_library",
"//vendor/github.com/nxadm/tail:go_default_library",
"//vendor/github.com/spf13/pflag:go_default_library",
"//vendor/golang.org/x/sync/errgroup:go_default_library",
],
)

go_binary(
name = "virt-tail",
embed = [":go_default_library"],
visibility = ["//visibility:public"],
)
230 changes: 230 additions & 0 deletions cmd/virt-tail/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
/*
* This file is part of the KubeVirt project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Copyright 2023 Red Hat, Inc.
*
*/

package main

import (
"context"
"errors"
goflag "flag"
"fmt"
"os"
"os/signal"
"path/filepath"
"strconv"
"strings"
"time"

"github.com/fsnotify/fsnotify"
"github.com/nxadm/tail"
"github.com/spf13/pflag"
"golang.org/x/sync/errgroup"

"kubevirt.io/client-go/log"
)

type TermFileError struct{}

func (m *TermFileError) Error() string {
return "termFile got detected"
}

type VirtTail struct {
ctx context.Context
logFile string
g *errgroup.Group
}

func (v *VirtTail) checkFile(socketFile string) bool {
_, err := os.Stat(socketFile)
return !os.IsNotExist(err)
}

func (v *VirtTail) tailLogs() error {
t, err := tail.TailFile(v.logFile, tail.Config{
Follow: true,
CompleteLines: true,
MustExist: false,
ReOpen: true,
Logger: tail.DiscardingLogger,
})
if err != nil {
return err
}
defer func() {
serr := t.Stop()
if serr != nil {
log.Log.V(3).Infof("tail error: %v", serr)
}
t.Cleanup()
}()

for {
select {
case line, ok := <-t.Lines:
if !ok {
log.Log.V(4).Info("tail error: line not ok")
} else if line != nil {
if line.Err != nil {
log.Log.V(3).Infof("tail error: %v", line.Err)
} else {
fmt.Println(line.Text)
}
}
case <-v.ctx.Done():
return v.ctx.Err()
}
}
}

func (v *VirtTail) watchFS() error {
socketFile := strings.TrimSuffix(v.logFile, "-log")
termFile := v.logFile + "-sigTerm"
socketExists := v.checkFile(socketFile)

watcher, err := fsnotify.NewWatcher()
if err != nil {
log.Log.V(3).Infof("watcher error: %v", err)
return err
}
defer watcher.Close()

// Add a path.
dirPath := filepath.Dir(v.logFile)
found := false
i := 0
for i < 30 && !found {
i = i + 1
if _, derr := os.Stat(dirPath); derr == nil {
found = true
if err = watcher.Add(dirPath); err != nil {
log.Log.V(3).Infof("watcher error: %v - %s", err, dirPath)
return err
}
} else {
time.Sleep(100 * time.Millisecond)
}
}
if !found {
rerr := errors.New("expected directory is still not ready")
log.Log.V(3).Infof("watchFS error: %v", rerr)
return rerr
}

// initial timeout for serial console socket creation
const initialSocketTimeout = time.Second * 20
socketCheckCh := make(chan int)
time.AfterFunc(initialSocketTimeout, func() {
socketCheckCh <- 1
})

// Start listening for events.
for {
select {
case <-socketCheckCh:
if !socketExists {
if socketExists = v.checkFile(socketFile); !socketExists {
rerr := errors.New("socketFile is still not ready")
log.Log.V(3).Infof("watchFS error: %v", rerr)
return rerr
}
}
if v.checkFile(termFile) {
log.Log.V(3).Infof("watchFS error: termFile was already there")
return &TermFileError{}
}
case event := <-watcher.Events:
if event.Has(fsnotify.Create) {
if event.Name == termFile {
// termination file got created, we should quickly terminate
terr := &TermFileError{}
log.Log.V(3).Infof("watchFS error: %v", terr)
return terr
} else if event.Name == socketFile {
// socket file got created
socketExists = true
}
} else if event.Has(fsnotify.Remove) {
if event.Name == socketFile {
// socket file got deleted, we should quickly terminate
rerr := errors.New("socketFile got removed")
log.Log.V(3).Infof("watchFS error: %v", rerr)
return rerr
}
}
case werr := <-watcher.Errors:
log.Log.V(3).Infof("watcher error: %v", werr)
return werr
case <-v.ctx.Done():
return v.ctx.Err()
}
}
}

func main() {
// set new default verbosity, was set to 0 by glog
goflag.Set("v", "2")
pflag.CommandLine.AddGoFlag(goflag.CommandLine.Lookup("v"))
pflag.CommandLine.ParseErrorsWhitelist = pflag.ParseErrorsWhitelist{UnknownFlags: true}
logFile := pflag.String("logfile", "", "path of the logfile to be streamed")
pflag.Parse()

log.InitializeLogging("virt-tail")
setTailLogverbosity()

if logFile == nil || *logFile == "" {
log.Log.V(3).Infof("logfile flags must be provided")
os.Exit(1)
}

// Create context that listens for the interrupt signal from the container runtime.
ctx, _ := signal.NotifyContext(context.Background(), os.Interrupt)

g, gctx := errgroup.WithContext(ctx)

v := &VirtTail{
ctx: gctx,
logFile: *logFile,
g: g,
}

g.Go(v.tailLogs)
g.Go(v.watchFS)

// wait for all errgroup goroutines
if err := g.Wait(); err != nil {
// Exit cleanly on clean termination errors
if !(errors.Is(err, context.Canceled) || errors.Is(err, &TermFileError{})) {
log.Log.V(3).Infof("received error: %v", err)
os.Exit(1)
}
}
}

func setTailLogverbosity() {
// check if virt-launcher verbosity should be changed
if verbosityStr, ok := os.LookupEnv("VIRT_LAUNCHER_LOG_VERBOSITY"); ok {
if verbosity, err := strconv.Atoi(verbosityStr); err == nil {
log.Log.SetVerbosityLevel(verbosity)
log.Log.V(3).Infof("set log verbosity to %d", verbosity)
} else {
log.Log.Warningf("failed to set log verbosity. The value of logVerbosity label should be an integer, got %s instead.", verbosityStr)
}
}
}
6 changes: 5 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ require (
github.com/mitchellh/go-vnc v0.0.0-20150629162542-723ed9867aed
github.com/moby/sys/mountinfo v0.6.2
github.com/nunnatsa/ginkgolinter v0.13.3
github.com/nxadm/tail v1.4.8
github.com/onsi/ginkgo/v2 v2.9.4
github.com/onsi/gomega v1.27.6
github.com/opencontainers/runc v1.1.4
Expand All @@ -58,6 +59,7 @@ require (
github.com/wadey/gocovmerge v0.0.0-20160331181800-b5bfa59ec0ad
golang.org/x/crypto v0.11.0
golang.org/x/net v0.12.0
golang.org/x/sync v0.3.0
golang.org/x/sys v0.10.0
golang.org/x/term v0.10.0
golang.org/x/time v0.3.0
Expand Down Expand Up @@ -142,12 +144,12 @@ require (
golang.org/x/exp/typeparams v0.0.0-20230203172020-98cc5a0785f9 // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect
golang.org/x/sync v0.3.0 // indirect
golang.org/x/text v0.11.0 // indirect
google.golang.org/appengine v1.6.7 // indirect
google.golang.org/genproto v0.0.0-20220720214146-176da50484ac // indirect
google.golang.org/protobuf v1.30.0 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
mvdan.cc/editorconfig v0.1.1-0.20200121172147-e40951bde157 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
Expand All @@ -161,6 +163,8 @@ require (

replace (
github.com/golang/glog => ./staging/src/github.com/golang/glog

github.com/nxadm/tail => github.com/nxadm/tail v0.0.0-20211216163028-4472660a31a6
github.com/opencontainers/selinux => github.com/opencontainers/selinux v1.6.0
github.com/openshift/api => github.com/openshift/api v0.0.0-20191219222812-2987a591a72c
github.com/openshift/client-go => github.com/openshift/client-go v0.0.0-20210112165513-ebc401615f47
Expand Down
Loading

0 comments on commit ab47865

Please sign in to comment.