Merge pull request #249 from captainroy-hy/app-topology

implement "vela app status"
This commit is contained in:
Sun Jianbo
2020-09-10 10:06:25 +08:00
committed by GitHub
8 changed files with 274 additions and 17 deletions

View File

@@ -24,6 +24,7 @@ var _ = ginkgo.Describe("Application", func() {
e2e.TraitManualScalerAttachContext("vela attach trait", traitAlias, applicationName)
//e2e.ApplicationListContext("app ls", applicationName, traitAlias)
e2e.ApplicationShowContext("app show", applicationName, workloadType)
e2e.ApplicationStatusContext("comp status", applicationName, workloadType)
e2e.ApplicationStatusContext("app status", applicationName, workloadType)
e2e.ApplicationCompStatusContext("comp status", applicationName, workloadType)
e2e.WorkloadDeleteContext("delete", applicationName)
})

View File

@@ -177,6 +177,18 @@ var (
ApplicationStatusContext = func(context string, applicationName string, workloadType string) bool {
return ginkgo.Context(context, func() {
ginkgo.It("should get status for the application", func() {
cli := fmt.Sprintf("vela app status %s", applicationName)
output, err := Exec(cli)
gomega.Expect(err).NotTo(gomega.HaveOccurred())
gomega.Expect(output).To(gomega.ContainSubstring(applicationName))
// TODO(roywang) add more assertion to check health status
})
})
}
ApplicationCompStatusContext = func(context string, applicationName string, workloadType string) bool {
return ginkgo.Context(context, func() {
ginkgo.It("should get status for the component", func() {
cli := fmt.Sprintf("vela comp status %s", applicationName)
output, err := Exec(cli)
gomega.Expect(err).NotTo(gomega.HaveOccurred())

6
go.mod
View File

@@ -7,7 +7,8 @@ require (
github.com/Azure/go-autorest v12.2.0+incompatible // Don't remove. https://github.com/kubernetes/client-go/issues/628
github.com/coreos/prometheus-operator v0.41.1
github.com/crossplane/crossplane-runtime v0.9.0
github.com/crossplane/oam-kubernetes-runtime v0.0.9
github.com/crossplane/oam-kubernetes-runtime v0.1.1-0.20200909070723-78b84f2c4799
github.com/fatih/color v1.9.0
github.com/gertd/go-pluralize v0.1.7
github.com/ghodss/yaml v1.0.0
github.com/gin-gonic/gin v1.6.3
@@ -15,11 +16,11 @@ require (
github.com/google/go-cmp v0.5.2
github.com/google/go-github/v32 v32.1.0
github.com/gosuri/uitable v0.0.4
github.com/oam-dev/catalog/traits/metricstrait v0.0.0-20200826071236-d96c1d64e221
github.com/oam-dev/trait-injector v0.0.0-20200331033130-0a27b176ffc4
github.com/onsi/ginkgo v1.11.0
github.com/onsi/gomega v1.8.1
github.com/pkg/errors v0.9.1
github.com/rs/xid v1.2.1 // indirect
github.com/satori/go.uuid v1.2.1-0.20181028125025-b2ce2384e17b
github.com/spf13/cobra v1.0.0
github.com/spf13/pflag v1.0.5
@@ -36,6 +37,7 @@ require (
k8s.io/cli-runtime v0.18.6
k8s.io/client-go v12.0.0+incompatible
k8s.io/klog v1.0.0
k8s.io/kube-openapi v0.0.0-20200410145947-bcb3869e6f29 // indirect
k8s.io/kubectl v0.18.6 // indirect
k8s.io/utils v0.0.0-20200414100711-2df71ebbae66
rsc.io/letsencrypt v0.0.3 // indirect

6
go.sum
View File

@@ -46,6 +46,7 @@ github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7O
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
github.com/Azure/go-autorest v10.8.1+incompatible h1:u0jVQf+a6k6x8A+sT60l6EY9XZu+kHdnZVPAYqpVRo0=
github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest v11.2.8+incompatible h1:Q2feRPMlcfVcqz3pF87PJzkm5lZrL+x6BDtzhODzNJM=
github.com/Azure/go-autorest v11.2.8+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
github.com/Azure/go-autorest v12.2.0+incompatible h1:2Fxszbg492oAJrcvJlgyVaTqnQYRkxmEK6VPCLLVpBI=
github.com/Azure/go-autorest v12.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
@@ -237,7 +238,6 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc
github.com/coreos/pkg v0.0.0-20180108230652-97fdf19511ea/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/prometheus-operator v0.40.0/go.mod h1:QOoL5cVI3b1OHgpw8s+pH+Ok4AFRp2HOUvBpqs7UWcg=
github.com/coreos/prometheus-operator v0.41.1 h1:MEhY9syliPlQg+VlFRUfNodUEVXRXJ2n1pFG0aBp+mI=
github.com/coreos/prometheus-operator v0.41.1/go.mod h1:LhLfEBydppl7nvfEA1jIqlF3xJ9myHCnzrU+HHDxRd4=
github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
@@ -251,9 +251,10 @@ github.com/crossplane/crossplane-runtime v0.8.0/go.mod h1:gNY/21MLBaz5KNP7hmfXbB
github.com/crossplane/crossplane-runtime v0.9.0 h1:K6/tLhXKzhsEUUddTvEWWnQLLrawWyw1ptNK7NBDpDU=
github.com/crossplane/crossplane-runtime v0.9.0/go.mod h1:gNY/21MLBaz5KNP7hmfXbBXp8reYRbwY5B/97Kp4tgM=
github.com/crossplane/crossplane-tools v0.0.0-20200219001116-bb8b2ce46330/go.mod h1:C735A9X0x0lR8iGVOOxb49Mt70Ua4EM2b7PGaRPBLd4=
github.com/crossplane/oam-kubernetes-runtime v0.0.7/go.mod h1:f5xqmo0B2WtaOTZh8jhP+0f0XuzqhJG2xRtxfMZR3jA=
github.com/crossplane/oam-kubernetes-runtime v0.0.9 h1:cZMT7p1jZ6MsJqAuzVIZwvOxVdD+PGEQgCYHHuwR7Pc=
github.com/crossplane/oam-kubernetes-runtime v0.0.9/go.mod h1:f5xqmo0B2WtaOTZh8jhP+0f0XuzqhJG2xRtxfMZR3jA=
github.com/crossplane/oam-kubernetes-runtime v0.1.1-0.20200909070723-78b84f2c4799 h1:424LLFb7C8Qvy3wFZZ7HzmawlCeF32PNRTXXK5rKOk0=
github.com/crossplane/oam-kubernetes-runtime v0.1.1-0.20200909070723-78b84f2c4799/go.mod h1:UZ4eXkl/e4lKrAhK81Pz1sR90wqeuE9PgdwVXr8kDgI=
github.com/cyphar/filepath-securejoin v0.2.2 h1:jCwT2GTP+PY5nBz3c/YL5PAIbusElVrPujOBSCj8xRg=
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
github.com/dave/jennifer v1.2.0/go.mod h1:fIb+770HOpJ2fmN9EPPKOqm1vMGhB+TwXKMZhrIygKg=
@@ -874,7 +875,6 @@ github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM=
github.com/oam-dev/catalog/traits/metricstrait v0.0.0-20200826071236-d96c1d64e221/go.mod h1:mpYuuccw79F/yddIODjdDCzUyvXNnuksPWVx/z5vZBw=
github.com/oam-dev/stern v1.13.0-alpha h1:EVjM8Qvh6LssB6t4RZrjf9DtCq1cz+/cy6OF7fpy9wk=
github.com/oam-dev/stern v1.13.0-alpha/go.mod h1:AOkvfFUv0Arz7GBi0jz7S0Jsu4K/kdvSjNsnRt1+BIg=
github.com/oam-dev/trait-injector v0.0.0-20200331033130-0a27b176ffc4 h1:fc41JCTc9w140pE/WPtpmL3uiCjC3DEfzbtvHT6h6xY=

View File

@@ -19,6 +19,7 @@ func NewAppsCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
}
cmd.AddCommand(NewAppListCommand(c, ioStreams),
NewAppStatusCommand(c, ioStreams),
NewDeleteCommand(c, ioStreams),
NewAppShowCommand(ioStreams),
NewRunCommand(c, ioStreams))

View File

@@ -2,10 +2,14 @@ package cmd
import (
"context"
"fmt"
"os"
"strings"
"time"
"github.com/crossplane/oam-kubernetes-runtime/apis/core/v1alpha2"
"github.com/fatih/color"
"github.com/gosuri/uitable"
"github.com/cloud-native-application/rudrx/pkg/application"
@@ -16,12 +20,50 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
)
func NewCompStatusCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
// HealthStatus represents health status strings.
type HealthStatus = v1alpha2.HealthStatus
const (
// StatusNotFound means there's no health check info returned from the scope.
StatusNotFound HealthStatus = "NOT DIAGNOSED"
)
const (
// StatusHealthy represents healthy status.
StatusHealthy = v1alpha2.StatusHealthy
// StatusUnhealthy represents unhealthy status.
StatusUnhealthy = v1alpha2.StatusUnhealthy
// StatusUnknown represents unknown status.
StatusUnknown = v1alpha2.StatusUnknown
)
// WorkloadHealthCondition holds health status of any resource
type WorkloadHealthCondition = v1alpha2.WorkloadHealthCondition
// ScopeHealthCondition holds health condition of a scope
type ScopeHealthCondition = v1alpha2.ScopeHealthCondition
const (
firstElemPrefix = `├─`
lastElemPrefix = `└─`
indent = " "
pipe = ``
)
var (
gray = color.New(color.FgHiBlack)
red = color.New(color.FgRed)
green = color.New(color.FgGreen)
yellow = color.New(color.FgYellow)
white = color.New(color.Bold, color.FgWhite)
)
func NewAppStatusCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
ctx := context.Background()
cmd := &cobra.Command{
Use: "status <APPLICATION-NAME>",
Short: "get status of an application",
Long: "get status of an application, including its workload and trait",
Long: "get status of an application, including workloads and traits of each components.",
Example: `vela status <APPLICATION-NAME>`,
RunE: func(cmd *cobra.Command, args []string) error {
argsLength := len(args)
@@ -29,6 +71,119 @@ func NewCompStatusCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Comm
ioStreams.Errorf("Hint: please specify an application")
os.Exit(1)
}
appName := args[0]
env, err := GetEnv(cmd)
if err != nil {
ioStreams.Errorf("Error: failed to get Env: %s", err)
return err
}
newClient, err := client.New(c.Config, client.Options{Scheme: c.Schema})
if err != nil {
return err
}
return printAppStatus(ctx, newClient, ioStreams, appName, env)
},
Annotations: map[string]string{
types.TagCommandType: types.TypeApp,
},
}
cmd.SetOut(ioStreams.Out)
return cmd
}
func printAppStatus(ctx context.Context, c client.Client, ioStreams cmdutil.IOStreams, appName string, env *types.EnvMeta) error {
app, err := application.Load(env.Name, appName)
if err != nil {
return err
}
namespace := env.Name
tbl := uitable.New()
tbl.Separator = " "
tbl.AddRow(
white.Sprint("NAMESPCAE"),
white.Sprint("NAME"),
white.Sprint("INFO"))
tbl.AddRow(
namespace,
fmt.Sprintf("%s/%s",
"Application",
appName))
components := app.GetComponents()
// get a map coantaining all workloads health condition
wlConditionsMap, err := getWorkloadHealthConditions(ctx, c, app, namespace)
if err != nil {
return err
}
for cIndex, compName := range components {
var cPrefix string
switch cIndex {
case len(components) - 1:
cPrefix = lastElemPrefix
default:
cPrefix = firstElemPrefix
}
wlHealthCondition := wlConditionsMap[compName]
wlHealthStatus := wlHealthCondition.HealthStatus
healthColor := getHealthStatusColor(wlHealthStatus)
// print component info
tbl.AddRow("",
fmt.Sprintf("%s%s/%s",
gray.Sprint(printPrefix(cPrefix)),
"Component",
compName),
healthColor.Sprintf("%s %s", wlHealthStatus, wlHealthCondition.Diagnosis))
}
ioStreams.Info(tbl)
return nil
}
// map componentName <=> WorkloadHealthCondition
func getWorkloadHealthConditions(ctx context.Context, c client.Client, app *application.Application, ns string) (map[string]*WorkloadHealthCondition, error) {
hs := &v1alpha2.HealthScope{}
// only use default health scope
hsName := application.FormatDefaultHealthScopeName(app.Name)
if err := c.Get(ctx, client.ObjectKey{Namespace: ns, Name: hsName}, hs); err != nil {
return nil, err
}
wlConditions := hs.Status.WorkloadHealthConditions
r := map[string]*WorkloadHealthCondition{}
components := app.GetComponents()
for _, compName := range components {
for _, wlhc := range wlConditions {
if wlhc.ComponentName == compName {
r[compName] = wlhc
break
}
}
if r[compName] == nil {
r[compName] = &WorkloadHealthCondition{
HealthStatus: StatusNotFound,
}
}
}
return r, nil
}
func NewCompStatusCommand(c types.Args, ioStreams cmdutil.IOStreams) *cobra.Command {
ctx := context.Background()
cmd := &cobra.Command{
Use: "status <COMPONENT-NAME>",
Short: "get status of a component",
Long: "get status of a component, including its workload and health status",
Example: `vela comp status <COMPONENT-NAME>`,
RunE: func(cmd *cobra.Command, args []string) error {
argsLength := len(args)
if argsLength == 0 {
ioStreams.Errorf("Hint: please specify a component")
os.Exit(1)
}
compName := args[0]
env, err := GetEnv(cmd)
if err != nil {
@@ -67,16 +222,103 @@ func printComponentStatus(ctx context.Context, c client.Client, ioStreams cmduti
if err = c.Get(ctx, client.ObjectKey{Namespace: env.Namespace, Name: application.FormatDefaultHealthScopeName(app.Name)}, &health); err != nil {
return err
}
ioStreams.Info("Component Status:")
//TODO(wonderflow): add more information from health scope
ioStreams.Infof("\n %s \n\n", health.Status.Health)
ioStreams.Infof(white.Sprint("Component Status:\n"))
var wlhc *v1alpha2.WorkloadHealthCondition
for _, v := range health.Status.WorkloadHealthConditions {
if v.ComponentName == compName {
wlhc = v
}
}
var (
healthColor *color.Color
healthStatus HealthStatus
healthInfo string
workloadType string
)
if wlhc == nil {
workloadType = ""
healthStatus = StatusNotFound
healthInfo = fmt.Sprintf("%s %s", healthStatus, "Cannot get health status")
} else {
workloadType = wlhc.TargetWorkload.Kind
healthStatus = wlhc.HealthStatus
healthInfo = fmt.Sprintf("%s %s", healthStatus, wlhc.Diagnosis)
}
healthColor = getHealthStatusColor(healthStatus)
ioStreams.Infof("\tName: %s %s(type) %s \n", compName, workloadType, healthColor.Sprint(healthInfo))
traits, err := app.GetTraits(compName)
if err != nil {
return err
}
if len(traits) > 0 {
// print tree structure of Traits
tbl := uitable.New()
tbl.Separator = " "
traitNames := []string{}
for k := range traits {
traitNames = append(traitNames, k)
}
for tIndex, tName := range traitNames {
var tPrefix string
switch tIndex {
case len(traitNames) - 1:
tPrefix = lastElemPrefix
default:
tPrefix = firstElemPrefix
}
tbl.AddRow(
"\t",
fmt.Sprintf("%s%s%s/%s",
indent,
gray.Sprint(printPrefix(tPrefix)),
"Trait",
tName))
}
ioStreams.Info("\tTraits")
ioStreams.Info(tbl)
}
var appConfig v1alpha2.ApplicationConfiguration
if err = c.Get(ctx, client.ObjectKey{Namespace: env.Namespace, Name: app.Name}, &appConfig); err != nil {
return err
}
ioStreams.Infof("Last Deployment:\n\n")
ioStreams.Infof("\tCreated at:\t%v\n", appConfig.CreationTimestamp)
ioStreams.Infof("\tUpdated at:\t%v\n", app.UpdateTime.Format(time.RFC3339))
ioStreams.Infof(white.Sprint("\nLast Deployment:\n"))
ioStreams.Infof("\tCreated at: %v\n", appConfig.CreationTimestamp)
ioStreams.Infof("\tUpdated at: %v\n", app.UpdateTime.Format(time.RFC3339))
return nil
}
func printPrefix(p string) string {
if strings.HasSuffix(p, firstElemPrefix) {
p = strings.Replace(p, firstElemPrefix, pipe, strings.Count(p, firstElemPrefix)-1)
} else {
p = strings.ReplaceAll(p, firstElemPrefix, pipe)
}
if strings.HasSuffix(p, lastElemPrefix) {
p = strings.Replace(p, lastElemPrefix, strings.Repeat(" ", len([]rune(lastElemPrefix))), strings.Count(p, lastElemPrefix)-1)
} else {
p = strings.ReplaceAll(p, lastElemPrefix, strings.Repeat(" ", len([]rune(lastElemPrefix))))
}
return p
}
func getHealthStatusColor(s HealthStatus) *color.Color {
var c *color.Color
switch s {
case StatusHealthy:
c = green
case StatusUnhealthy:
c = red
case StatusUnknown:
c = yellow
case StatusNotFound:
c = yellow
default:
c = red
}
return c
}

View File

@@ -38,4 +38,3 @@ func printWorkloadList(workloadList []types.Capability, ioStreams cmdutil.IOStre
ioStreams.Info(table.String())
return nil
}