Skip to content
This repository has been archived by the owner on Dec 7, 2023. It is now read-only.

preflight before start operation #360

Merged
merged 10 commits into from
Sep 9, 2019
13 changes: 11 additions & 2 deletions cmd/ignite/run/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,15 @@ import (
"fmt"

"github.com/weaveworks/ignite/pkg/operations"
"github.com/weaveworks/ignite/pkg/preflight/checkers"
"github.com/weaveworks/ignite/pkg/util"
"k8s.io/apimachinery/pkg/util/sets"
)

type StartFlags struct {
Interactive bool
Debug bool
Interactive bool
Debug bool
IgnoredPreflightErrors []string
}

type startOptions struct {
Expand All @@ -34,6 +38,11 @@ func Start(so *startOptions) error {
return fmt.Errorf("VM %q is already running", so.vm.GetUID())
}

ignoredPreflightErrors := sets.NewString(util.ToLower(so.StartFlags.IgnoredPreflightErrors)...)
if err := checkers.StartCmdChecks(so.vm, ignoredPreflightErrors); err != nil {
return err
}

if err := operations.StartVM(so.vm, so.Debug); err != nil {
return err
}
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ require (
github.com/sirupsen/logrus v1.4.2
github.com/spf13/cobra v0.0.5
github.com/spf13/pflag v1.0.3
github.com/stretchr/testify v1.3.0
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 // indirect
github.com/vishvananda/netlink v1.0.0
github.com/vishvananda/netns v0.0.0-20190625233234-7109fa855b0f // indirect
Expand Down
15 changes: 15 additions & 0 deletions pkg/constants/dependencies.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package constants

var Dependencies = [...]string{
"mount",
"unmount",
"tar",
"mkfs.ext4",
"e2fsck",
"resize2fs",
"strings",
"docker",
"dmsetup",
"ssh",
"git",
}
137 changes: 137 additions & 0 deletions pkg/preflight/checkers/checks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
package checkers

import (
"bytes"
"fmt"
"net"
"os"
"os/exec"
"strings"

api "github.com/weaveworks/ignite/pkg/apis/ignite"
"github.com/weaveworks/ignite/pkg/constants"
"github.com/weaveworks/ignite/pkg/preflight"
"github.com/weaveworks/ignite/pkg/providers"
"k8s.io/apimachinery/pkg/util/sets"
)

const (
oldPathString = "/"
newPathString = "-"
noReplaceLimit = -1
)

type PortOpenChecker struct {
port uint64
}

func (poc PortOpenChecker) Check() error {
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", poc.port))
if err != nil {
return fmt.Errorf("Port %d is in use", poc.port)
}
if err := listener.Close(); err != nil {
return fmt.Errorf("Port %d is in used, failed to close it", poc.port)
}
return nil
}

func (poc PortOpenChecker) Name() string {
return fmt.Sprintf("Port-%d", poc.port)
}

func (poc PortOpenChecker) Type() string {
return "Port"
}

type ExistingFileChecker struct {
filePath string
}

func NewExistingFileChecker(filePath string) ExistingFileChecker {
return ExistingFileChecker{
filePath: filePath,
}
}

func (efc ExistingFileChecker) Check() error {
if _, err := os.Stat(efc.filePath); os.IsNotExist(err) {
return fmt.Errorf("File %s, does not exist", efc.filePath)
}
return nil
}

func (efc ExistingFileChecker) Name() string {
return fmt.Sprintf("ExistingFile-%s", strings.Replace(efc.filePath, oldPathString, newPathString, noReplaceLimit))
}

func (efc ExistingFileChecker) Type() string {
return "ExistingFile"
}

type BinInPathChecker struct {
bin string
}

func (bipc BinInPathChecker) Check() error {
_, err := exec.LookPath(bipc.bin)
if err != nil {
return fmt.Errorf("Bin %s is not in your PATH", bipc.bin)
}
return nil
}

func (bipc BinInPathChecker) Name() string {
return ""
}

func (bipc BinInPathChecker) Type() string {
return ""
}

type AvailablePathChecker struct {
path string
}

func StartCmdChecks(vm *api.VM, ignoredPreflightErrors sets.String) error {
// the number "4" is chosen for the static preflight checkers see below
nCheckers := 4 + len(vm.Spec.Network.Ports) + len(constants.Dependencies)
checks := make([]preflight.Checker, 0, nCheckers)
checks = append(checks, ExistingFileChecker{filePath: "/dev/mapper/control"})
checks = append(checks, ExistingFileChecker{filePath: "/dev/net/tun"})
checks = append(checks, ExistingFileChecker{filePath: "/dev/kvm"})
checks = append(checks, providers.Runtime.PreflightChecker())
Copy link
Contributor

Choose a reason for hiding this comment

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

This could be an inline constructor instead of 4 allocations

Copy link
Member

Choose a reason for hiding this comment

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

@najeal could you please address this per @twelho comment.

for _, port := range vm.Spec.Network.Ports {
checks = append(checks, PortOpenChecker{port: port.HostPort})
}
for _, dependency := range constants.Dependencies {
checks = append(checks, BinInPathChecker{bin: dependency})
}
return runChecks(checks, ignoredPreflightErrors)
}

func runChecks(checks []preflight.Checker, ignoredPreflightErrors sets.String) error {
var errBuffer bytes.Buffer

for _, check := range checks {
name := check.Name()
checkType := check.Type()

err := check.Check()
if isIgnoredPreflightError(ignoredPreflightErrors, checkType) {
err = nil
}

if err != nil {
errBuffer.WriteString(fmt.Sprintf("[ERROR %s]: %v\n", name, err))
}
}
if errBuffer.Len() > 0 {
return fmt.Errorf(errBuffer.String())
}
return nil
}

func isIgnoredPreflightError(ignoredPreflightError sets.String, checkType string) bool {
return ignoredPreflightError.Has("all") || ignoredPreflightError.Has(strings.ToLower(checkType))
}
112 changes: 112 additions & 0 deletions pkg/preflight/checkers/checks_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package checkers

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
"k8s.io/apimachinery/pkg/util/sets"
)

type fakeChecker struct {
info string
}

func (fc fakeChecker) Check() error {
if fc.info == "error" {
return fmt.Errorf("error")
}
return nil
}

func (fc fakeChecker) Name() string {
return "FakeChecker"
}

func TestRunChecks(t *testing.T) {
utests := []struct {
checkers []Checker
ignoredErrors []string
expectedError bool
}{
{
checkers: []Checker{fakeChecker{info: ""}},
ignoredErrors: []string{},
expectedError: false,
},
{
checkers: []Checker{fakeChecker{info: ""}, fakeChecker{info: "error"}},
ignoredErrors: []string{},
expectedError: true,
},
{
checkers: []Checker{fakeChecker{info: ""}, fakeChecker{info: "error"}},
ignoredErrors: []string{"fakechecker"},
expectedError: false,
},
}
for _, utest := range utests {
ignoredErrors := sets.NewString(utest.ignoredErrors...)
err := runChecks(utest.checkers, ignoredErrors)
assert.Equal(t, utest.expectedError, (err != nil))
}
}

func TestIsIgnoredPreflightError(t *testing.T) {
const (
all = "all"
ignoredError = "my-ignored-error"
notIgnoredError = "not-ignored-error"
)
utests := []struct {
name string
ignoredErrors []string
searchedError string
expectedResponse bool
}{
{
name: "IgnoreAll",
ignoredErrors: []string{all},
searchedError: notIgnoredError,
expectedResponse: true,
},
{
name: "IgnoreSpecificError1",
ignoredErrors: []string{ignoredError},
searchedError: ignoredError,
expectedResponse: true,
},
{
name: "IgnoreSpecificError2",
ignoredErrors: []string{ignoredError},
searchedError: notIgnoredError,
expectedResponse: false,
},
{
name: "IgnoreAllAndSpecificError1",
ignoredErrors: []string{all, ignoredError},
searchedError: ignoredError,
expectedResponse: true,
},
{
name: "IgnoreAllAndSpecificError2",
ignoredErrors: []string{all, ignoredError},
searchedError: notIgnoredError,
expectedResponse: true,
},
{
name: "NoIgnore",
ignoredErrors: []string{},
searchedError: notIgnoredError,
expectedResponse: false,
},
}

for _, utest := range utests {
t.Run(utest.name, func(t *testing.T) {
ignoredErrors := sets.NewString(utest.ignoredErrors...)
isIgnored := isIgnoredPreflightError(ignoredErrors, utest.searchedError)
assert.Equal(t, utest.expectedResponse, isIgnored)
})
}
}
7 changes: 7 additions & 0 deletions pkg/preflight/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package preflight

type Checker interface {
Check() error
Name() string
Type() string
}
6 changes: 6 additions & 0 deletions pkg/runtime/containerd/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import (
"github.com/opencontainers/runtime-spec/specs-go"
log "github.com/sirupsen/logrus"
meta "github.com/weaveworks/ignite/pkg/apis/meta/v1alpha1"
"github.com/weaveworks/ignite/pkg/preflight"
"github.com/weaveworks/ignite/pkg/preflight/checkers"
"github.com/weaveworks/ignite/pkg/runtime"
"github.com/weaveworks/ignite/pkg/util"
"golang.org/x/sys/unix"
Expand Down Expand Up @@ -632,6 +634,10 @@ func (cc *ctdClient) RawClient() interface{} {
return cc.client
}

func (cc *ctdClient) PreflightChecker() preflight.Checker {
return checkers.NewExistingFileChecker(ctdSocket)
}

// imageUsage returns the size/inode usage of the given image by
// summing up the resource usage of each of its snapshot layers
func (cc *ctdClient) imageUsage(image containerd.Image) (usage snapshots.Usage, err error) {
Expand Down
10 changes: 10 additions & 0 deletions pkg/runtime/docker/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,16 @@ import (
cont "github.com/docker/docker/api/types/container"
"github.com/docker/docker/client"
meta "github.com/weaveworks/ignite/pkg/apis/meta/v1alpha1"
"github.com/weaveworks/ignite/pkg/preflight"
"github.com/weaveworks/ignite/pkg/preflight/checkers"
"github.com/weaveworks/ignite/pkg/runtime"
"github.com/weaveworks/ignite/pkg/util"
)

const (
dcSocket = "/var/run/docker.sock"
)

// dockerClient is a runtime.Interface
// implementation serving the Docker client
type dockerClient struct {
Expand Down Expand Up @@ -219,6 +225,10 @@ func (dc *dockerClient) RawClient() interface{} {
return dc.client
}

func (dc *dockerClient) PreflightChecker() preflight.Checker {
return checkers.NewExistingFileChecker(dcSocket)
}

func (dc *dockerClient) waitForContainer(container string, condition cont.WaitCondition, readyC *chan struct{}) error {
resultC, errC := dc.client.ContainerWait(context.Background(), container, condition)

Expand Down
3 changes: 3 additions & 0 deletions pkg/runtime/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"time"

meta "github.com/weaveworks/ignite/pkg/apis/meta/v1alpha1"
"github.com/weaveworks/ignite/pkg/preflight"
)

type ImageInspectResult struct {
Expand Down Expand Up @@ -63,6 +64,8 @@ type Interface interface {

Name() Name
RawClient() interface{}

PreflightChecker() preflight.Checker
}

// Name defines a name for a runtime
Expand Down
13 changes: 13 additions & 0 deletions pkg/util/norm_string.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package util

import (
"strings"
)

func ToLower(a []string) []string {
b := make([]string, 0, len(a))
for _, c := range a {
b = append(b, strings.ToLower(c))
}
return b
}
Loading