Merge branch 'master' into docker-stress-test
commit
6d2ba5b00e
|
@ -811,7 +811,6 @@ func suggestMemoryAllocation(sysLimit int, containerLimit int, nodes int) int {
|
||||||
|
|
||||||
// validateMemorySize validates the memory size matches the minimum recommended
|
// validateMemorySize validates the memory size matches the minimum recommended
|
||||||
func validateMemorySize(req int, drvName string) {
|
func validateMemorySize(req int, drvName string) {
|
||||||
|
|
||||||
sysLimit, containerLimit, err := memoryLimits(drvName)
|
sysLimit, containerLimit, err := memoryLimits(drvName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Warningf("Unable to query memory limits: %v", err)
|
glog.Warningf("Unable to query memory limits: %v", err)
|
||||||
|
@ -822,7 +821,7 @@ func validateMemorySize(req int, drvName string) {
|
||||||
out.V{"requested": req, "mininum": minUsableMem})
|
out.V{"requested": req, "mininum": minUsableMem})
|
||||||
}
|
}
|
||||||
if req < minRecommendedMem && !viper.GetBool(force) {
|
if req < minRecommendedMem && !viper.GetBool(force) {
|
||||||
out.T(out.Notice, "Requested memory allocation ({{.requested}}MB) is less than the recommended minimum {{.recommended}}MB. Kubernetes may crash unexpectedly.",
|
out.WarningT("Requested memory allocation ({{.requested}}MB) is less than the recommended minimum {{.recommended}}MB. Kubernetes may crash unexpectedly.",
|
||||||
out.V{"requested": req, "recommended": minRecommendedMem})
|
out.V{"requested": req, "recommended": minRecommendedMem})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -830,13 +829,13 @@ func validateMemorySize(req int, drvName string) {
|
||||||
// in Docker Desktop if you allocate 2 GB the docker info shows: Total Memory: 1.945GiB which becomes 1991 when we calculate the MBs
|
// in Docker Desktop if you allocate 2 GB the docker info shows: Total Memory: 1.945GiB which becomes 1991 when we calculate the MBs
|
||||||
// thats why it is not same number as other drivers which is 2 GB
|
// thats why it is not same number as other drivers which is 2 GB
|
||||||
if containerLimit < 1991 {
|
if containerLimit < 1991 {
|
||||||
out.T(out.Tip, `Increase Docker for Desktop memory to at least 2.5GB or more:
|
out.WarningT(`Increase Docker for Desktop memory to at least 2.5GB or more:
|
||||||
|
|
||||||
Docker for Desktop > Settings > Resources > Memory
|
Docker for Desktop > Settings > Resources > Memory
|
||||||
|
|
||||||
`)
|
`)
|
||||||
} else if containerLimit < 2997 && sysLimit > 8000 { // for users with more than 8 GB advice 3 GB
|
} else if containerLimit < 2997 && sysLimit > 8000 { // for users with more than 8 GB advice 3 GB
|
||||||
out.T(out.Tip, `Your system has {{.system_limit}}MB memory but Docker has only {{.container_limit}}MB. For a better performance increase to at least 3GB.
|
out.WarningT(`Your system has {{.system_limit}}MB memory but Docker has only {{.container_limit}}MB. For a better performance increase to at least 3GB.
|
||||||
|
|
||||||
Docker for Desktop > Settings > Resources > Memory
|
Docker for Desktop > Settings > Resources > Memory
|
||||||
|
|
||||||
|
|
|
@ -274,9 +274,12 @@ CONFIG_BRIDGE_EBT_LOG=m
|
||||||
CONFIG_BRIDGE_EBT_NFLOG=m
|
CONFIG_BRIDGE_EBT_NFLOG=m
|
||||||
CONFIG_BRIDGE=m
|
CONFIG_BRIDGE=m
|
||||||
CONFIG_NET_SCHED=y
|
CONFIG_NET_SCHED=y
|
||||||
|
CONFIG_NET_SCH_PRIO=y
|
||||||
|
CONFIG_NET_SCH_SFQ=y
|
||||||
CONFIG_NET_SCH_TBF=y
|
CONFIG_NET_SCH_TBF=y
|
||||||
CONFIG_NET_SCH_NETEM=y
|
CONFIG_NET_SCH_NETEM=y
|
||||||
CONFIG_NET_SCH_INGRESS=m
|
CONFIG_NET_SCH_INGRESS=m
|
||||||
|
CONFIG_NET_CLS_BASIC=m
|
||||||
CONFIG_NET_CLS_U32=m
|
CONFIG_NET_CLS_U32=m
|
||||||
CONFIG_NET_CLS_CGROUP=y
|
CONFIG_NET_CLS_CGROUP=y
|
||||||
CONFIG_NET_CLS_BPF=m
|
CONFIG_NET_CLS_BPF=m
|
||||||
|
|
|
@ -554,6 +554,9 @@ func (k *Bootstrapper) restartControlPlane(cfg config.ClusterConfig) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err := k.stopKubeSystem(cfg); err != nil {
|
||||||
|
glog.Warningf("Failed to stop kube-system containers: port conflicts may arise: %v", err)
|
||||||
|
}
|
||||||
if err := k.clearStaleConfigs(cfg); err != nil {
|
if err := k.clearStaleConfigs(cfg); err != nil {
|
||||||
return errors.Wrap(err, "clearing stale configs")
|
return errors.Wrap(err, "clearing stale configs")
|
||||||
}
|
}
|
||||||
|
@ -905,6 +908,27 @@ func (k *Bootstrapper) elevateKubeSystemPrivileges(cfg config.ClusterConfig) err
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// stopKubeSystem stops all the containers in the kube-system to prevent #8740 when doing hot upgrade
|
||||||
|
func (k *Bootstrapper) stopKubeSystem(cfg config.ClusterConfig) error {
|
||||||
|
glog.Info("stopping kube-system containers ...")
|
||||||
|
cr, err := cruntime.New(cruntime.Config{Type: cfg.KubernetesConfig.ContainerRuntime, Runner: k.c})
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "new cruntime")
|
||||||
|
}
|
||||||
|
|
||||||
|
ids, err := cr.ListContainers(cruntime.ListOptions{Namespaces: []string{"kube-system"}})
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "list")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ids) > 0 {
|
||||||
|
if err := cr.StopContainers(ids); err != nil {
|
||||||
|
return errors.Wrap(err, "stop")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// adviseNodePressure will advise the user what to do with difference pressure errors based on their environment
|
// adviseNodePressure will advise the user what to do with difference pressure errors based on their environment
|
||||||
func adviseNodePressure(err error, name string, drv string) {
|
func adviseNodePressure(err error, name string, drv string) {
|
||||||
if diskErr, ok := err.(*kverify.ErrDiskPressure); ok {
|
if diskErr, ok := err.(*kverify.ErrDiskPressure); ok {
|
||||||
|
|
|
@ -43,13 +43,13 @@ const (
|
||||||
|
|
||||||
// UsageT outputs a templated usage error and exits with error code 64
|
// UsageT outputs a templated usage error and exits with error code 64
|
||||||
func UsageT(format string, a ...out.V) {
|
func UsageT(format string, a ...out.V) {
|
||||||
out.ErrT(out.Usage, format, a...)
|
out.ErrWithExitCode(out.Usage, format, BadUsage, a...)
|
||||||
os.Exit(BadUsage)
|
os.Exit(BadUsage)
|
||||||
}
|
}
|
||||||
|
|
||||||
// WithCodeT outputs a templated fatal error message and exits with the supplied error code.
|
// WithCodeT outputs a templated fatal error message and exits with the supplied error code.
|
||||||
func WithCodeT(code int, format string, a ...out.V) {
|
func WithCodeT(code int, format string, a ...out.V) {
|
||||||
out.FatalT(format, a...)
|
out.ErrWithExitCode(out.FatalType, format, code, a...)
|
||||||
os.Exit(code)
|
os.Exit(code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,8 +57,12 @@ func WithCodeT(code int, format string, a ...out.V) {
|
||||||
func WithError(msg string, err error) {
|
func WithError(msg string, err error) {
|
||||||
glog.Infof("WithError(%s)=%v called from:\n%s", msg, err, debug.Stack())
|
glog.Infof("WithError(%s)=%v called from:\n%s", msg, err, debug.Stack())
|
||||||
p := problem.FromError(err, runtime.GOOS)
|
p := problem.FromError(err, runtime.GOOS)
|
||||||
if p != nil {
|
if p != nil && out.JSON {
|
||||||
|
p.DisplayJSON(Config)
|
||||||
|
os.Exit(Config)
|
||||||
|
} else {
|
||||||
WithProblem(msg, err, p)
|
WithProblem(msg, err, p)
|
||||||
|
os.Exit(Config)
|
||||||
}
|
}
|
||||||
out.DisplayError(msg, err)
|
out.DisplayError(msg, err)
|
||||||
os.Exit(Software)
|
os.Exit(Software)
|
||||||
|
@ -74,5 +78,4 @@ func WithProblem(msg string, err error, p *problem.Problem) {
|
||||||
out.ErrT(out.Sad, "If the above advice does not help, please let us know: ")
|
out.ErrT(out.Sad, "If the above advice does not help, please let us know: ")
|
||||||
out.ErrT(out.URL, "https://github.com/kubernetes/minikube/issues/new/choose")
|
out.ErrT(out.URL, "https://github.com/kubernetes/minikube/issues/new/choose")
|
||||||
}
|
}
|
||||||
os.Exit(Config)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,6 +27,7 @@ import (
|
||||||
"github.com/shirou/gopsutil/mem"
|
"github.com/shirou/gopsutil/mem"
|
||||||
"k8s.io/minikube/pkg/minikube/command"
|
"k8s.io/minikube/pkg/minikube/command"
|
||||||
"k8s.io/minikube/pkg/minikube/out"
|
"k8s.io/minikube/pkg/minikube/out"
|
||||||
|
"k8s.io/minikube/pkg/minikube/out/register"
|
||||||
)
|
)
|
||||||
|
|
||||||
type hostInfo struct {
|
type hostInfo struct {
|
||||||
|
@ -77,6 +78,7 @@ func showLocalOsRelease() {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
register.Reg.SetStep(register.LocalOSRelease)
|
||||||
out.T(out.Provisioner, "OS release is {{.pretty_name}}", out.V{"pretty_name": osReleaseInfo.PrettyName})
|
out.T(out.Provisioner, "OS release is {{.pretty_name}}", out.V{"pretty_name": osReleaseInfo.PrettyName})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -253,6 +253,7 @@ func showHostInfo(cfg config.ClusterConfig) {
|
||||||
if driver.BareMetal(cfg.Driver) {
|
if driver.BareMetal(cfg.Driver) {
|
||||||
info, err := getHostInfo()
|
info, err := getHostInfo()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
register.Reg.SetStep(register.RunningLocalhost)
|
||||||
out.T(out.StartingNone, "Running on localhost (CPUs={{.number_of_cpus}}, Memory={{.memory_size}}MB, Disk={{.disk_size}}MB) ...", out.V{"number_of_cpus": info.CPUs, "memory_size": info.Memory, "disk_size": info.DiskSize})
|
out.T(out.StartingNone, "Running on localhost (CPUs={{.number_of_cpus}}, Memory={{.memory_size}}MB, Disk={{.disk_size}}MB) ...", out.V{"number_of_cpus": info.CPUs, "memory_size": info.Memory, "disk_size": info.DiskSize})
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
|
|
@ -510,6 +510,7 @@ func tryRegistry(r command.Runner, driverName string, imageRepository string) {
|
||||||
|
|
||||||
// prepareNone prepares the user and host for the joy of the "none" driver
|
// prepareNone prepares the user and host for the joy of the "none" driver
|
||||||
func prepareNone() {
|
func prepareNone() {
|
||||||
|
register.Reg.SetStep(register.ConfiguringLHEnv)
|
||||||
out.T(out.StartingNone, "Configuring local host environment ...")
|
out.T(out.StartingNone, "Configuring local host environment ...")
|
||||||
if viper.GetBool(config.WantNoneDriverWarning) {
|
if viper.GetBool(config.WantNoneDriverWarning) {
|
||||||
out.ErrT(out.Empty, "")
|
out.ErrT(out.Empty, "")
|
||||||
|
|
|
@ -115,6 +115,16 @@ func Ln(format string, a ...interface{}) {
|
||||||
String(format+"\n", a...)
|
String(format+"\n", a...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ErrWithExitCode includes the exit code in JSON output
|
||||||
|
func ErrWithExitCode(style StyleEnum, format string, exitcode int, a ...V) {
|
||||||
|
if JSON {
|
||||||
|
errStyled := ApplyTemplateFormatting(style, useColor, format, a...)
|
||||||
|
register.PrintErrorExitCode(errStyled, exitcode)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ErrT(style, format, a...)
|
||||||
|
}
|
||||||
|
|
||||||
// ErrT writes a stylized and templated error message to stderr
|
// ErrT writes a stylized and templated error message to stderr
|
||||||
func ErrT(style StyleEnum, format string, a ...V) {
|
func ErrT(style StyleEnum, format string, a ...V) {
|
||||||
errStyled := ApplyTemplateFormatting(style, useColor, format, a...)
|
errStyled := ApplyTemplateFormatting(style, useColor, format, a...)
|
||||||
|
@ -123,6 +133,10 @@ func ErrT(style StyleEnum, format string, a ...V) {
|
||||||
|
|
||||||
// Err writes a basic formatted string to stderr
|
// Err writes a basic formatted string to stderr
|
||||||
func Err(format string, a ...interface{}) {
|
func Err(format string, a ...interface{}) {
|
||||||
|
if JSON {
|
||||||
|
register.PrintError(format)
|
||||||
|
return
|
||||||
|
}
|
||||||
if errFile == nil {
|
if errFile == nil {
|
||||||
glog.Errorf("[unset errFile]: %s", fmt.Sprintf(format, a...))
|
glog.Errorf("[unset errFile]: %s", fmt.Sprintf(format, a...))
|
||||||
return
|
return
|
||||||
|
@ -232,8 +246,12 @@ func LogEntries(msg string, err error, entries map[string][]string) {
|
||||||
|
|
||||||
// DisplayError prints the error and displays the standard minikube error messaging
|
// DisplayError prints the error and displays the standard minikube error messaging
|
||||||
func DisplayError(msg string, err error) {
|
func DisplayError(msg string, err error) {
|
||||||
// use Warning because Error will display a duplicate message to stderr
|
|
||||||
glog.Warningf(fmt.Sprintf("%s: %v", msg, err))
|
glog.Warningf(fmt.Sprintf("%s: %v", msg, err))
|
||||||
|
if JSON {
|
||||||
|
FatalT("{{.msg}}: {{.err}}", V{"msg": translate.T(msg), "err": err})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// use Warning because Error will display a duplicate message to stderr
|
||||||
ErrT(Empty, "")
|
ErrT(Empty, "")
|
||||||
FatalT("{{.msg}}: {{.err}}", V{"msg": translate.T(msg), "err": err})
|
FatalT("{{.msg}}: {{.err}}", V{"msg": translate.T(msg), "err": err})
|
||||||
ErrT(Empty, "")
|
ErrT(Empty, "")
|
||||||
|
|
|
@ -31,8 +31,8 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
outputFile io.Writer = os.Stdout
|
OutputFile io.Writer = os.Stdout
|
||||||
getUUID = randomID
|
GetUUID = randomID
|
||||||
)
|
)
|
||||||
|
|
||||||
func printAsCloudEvent(log Log, data map[string]string) {
|
func printAsCloudEvent(log Log, data map[string]string) {
|
||||||
|
@ -43,12 +43,12 @@ func printAsCloudEvent(log Log, data map[string]string) {
|
||||||
if err := event.SetData(cloudevents.ApplicationJSON, data); err != nil {
|
if err := event.SetData(cloudevents.ApplicationJSON, data); err != nil {
|
||||||
glog.Warningf("error setting data: %v", err)
|
glog.Warningf("error setting data: %v", err)
|
||||||
}
|
}
|
||||||
event.SetID(getUUID())
|
event.SetID(GetUUID())
|
||||||
json, err := event.MarshalJSON()
|
json, err := event.MarshalJSON()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.Warningf("error marashalling event: %v", err)
|
glog.Warningf("error marashalling event: %v", err)
|
||||||
}
|
}
|
||||||
fmt.Fprintln(outputFile, string(json))
|
fmt.Fprintln(OutputFile, string(json))
|
||||||
}
|
}
|
||||||
|
|
||||||
func randomID() string {
|
func randomID() string {
|
||||||
|
|
|
@ -40,6 +40,18 @@ func PrintDownloadProgress(artifact, progress string) {
|
||||||
printAsCloudEvent(s, s.data)
|
printAsCloudEvent(s, s.data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PrintError prints an Error type in JSON format
|
||||||
|
func PrintError(err string) {
|
||||||
|
e := NewError(err)
|
||||||
|
printAsCloudEvent(e, e.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrintErrorExitCode prints an error in JSON format and includes an exit code
|
||||||
|
func PrintErrorExitCode(err string, exitcode int, additionalArgs ...map[string]string) {
|
||||||
|
e := NewErrorExitCode(err, exitcode, additionalArgs...)
|
||||||
|
printAsCloudEvent(e, e.data)
|
||||||
|
}
|
||||||
|
|
||||||
// PrintWarning prints a Warning type in JSON format
|
// PrintWarning prints a Warning type in JSON format
|
||||||
func PrintWarning(warning string) {
|
func PrintWarning(warning string) {
|
||||||
w := NewWarning(warning)
|
w := NewWarning(warning)
|
||||||
|
|
|
@ -29,10 +29,10 @@ func TestPrintStep(t *testing.T) {
|
||||||
expected += "\n"
|
expected += "\n"
|
||||||
|
|
||||||
buf := bytes.NewBuffer([]byte{})
|
buf := bytes.NewBuffer([]byte{})
|
||||||
outputFile = buf
|
OutputFile = buf
|
||||||
defer func() { outputFile = os.Stdout }()
|
defer func() { OutputFile = os.Stdout }()
|
||||||
|
|
||||||
getUUID = func() string {
|
GetUUID = func() string {
|
||||||
return "random-id"
|
return "random-id"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,10 +49,10 @@ func TestPrintInfo(t *testing.T) {
|
||||||
expected += "\n"
|
expected += "\n"
|
||||||
|
|
||||||
buf := bytes.NewBuffer([]byte{})
|
buf := bytes.NewBuffer([]byte{})
|
||||||
outputFile = buf
|
OutputFile = buf
|
||||||
defer func() { outputFile = os.Stdout }()
|
defer func() { OutputFile = os.Stdout }()
|
||||||
|
|
||||||
getUUID = func() string {
|
GetUUID = func() string {
|
||||||
return "random-id"
|
return "random-id"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,15 +64,53 @@ func TestPrintInfo(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestError(t *testing.T) {
|
||||||
|
expected := `{"data":{"message":"error"},"datacontenttype":"application/json","id":"random-id","source":"https://minikube.sigs.k8s.io/","specversion":"1.0","type":"io.k8s.sigs.minikube.error"}`
|
||||||
|
expected += "\n"
|
||||||
|
|
||||||
|
buf := bytes.NewBuffer([]byte{})
|
||||||
|
OutputFile = buf
|
||||||
|
defer func() { OutputFile = os.Stdout }()
|
||||||
|
|
||||||
|
GetUUID = func() string {
|
||||||
|
return "random-id"
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintError("error")
|
||||||
|
actual := buf.String()
|
||||||
|
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("expected didn't match actual:\nExpected:\n%v\n\nActual:\n%v", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorExitCode(t *testing.T) {
|
||||||
|
expected := `{"data":{"a":"b","c":"d","exitcode":"5","message":"error"},"datacontenttype":"application/json","id":"random-id","source":"https://minikube.sigs.k8s.io/","specversion":"1.0","type":"io.k8s.sigs.minikube.error"}`
|
||||||
|
expected += "\n"
|
||||||
|
|
||||||
|
buf := bytes.NewBuffer([]byte{})
|
||||||
|
OutputFile = buf
|
||||||
|
defer func() { OutputFile = os.Stdout }()
|
||||||
|
|
||||||
|
GetUUID = func() string {
|
||||||
|
return "random-id"
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintErrorExitCode("error", 5, map[string]string{"a": "b"}, map[string]string{"c": "d"})
|
||||||
|
actual := buf.String()
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("expected didn't match actual:\nExpected:\n%v\n\nActual:\n%v", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
func TestWarning(t *testing.T) {
|
func TestWarning(t *testing.T) {
|
||||||
expected := `{"data":{"message":"warning"},"datacontenttype":"application/json","id":"random-id","source":"https://minikube.sigs.k8s.io/","specversion":"1.0","type":"io.k8s.sigs.minikube.warning"}`
|
expected := `{"data":{"message":"warning"},"datacontenttype":"application/json","id":"random-id","source":"https://minikube.sigs.k8s.io/","specversion":"1.0","type":"io.k8s.sigs.minikube.warning"}`
|
||||||
expected += "\n"
|
expected += "\n"
|
||||||
|
|
||||||
buf := bytes.NewBuffer([]byte{})
|
buf := bytes.NewBuffer([]byte{})
|
||||||
outputFile = buf
|
OutputFile = buf
|
||||||
defer func() { outputFile = os.Stdout }()
|
defer func() { OutputFile = os.Stdout }()
|
||||||
|
|
||||||
getUUID = func() string {
|
GetUUID = func() string {
|
||||||
return "random-id"
|
return "random-id"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,8 @@ limitations under the License.
|
||||||
|
|
||||||
package register
|
package register
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
|
||||||
// Log represents the different types of logs that can be output as JSON
|
// Log represents the different types of logs that can be output as JSON
|
||||||
// This includes: Step, Download, DownloadProgress, Warning, Info, Error
|
// This includes: Step, Download, DownloadProgress, Warning, Info, Error
|
||||||
type Log interface {
|
type Log interface {
|
||||||
|
@ -120,6 +122,27 @@ func NewInfo(message string) *Info {
|
||||||
|
|
||||||
// Error will be used to notify the user of errors
|
// Error will be used to notify the user of errors
|
||||||
type Error struct {
|
type Error struct {
|
||||||
|
data map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewError(err string) *Error {
|
||||||
|
return &Error{
|
||||||
|
map[string]string{
|
||||||
|
"message": err,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewErrorExitCode returns an error that has an associated exit code
|
||||||
|
func NewErrorExitCode(err string, exitcode int, additionalData ...map[string]string) *Error {
|
||||||
|
e := NewError(err)
|
||||||
|
e.data["exitcode"] = fmt.Sprintf("%v", exitcode)
|
||||||
|
for _, a := range additionalData {
|
||||||
|
for k, v := range a {
|
||||||
|
e.data[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Error) Type() string {
|
func (s *Error) Type() string {
|
||||||
|
|
|
@ -26,8 +26,11 @@ const (
|
||||||
SelectingDriver RegStep = "Selecting Driver"
|
SelectingDriver RegStep = "Selecting Driver"
|
||||||
DownloadingArtifacts RegStep = "Downloading Artifacts"
|
DownloadingArtifacts RegStep = "Downloading Artifacts"
|
||||||
StartingNode RegStep = "Starting Node"
|
StartingNode RegStep = "Starting Node"
|
||||||
|
RunningLocalhost RegStep = "Running on Localhost"
|
||||||
|
LocalOSRelease RegStep = "Local OS Release"
|
||||||
CreatingContainer RegStep = "Creating Container"
|
CreatingContainer RegStep = "Creating Container"
|
||||||
CreatingVM RegStep = "Creating VM"
|
CreatingVM RegStep = "Creating VM"
|
||||||
|
ConfiguringLHEnv RegStep = "Configuring Localhost Environment"
|
||||||
PreparingKubernetes RegStep = "Preparing Kubernetes"
|
PreparingKubernetes RegStep = "Preparing Kubernetes"
|
||||||
VerifyingKubernetes RegStep = "Verifying Kubernetes"
|
VerifyingKubernetes RegStep = "Verifying Kubernetes"
|
||||||
EnablingAddons RegStep = "Enabling Addons"
|
EnablingAddons RegStep = "Enabling Addons"
|
||||||
|
@ -54,9 +57,12 @@ func init() {
|
||||||
SelectingDriver,
|
SelectingDriver,
|
||||||
DownloadingArtifacts,
|
DownloadingArtifacts,
|
||||||
StartingNode,
|
StartingNode,
|
||||||
|
RunningLocalhost,
|
||||||
|
LocalOSRelease,
|
||||||
CreatingContainer,
|
CreatingContainer,
|
||||||
CreatingVM,
|
CreatingVM,
|
||||||
PreparingKubernetes,
|
PreparingKubernetes,
|
||||||
|
ConfiguringLHEnv,
|
||||||
VerifyingKubernetes,
|
VerifyingKubernetes,
|
||||||
EnablingAddons,
|
EnablingAddons,
|
||||||
Done,
|
Done,
|
||||||
|
|
|
@ -32,10 +32,10 @@ func TestSetCurrentStep(t *testing.T) {
|
||||||
expected += "\n"
|
expected += "\n"
|
||||||
|
|
||||||
buf := bytes.NewBuffer([]byte{})
|
buf := bytes.NewBuffer([]byte{})
|
||||||
outputFile = buf
|
OutputFile = buf
|
||||||
defer func() { outputFile = os.Stdout }()
|
defer func() { OutputFile = os.Stdout }()
|
||||||
|
|
||||||
getUUID = func() string {
|
GetUUID = func() string {
|
||||||
return "random-id"
|
return "random-id"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"regexp"
|
"regexp"
|
||||||
|
|
||||||
"k8s.io/minikube/pkg/minikube/out"
|
"k8s.io/minikube/pkg/minikube/out"
|
||||||
|
"k8s.io/minikube/pkg/minikube/out/register"
|
||||||
"k8s.io/minikube/pkg/minikube/translate"
|
"k8s.io/minikube/pkg/minikube/translate"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -80,6 +81,21 @@ func (p *Problem) Display() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DisplayJSON displays problem metadata in JSON format
|
||||||
|
func (p *Problem) DisplayJSON(exitcode int) {
|
||||||
|
var issues string
|
||||||
|
for _, i := range p.Issues {
|
||||||
|
issues += fmt.Sprintf("https://github.com/kubernetes/minikube/issues/%v,", i)
|
||||||
|
}
|
||||||
|
extraArgs := map[string]string{
|
||||||
|
"name": p.ID,
|
||||||
|
"advice": p.Advice,
|
||||||
|
"url": p.URL,
|
||||||
|
"issues": issues,
|
||||||
|
}
|
||||||
|
register.PrintErrorExitCode(p.Err.Error(), exitcode, extraArgs)
|
||||||
|
}
|
||||||
|
|
||||||
// FromError returns a known problem from an error on an OS
|
// FromError returns a known problem from an error on an OS
|
||||||
func FromError(err error, goos string) *Problem {
|
func FromError(err error, goos string) *Problem {
|
||||||
maps := []map[string]match{
|
maps := []map[string]match{
|
||||||
|
|
|
@ -19,10 +19,12 @@ package problem
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"k8s.io/minikube/pkg/minikube/out"
|
"k8s.io/minikube/pkg/minikube/out"
|
||||||
|
"k8s.io/minikube/pkg/minikube/out/register"
|
||||||
)
|
)
|
||||||
|
|
||||||
type buffFd struct {
|
type buffFd struct {
|
||||||
|
@ -96,6 +98,45 @@ func TestDisplay(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDisplayJSON(t *testing.T) {
|
||||||
|
defer out.SetJSON(false)
|
||||||
|
out.SetJSON(true)
|
||||||
|
|
||||||
|
tcs := []struct {
|
||||||
|
p *Problem
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
p: &Problem{
|
||||||
|
Err: fmt.Errorf("my error"),
|
||||||
|
Advice: "fix me!",
|
||||||
|
Issues: []int{1, 2},
|
||||||
|
URL: "url",
|
||||||
|
ID: "BUG",
|
||||||
|
},
|
||||||
|
expected: `{"data":{"advice":"fix me!","exitcode":"4","issues":"https://github.com/kubernetes/minikube/issues/1,https://github.com/kubernetes/minikube/issues/2,","message":"my error","name":"BUG","url":"url"},"datacontenttype":"application/json","id":"random-id","source":"https://minikube.sigs.k8s.io/","specversion":"1.0","type":"io.k8s.sigs.minikube.error"}
|
||||||
|
`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range tcs {
|
||||||
|
t.Run(tc.p.ID, func(t *testing.T) {
|
||||||
|
buf := bytes.NewBuffer([]byte{})
|
||||||
|
register.OutputFile = buf
|
||||||
|
defer func() { register.OutputFile = os.Stdout }()
|
||||||
|
|
||||||
|
register.GetUUID = func() string {
|
||||||
|
return "random-id"
|
||||||
|
}
|
||||||
|
|
||||||
|
tc.p.DisplayJSON(4)
|
||||||
|
actual := buf.String()
|
||||||
|
if actual != tc.expected {
|
||||||
|
t.Fatalf("expected didn't match actual:\nExpected:\n%v\n\nActual:\n%v", tc.expected, actual)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestFromError(t *testing.T) {
|
func TestFromError(t *testing.T) {
|
||||||
var tests = []struct {
|
var tests = []struct {
|
||||||
issue int
|
issue int
|
||||||
|
|
|
@ -109,7 +109,7 @@ func (d DriverDef) String() string {
|
||||||
|
|
||||||
type driverRegistry struct {
|
type driverRegistry struct {
|
||||||
drivers map[string]DriverDef
|
drivers map[string]DriverDef
|
||||||
lock sync.Mutex
|
lock sync.RWMutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func newRegistry() *driverRegistry {
|
func newRegistry() *driverRegistry {
|
||||||
|
@ -133,8 +133,8 @@ func (r *driverRegistry) Register(def DriverDef) error {
|
||||||
|
|
||||||
// List returns a list of registered drivers
|
// List returns a list of registered drivers
|
||||||
func (r *driverRegistry) List() []DriverDef {
|
func (r *driverRegistry) List() []DriverDef {
|
||||||
r.lock.Lock()
|
r.lock.RLock()
|
||||||
defer r.lock.Unlock()
|
defer r.lock.RUnlock()
|
||||||
|
|
||||||
result := make([]DriverDef, 0, len(r.drivers))
|
result := make([]DriverDef, 0, len(r.drivers))
|
||||||
|
|
||||||
|
@ -147,7 +147,7 @@ func (r *driverRegistry) List() []DriverDef {
|
||||||
|
|
||||||
// Driver returns a driver given a name
|
// Driver returns a driver given a name
|
||||||
func (r *driverRegistry) Driver(name string) DriverDef {
|
func (r *driverRegistry) Driver(name string) DriverDef {
|
||||||
r.lock.Lock()
|
r.lock.RLock()
|
||||||
defer r.lock.Unlock()
|
defer r.lock.RUnlock()
|
||||||
return r.drivers[name]
|
return r.drivers[name]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
---
|
||||||
|
title: "Minikube JSON Output"
|
||||||
|
date: 2019-07-31
|
||||||
|
weight: 4
|
||||||
|
description: >
|
||||||
|
How to add logs to facilitate JSON output
|
||||||
|
---
|
||||||
|
|
||||||
|
This document is written for minikube contributors who need to add logs to the minikube log registry for sucessful JSON output.
|
||||||
|
You may need to add logs to the registry if the `TestJSONOutput` integration test is failing on your PR.
|
||||||
|
|
||||||
|
### Background
|
||||||
|
minikube provides JSON output for `minikube start`, accesible via the `--output` flag:
|
||||||
|
|
||||||
|
```
|
||||||
|
minikube start --output json
|
||||||
|
```
|
||||||
|
|
||||||
|
This converts regular output:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ minikube start
|
||||||
|
|
||||||
|
😄 minikube v1.12.1 on Darwin 10.14.6
|
||||||
|
✨ Automatically selected the hyperkit driver
|
||||||
|
👍 Starting control plane node minikube in cluster minikube
|
||||||
|
🔥 Creating hyperkit VM (CPUs=2, Memory=6000MB, Disk=20000MB) ...
|
||||||
|
```
|
||||||
|
|
||||||
|
into a [Cloud Events](https://cloudevents.io/) compatible JSON output:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ minikube start --output json
|
||||||
|
|
||||||
|
{"data":{"currentstep":"0","message":"minikube v1.12.1 on Darwin 10.14.6\n","name":"Initial Minikube Setup","totalsteps":"10"},"datacontenttype":"application/json","id":"68ff70ae-202b-4b13-8351-e9f060e8c56e","source":"https://minikube.sigs.k8s.io/","specversion":"1.0","type":"io.k8s.sigs.minikube.step"}
|
||||||
|
{"data":{"currentstep":"1","message":"Automatically selected the hyperkit driver\n","name":"Selecting Driver","totalsteps":"10"},"datacontenttype":"application/json","id":"39bed8e9-3c1a-444e-997c-2ec19bdb1ca1","source":"https://minikube.sigs.k8s.io/","specversion":"1.0","type":"io.k8s.sigs.minikube.step"}
|
||||||
|
{"data":{"currentstep":"3","message":"Starting control plane node minikube in cluster minikube\n","name":"Starting Node","totalsteps":"10"},"datacontenttype":"application/json","id":"7c80bc53-3ac4-4a42-a493-92e269cc56c9","source":"https://minikube.sigs.k8s.io/","specversion":"1.0","type":"io.k8s.sigs.minikube.step"}
|
||||||
|
{"data":{"currentstep":"6","message":"Creating hyperkit VM (CPUs=2, Memory=6000MB, Disk=20000MB) ...\n","name":"Creating VM","totalsteps":"10"},"datacontenttype":"application/json","id":"7f5f23a4-9a09-4954-8abc-d29bda2cc569","source":"https://minikube.sigs.k8s.io/","specversion":"1.0","type":"io.k8s.sigs.minikube.step"}
|
||||||
|
```
|
||||||
|
|
||||||
|
There are a few key points to note in the above output:
|
||||||
|
1. Each log of type `io.k8s.sigs.minikube.step` indicates a distinct step in the `minikube start` process
|
||||||
|
1. Each step has a `currentstep` field which allows clients to track `minikube start` progress
|
||||||
|
1. Each `currentstep` is distinct and increasing in order
|
||||||
|
|
||||||
|
To achieve this output, minikube maintains a registry of logs.
|
||||||
|
This way, minikube knows how many expected `totalsteps` there are at the beginning of the process, and what the current step is.
|
||||||
|
|
||||||
|
If you change logs, or add a new log, you need to update the minikube registry to pass integration tests.
|
||||||
|
|
||||||
|
|
||||||
|
### Adding a Log to the Registry
|
||||||
|
|
||||||
|
There are three steps to adding a log to the registry, which exists in [register.go](https://github.com/kubernetes/minikube/blob/master/pkg/minikube/out/register/register.go).
|
||||||
|
|
||||||
|
You will need to add your new log in two places:
|
||||||
|
1. As a constant of type `RegStep` [here](https://github.com/kubernetes/minikube/blob/master/pkg/minikube/out/register/register.go#L24)
|
||||||
|
1. In the register itself in the `init()` function, [here](https://github.com/kubernetes/minikube/blob/master/pkg/minikube/out/register/register.go#L52)
|
||||||
|
|
||||||
|
**Note: It's important that the order of steps matches the expected order they will be called in. So, if you add a step that is supposed to be called after "Preparing Kubernetes", the new step should be place after "Preparing Kubernetes".
|
||||||
|
|
||||||
|
Finally, set your new step in the cod by placing this line before you call `out.T`:
|
||||||
|
|
||||||
|
```
|
||||||
|
register.Reg.SetStep(register.MyNewStep)
|
||||||
|
```
|
||||||
|
|
||||||
|
You can see an example of setting the registry step in the code in [config.go](https://github.com/kubernetes/minikube/blob/master/pkg/minikube/node/config.go):
|
||||||
|
|
||||||
|
```go
|
||||||
|
register.Reg.SetStep(register.PreparingKubernetes)
|
||||||
|
out.T(cr.Style(), "Preparing Kubernetes {{.k8sVersion}} on {{.runtime}} {{.runtimeVersion}} ...", out.V{"k8sVersion": k8sVersion, "runtime": cr.Name(), "runtimeVersion": version})
|
||||||
|
```
|
|
@ -16,7 +16,7 @@ All translations are stored in the top-level `translations` directory.
|
||||||
~/minikube$ ls translations/
|
~/minikube$ ls translations/
|
||||||
de.json es.json fr.json ja.json ko.json pl.json zh-CN.json
|
de.json es.json fr.json ja.json ko.json pl.json zh-CN.json
|
||||||
```
|
```
|
||||||
* Run `make extract` from root to populate that file with the strings to translate in json
|
* Run `make extract` from root directory to populate that file with the strings to translate in json
|
||||||
form.
|
form.
|
||||||
```
|
```
|
||||||
~/minikube$ make extract
|
~/minikube$ make extract
|
||||||
|
|
|
@ -25,6 +25,8 @@ minikube start \
|
||||||
--extra-config=apiserver.oidc-client-id=kubernetes-local
|
--extra-config=apiserver.oidc-client-id=kubernetes-local
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Note that as stated in the Kubernetes [documentation](https://kubernetes.io/docs/reference/access-authn-authz/authentication/#configuring-the-api-server), for `--extra-config=apiserver.oidc-issuer-url` flag, only URLs which use the `https://` scheme are accepted. Otherwise `kube-apiserver` will not start.
|
||||||
|
|
||||||
## Configuring kubectl
|
## Configuring kubectl
|
||||||
|
|
||||||
You can use the kubectl `oidc` authenticator to create a kubeconfig as shown in the Kubernetes docs: <https://kubernetes.io/docs/reference/access-authn-authz/authentication/#option-1-oidc-authenticator>
|
You can use the kubectl `oidc` authenticator to create a kubeconfig as shown in the Kubernetes docs: <https://kubernetes.io/docs/reference/access-authn-authz/authentication/#option-1-oidc-authenticator>
|
||||||
|
|
|
@ -36,6 +36,10 @@ var stderrAllow = []string{
|
||||||
`cache_images.go:.*error getting status`,
|
`cache_images.go:.*error getting status`,
|
||||||
// don't care if we can't push images to other profiles which are deleted.
|
// don't care if we can't push images to other profiles which are deleted.
|
||||||
`cache_images.go:.*Failed to load profile`,
|
`cache_images.go:.*Failed to load profile`,
|
||||||
|
// ! 'docker' driver reported a issue that could affect the performance."
|
||||||
|
`docker.*issue.*performance`,
|
||||||
|
// "* Suggestion: enable overlayfs kernel module on your Linux"
|
||||||
|
`Suggestion.*overlayfs`,
|
||||||
}
|
}
|
||||||
|
|
||||||
// stderrAllowRe combines rootCauses into a single regex
|
// stderrAllowRe combines rootCauses into a single regex
|
||||||
|
@ -64,14 +68,10 @@ func TestErrorSpam(t *testing.T) {
|
||||||
stderr := rr.Stderr.String()
|
stderr := rr.Stderr.String()
|
||||||
|
|
||||||
for _, line := range strings.Split(stderr, "\n") {
|
for _, line := range strings.Split(stderr, "\n") {
|
||||||
if strings.HasPrefix(line, "E") {
|
|
||||||
if stderrAllowRe.MatchString(line) {
|
if stderrAllowRe.MatchString(line) {
|
||||||
t.Logf("acceptable stderr: %q", line)
|
t.Logf("acceptable stderr: %q", line)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
t.Errorf("unexpected error log: %q", line)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(strings.TrimSpace(line)) > 0 {
|
if len(strings.TrimSpace(line)) > 0 {
|
||||||
t.Errorf("unexpected stderr: %q", line)
|
t.Errorf("unexpected stderr: %q", line)
|
||||||
|
|
|
@ -19,17 +19,19 @@ package integration
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
cloudevents "github.com/cloudevents/sdk-go/v2"
|
cloudevents "github.com/cloudevents/sdk-go/v2"
|
||||||
|
"k8s.io/minikube/pkg/minikube/exit"
|
||||||
|
"k8s.io/minikube/pkg/minikube/out/register"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestJSONOutput(t *testing.T) {
|
func TestJSONOutput(t *testing.T) {
|
||||||
if NoneDriver() || DockerDriver() {
|
|
||||||
t.Skipf("skipping: test drivers once all JSON output is enabled")
|
|
||||||
}
|
|
||||||
profile := UniqueProfileName("json-output")
|
profile := UniqueProfileName("json-output")
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), Minutes(40))
|
ctx, cancel := context.WithTimeout(context.Background(), Minutes(40))
|
||||||
defer Cleanup(t, profile, cancel)
|
defer Cleanup(t, profile, cancel)
|
||||||
|
@ -42,25 +44,117 @@ func TestJSONOutput(t *testing.T) {
|
||||||
t.Errorf("failed to clean up: args %q: %v", rr.Command(), err)
|
t.Errorf("failed to clean up: args %q: %v", rr.Command(), err)
|
||||||
}
|
}
|
||||||
|
|
||||||
type validateJSONOutputFunc func(context.Context, *testing.T, *RunResult)
|
ces, err := cloudEvents(t, rr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("converting to cloud events: %v\n", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type validateJSONOutputFunc func(context.Context, *testing.T, []*cloudEvent)
|
||||||
t.Run("serial", func(t *testing.T) {
|
t.Run("serial", func(t *testing.T) {
|
||||||
serialTests := []struct {
|
serialTests := []struct {
|
||||||
name string
|
name string
|
||||||
validator validateJSONOutputFunc
|
validator validateJSONOutputFunc
|
||||||
}{
|
}{
|
||||||
{"CloudEvents", validateCloudEvents},
|
{"DistinctCurrentSteps", validateDistinctCurrentSteps},
|
||||||
|
{"IncreasingCurrentSteps", validateIncreasingCurrentSteps},
|
||||||
}
|
}
|
||||||
for _, stc := range serialTests {
|
for _, stc := range serialTests {
|
||||||
t.Run(stc.name, func(t *testing.T) {
|
t.Run(stc.name, func(t *testing.T) {
|
||||||
stc.validator(ctx, t, rr)
|
stc.validator(ctx, t, ces)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// make sure all output can be marshaled as a cloud event
|
// make sure each step has a distinct step number
|
||||||
func validateCloudEvents(ctx context.Context, t *testing.T, rr *RunResult) {
|
func validateDistinctCurrentSteps(ctx context.Context, t *testing.T, ces []*cloudEvent) {
|
||||||
|
steps := map[string]string{}
|
||||||
|
for _, ce := range ces {
|
||||||
|
currentStep, exists := ce.data["currentstep"]
|
||||||
|
if !exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if msg, alreadySeen := steps[currentStep]; alreadySeen {
|
||||||
|
t.Fatalf("step %v has already been assigned to another step:\n%v\nCannot use for:\n%v\n%v", currentStep, msg, ce.data["message"], ces)
|
||||||
|
}
|
||||||
|
steps[currentStep] = ce.data["message"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// for successful minikube start, 'current step' should be increasing
|
||||||
|
func validateIncreasingCurrentSteps(ctx context.Context, t *testing.T, ces []*cloudEvent) {
|
||||||
|
step := -1
|
||||||
|
for _, ce := range ces {
|
||||||
|
currentStep, exists := ce.data["currentstep"]
|
||||||
|
if !exists {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
cs, err := strconv.Atoi(currentStep)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("current step is not an integer: %v\n%v", currentStep, ce)
|
||||||
|
}
|
||||||
|
if cs <= step {
|
||||||
|
t.Fatalf("current step is not in increasing order: %v", ces)
|
||||||
|
}
|
||||||
|
step = cs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONOutputError(t *testing.T) {
|
||||||
|
profile := UniqueProfileName("json-output-error")
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), Minutes(2))
|
||||||
|
defer Cleanup(t, profile, cancel)
|
||||||
|
|
||||||
|
// force a failure via --driver=fail so that we can make sure errors
|
||||||
|
// are printed as expected
|
||||||
|
startArgs := []string{"start", "-p", profile, "--memory=2200", "--output=json", "--wait=true", "--driver=fail"}
|
||||||
|
|
||||||
|
rr, err := Run(t, exec.CommandContext(ctx, Target(), startArgs...))
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("expected failure: args %q: %v", rr.Command(), err)
|
||||||
|
}
|
||||||
|
ces, err := cloudEvents(t, rr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// we want the last cloud event to be of type error and have the expected exit code and message
|
||||||
|
last := ces[len(ces)-1]
|
||||||
|
if last.Type() != register.NewError("").Type() {
|
||||||
|
t.Fatalf("last cloud event is not of type error: %v", last)
|
||||||
|
}
|
||||||
|
last.validateData(t, "exitcode", fmt.Sprintf("%v", exit.Unavailable))
|
||||||
|
last.validateData(t, "message", fmt.Sprintf("The driver 'fail' is not supported on %s\n", runtime.GOOS))
|
||||||
|
}
|
||||||
|
|
||||||
|
type cloudEvent struct {
|
||||||
|
cloudevents.Event
|
||||||
|
data map[string]string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCloudEvent(t *testing.T, ce cloudevents.Event) *cloudEvent {
|
||||||
|
m := map[string]string{}
|
||||||
|
data := ce.Data()
|
||||||
|
if err := json.Unmarshal(data, &m); err != nil {
|
||||||
|
t.Fatalf("marshalling cloud event: %v", err)
|
||||||
|
}
|
||||||
|
return &cloudEvent{
|
||||||
|
Event: ce,
|
||||||
|
data: m,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cloudEvent) validateData(t *testing.T, key, value string) {
|
||||||
|
v, ok := c.data[key]
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("expected key %s does not exist in cloud event", key)
|
||||||
|
}
|
||||||
|
if v != value {
|
||||||
|
t.Fatalf("values in cloud events do not match:\nActual:\n%v\nExpected:\n%v\n", v, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloudEvents(t *testing.T, rr *RunResult) ([]*cloudEvent, error) {
|
||||||
|
ces := []*cloudEvent{}
|
||||||
stdout := strings.Split(rr.Stdout.String(), "\n")
|
stdout := strings.Split(rr.Stdout.String(), "\n")
|
||||||
for _, s := range stdout {
|
for _, s := range stdout {
|
||||||
if s == "" {
|
if s == "" {
|
||||||
|
@ -68,7 +162,10 @@ func validateCloudEvents(ctx context.Context, t *testing.T, rr *RunResult) {
|
||||||
}
|
}
|
||||||
event := cloudevents.NewEvent()
|
event := cloudevents.NewEvent()
|
||||||
if err := json.Unmarshal([]byte(s), &event); err != nil {
|
if err := json.Unmarshal([]byte(s), &event); err != nil {
|
||||||
t.Fatalf("unable to unmarshal output: %v\n%s", err, s)
|
t.Logf("unable to marshal output: %v", s)
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
ces = append(ces, newCloudEvent(t, event))
|
||||||
}
|
}
|
||||||
|
return ces, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,7 +81,7 @@ const (
|
||||||
|
|
||||||
// The srvFile type represents a file (or directory) served by the file server.
|
// The srvFile type represents a file (or directory) served by the file server.
|
||||||
type srvFile struct {
|
type srvFile struct {
|
||||||
sync.Mutex
|
sync.RWMutex
|
||||||
Dir
|
Dir
|
||||||
flags FFlags
|
flags FFlags
|
||||||
|
|
||||||
|
@ -239,13 +239,13 @@ func (f *srvFile) Rename(name string) error {
|
||||||
func (p *srvFile) Find(name string) *srvFile {
|
func (p *srvFile) Find(name string) *srvFile {
|
||||||
var f *srvFile
|
var f *srvFile
|
||||||
|
|
||||||
p.Lock()
|
p.RLock()
|
||||||
for f = p.cfirst; f != nil; f = f.next {
|
for f = p.cfirst; f != nil; f = f.next {
|
||||||
if name == f.Name {
|
if name == f.Name {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
p.Unlock()
|
p.RUnlock()
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -496,13 +496,13 @@ func (*Fsrv) Clunk(req *SrvReq) {
|
||||||
func (*Fsrv) Remove(req *SrvReq) {
|
func (*Fsrv) Remove(req *SrvReq) {
|
||||||
fid := req.Fid.Aux.(*FFid)
|
fid := req.Fid.Aux.(*FFid)
|
||||||
f := fid.F
|
f := fid.F
|
||||||
f.Lock()
|
f.RLock()
|
||||||
if f.cfirst != nil {
|
if f.cfirst != nil {
|
||||||
f.Unlock()
|
f.RUnlock()
|
||||||
req.RespondError(Enotempty)
|
req.RespondError(Enotempty)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
f.Unlock()
|
f.RUnlock()
|
||||||
|
|
||||||
if rop, ok := (f.ops).(FRemoveOp); ok {
|
if rop, ok := (f.ops).(FRemoveOp); ok {
|
||||||
err := rop.Remove(fid)
|
err := rop.Remove(fid)
|
||||||
|
|
Loading…
Reference in New Issue