From 52bb3dd7b3238aa39129236c1d73b05bd7eb88a1 Mon Sep 17 00:00:00 2001 From: Shuyang Wu Date: Thu, 17 Dec 2020 20:17:02 +0800 Subject: [PATCH] feat: chaosctl for print debug info (#1074) Signed-off-by: yiyiyimu --- .github/ISSUE_TEMPLATE/bug-report.md | 4 + .github/ISSUE_TEMPLATE/question.md | 6 +- Makefile | 4 + cmd/chaosctl/main.go | 20 + go.mod | 9 +- go.sum | 117 +++++- images/chaos-daemon/Dockerfile | 4 +- pkg/chaosctl/README.md | 33 ++ pkg/chaosctl/cmd/completion.go | 88 ++++ pkg/chaosctl/cmd/debug.go | 207 ++++++++++ pkg/chaosctl/cmd/logs.go | 119 ++++++ pkg/chaosctl/cmd/root.go | 48 +++ pkg/chaosctl/common/common.go | 389 ++++++++++++++++++ pkg/chaosctl/common/common_test.go | 208 ++++++++++ pkg/chaosctl/common/exec.go | 103 +++++ pkg/chaosctl/debug/iochaos/iochaos.go | 78 ++++ .../debug/networkchaos/networkchaos.go | 158 +++++++ pkg/chaosctl/debug/stresschaos/stresschaos.go | 168 ++++++++ .../util => pkg}/portforward/portforward.go | 14 +- pkg/utils/chaosdaemon.go | 19 + pkg/utils/grpc.go | 7 +- pkg/utils/selector.go | 9 +- test/e2e/chaos/basic.go | 5 +- 23 files changed, 1791 insertions(+), 26 deletions(-) create mode 100644 cmd/chaosctl/main.go create mode 100644 pkg/chaosctl/README.md create mode 100644 pkg/chaosctl/cmd/completion.go create mode 100644 pkg/chaosctl/cmd/debug.go create mode 100644 pkg/chaosctl/cmd/logs.go create mode 100644 pkg/chaosctl/cmd/root.go create mode 100644 pkg/chaosctl/common/common.go create mode 100644 pkg/chaosctl/common/common_test.go create mode 100644 pkg/chaosctl/common/exec.go create mode 100644 pkg/chaosctl/debug/iochaos/iochaos.go create mode 100644 pkg/chaosctl/debug/networkchaos/networkchaos.go create mode 100644 pkg/chaosctl/debug/stresschaos/stresschaos.go rename {test/e2e/util => pkg}/portforward/portforward.go (94%) diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index 9c7eee462c..e59a3e6c4c 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -15,3 +15,7 @@ about: Something isn't working as expected **What did you expect to see?** **What did you see instead?** + +**Output of chaosctl** + + diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index 57c4617d8f..614b7ab7aa 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -12,4 +12,8 @@ Before asking a question, make sure you have: - Searched open and closed [GitHub issues](https://github.com/chaos-mesh/chaos-mesh/issues?utf8=%E2%9C%93&q=is%3Aissue) - Read the documentation: - [Chaos Mesh Readme](https://github.com/chaos-mesh/chaos-mesh) - - [Chaos Mesh Doc](https://github.com/chaos-mesh/chaos-mesh/tree/master/doc) + - [Chaos Mesh Doc](https://chaos-mesh.org/docs) + +**Output of chaosctl** + + diff --git a/Makefile b/Makefile index ec13b81a31..5e092a8924 100644 --- a/Makefile +++ b/Makefile @@ -130,6 +130,10 @@ binary: chaosdaemon manager chaosfs chaos-dashboard bin/pause watchmaker: $(CGOENV) go build -ldflags '$(LDFLAGS)' -o bin/watchmaker ./cmd/watchmaker/... +# Build chaosctl +chaosctl: + $(GO) build -ldflags '$(LDFLAGS)' -o bin/chaosctl ./cmd/chaosctl/*.go + # Run against the configured Kubernetes cluster in ~/.kube/config run: generate fmt vet manifests $(GO) run ./cmd/controller-manager/main.go diff --git a/cmd/chaosctl/main.go b/cmd/chaosctl/main.go new file mode 100644 index 0000000000..de59fb17fd --- /dev/null +++ b/cmd/chaosctl/main.go @@ -0,0 +1,20 @@ +// Copyright 2020 Chaos Mesh Authors. +// +// 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, +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import "github.com/chaos-mesh/chaos-mesh/pkg/chaosctl/cmd" + +func main() { + cmd.Execute() +} diff --git a/go.mod b/go.mod index 5cfa0fd88b..62339afe08 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,10 @@ module github.com/chaos-mesh/chaos-mesh require ( + code.cloudfoundry.org/bytefmt v0.0.0-20200131002437-cf55d5288a48 + github.com/DATA-DOG/go-sqlmock v1.5.0 + github.com/bxcodec/faker v2.0.1+incompatible + github.com/chaos-mesh/k8s_dns_chaos v0.0.0-20200922120555-7ced93637075 github.com/containerd/cgroups v0.0.0-20200404012852-53ba5634dc0f github.com/containerd/containerd v1.2.3 github.com/containerd/continuity v0.0.0-20200107194136-26c1120b8d41 // indirect @@ -12,6 +16,7 @@ require ( github.com/docker/go-connections v0.4.0 // indirect github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect github.com/ethercflow/hookfs v0.3.0 + github.com/fatih/color v1.9.0 github.com/ghodss/yaml v1.0.0 github.com/gin-gonic/gin v1.6.3 github.com/go-logr/logr v0.1.0 @@ -20,7 +25,6 @@ require ( github.com/gogo/googleapis v1.3.2 // indirect github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect github.com/golang/protobuf v1.4.2 - github.com/gorilla/websocket v1.4.1 // indirect github.com/grpc-ecosystem/go-grpc-middleware v1.2.0 github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 github.com/grpc-ecosystem/grpc-gateway v1.14.1 // indirect @@ -46,7 +50,8 @@ require ( github.com/shirou/gopsutil v0.0.0-20180427012116-c95755e4bcd7 github.com/shurcooL/httpfs v0.0.0-20190707220628-8d4bc4ba7749 // indirect github.com/shurcooL/vfsgen v0.0.0-20181202132449-6a9ea43bcacd - github.com/spf13/cobra v0.0.6 // indirect + github.com/spf13/cobra v1.1.1 + github.com/stretchr/testify v1.4.0 github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 github.com/swaggo/gin-swagger v1.2.0 github.com/swaggo/swag v1.6.7 diff --git a/go.sum b/go.sum index 20918ea38a..23bb398068 100644 --- a/go.sum +++ b/go.sum @@ -3,6 +3,19 @@ bitbucket.org/bertimus9/systemstat v0.0.0-20180207000608-0eeff89b0690/go.mod h1: cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.38.0 h1:ROfEUZz+Gh5pa62DJWXSaonyu3StP6EA6lPEXPI6mCo= cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3 h1:AVXDdKsrtX33oR9fbCMu/+c1o8Ofjq6Ku/MInaLVg5Y= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +code.cloudfoundry.org/bytefmt v0.0.0-20200131002437-cf55d5288a48 h1:/EMHruHCFXR9xClkGV/t0rmHrdhX4+trQUcBqjwc9xE= +code.cloudfoundry.org/bytefmt v0.0.0-20200131002437-cf55d5288a48/go.mod h1:wN/zk7mhREp/oviagqUXY3EwuHhWyOvAdsn5Y4CzOrc= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/Azure/azure-sdk-for-go v35.0.0+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= @@ -24,10 +37,12 @@ github.com/Azure/go-autorest/tracing v0.5.0/go.mod h1:r/s2XiOKccPW3HrqB+W0TQzfbt github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= github.com/GoogleCloudPlatform/k8s-cloud-provider v0.0.0-20190822182118-27a4ced34534/go.mod h1:iroGtC8B3tQiqtds1l+mgk/BBOrxbqjH+eUfFQYRc14= github.com/JeffAshton/win_pdh v0.0.0-20161109143554-76bb4ee9f0ab/go.mod h1:3VYc5hodBMJ5+l/7J4xAyMeuM2PNuepvHlGs8yilUCA= github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd h1:sjQovDkwrZp8u+gxLtPgKGjk5hCxuy2hrRejBTA9xFU= github.com/MakeNowJust/heredoc v0.0.0-20170808103936-bb23615498cd/go.mod h1:64YHyfSL2R96J44Nlwm39UHepQbyR5q10x7iYa1ks2E= github.com/Microsoft/go-winio v0.4.11 h1:zoIOcVf0xPN1tnMVbTtEdI+P8OofVk3NObnwOQ6nK2Q= github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= @@ -57,6 +72,8 @@ github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYU github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/asaskevich/govalidator v0.0.0-20180720115003-f9ffefc3facf/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= github.com/auth0/go-jwt-middleware v0.0.0-20170425171159-5493cabe49f7/go.mod h1:LWMyo4iOLWXHGdBki7NIht1kHru/0wM179h+d3g8ATM= @@ -72,17 +89,22 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bifurcation/mint v0.0.0-20180715133206-93c51c6ce115/go.mod h1:zVt7zX3K/aDCk9Tj+VM7YymsX66ERvzCJzw8rFCX2JU= +github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84= github.com/blang/semver v3.5.0+incompatible h1:CGxCgetQ64DKk7rdZ++Vfnb1+ogGNnB17OJKJXD2Cfs= github.com/blang/semver v3.5.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/boltdb/bolt v1.3.1/go.mod h1:clJnj/oiGkjum5o1McbSZDSLxVThjynRyGBgiAx27Ps= github.com/bradfitz/go-smtpd v0.0.0-20170404230938-deb6d6237625/go.mod h1:HYsPBTaaSFSlLx/70C2HPIMNZpVV8+vt/A+FMnYP11g= github.com/bruth/assert v0.0.0-20130823105606-de420fa3b72e/go.mod h1:MT8TZkfLPRir91B19sXF7pmKBma+n6ecyjbqgXabchs= +github.com/bxcodec/faker v2.0.1+incompatible/go.mod h1:BNzfpVdTwnFJ6GtfYTcQu6l6rHShT+veBxNCnjCx5XM= github.com/caddyserver/caddy v1.0.3/go.mod h1:G+ouvOY32gENkJC+jhgl62TyhvqEsFaDiZ4uw0RzP1E= github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/cespare/prettybench v0.0.0-20150116022406-03b8cfe5406c/go.mod h1:Xe6ZsFhtM8HrDku0pxJ3/Lr51rwykrzgFwpmTzleatY= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5 h1:7aWHqerlJ41y6FOsEUvknqgXnGmJyJSbjhAWq5pO4F8= github.com/chai2010/gettext-go v0.0.0-20160711120539-c6fed771bfd5/go.mod h1:/iP1qXHoty45bqomnu2LM+VVyAEdWN+vtSHGlQgyxbw= +github.com/chaos-mesh/k8s_dns_chaos v0.0.0-20200922120555-7ced93637075/go.mod h1:CB8grXv5pqxLgiI0HSZxyyykmDRekpd5M7fz+NlOdMs= github.com/checkpoint-restore/go-criu v0.0.0-20190109184317-bdb7599cd87b/go.mod h1:TrMrLQfeENAPYPRsJuq3jsqdlRh3lvi6trTZJG8+tho= github.com/cheekybits/genny v0.0.0-20170328200008-9127e812e1e9/go.mod h1:+tQajlRqAUrPI7DOSpB0XAqZYtQakVtB7wXkRAgjxjQ= github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= @@ -111,6 +133,7 @@ github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ github.com/coredns/corefile-migration v1.0.4/go.mod h1:OFwBp/Wc9dJt5cAZzHWMNhK1r5L0p0jDwIBc6j8NC8E= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= +github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -179,6 +202,7 @@ github.com/euank/go-kmsg-parser v2.0.0+incompatible/go.mod h1:MhmAMZ8V4CYH4ybgdR github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d h1:105gxyaGwCFad8crR9dcMQWvV9Hvulu6hwUh4tWPJnM= github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d/go.mod h1:ZZMPRZwes7CROmyNKgQzC3XPs6L/G2EJLHddWejkmf4= github.com/fatih/camelcase v1.0.0 h1:hxNvNX/xYBp0ovncs8WyWZrOrpBNub/JfaMvbURyft8= github.com/fatih/camelcase v1.0.0/go.mod h1:yN2Sb0lFhZJUdVvtELVWefmrXpuZESvPmqwoZc+/fpc= @@ -211,6 +235,7 @@ github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0 github.com/go-acme/lego v2.5.0+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG94USbYxYrZfTkIn0M= github.com/go-bindata/go-bindata v3.1.1+incompatible/go.mod h1:xK8Dsgwmeed+BBsSy2XTopBn/8uK2HWuGSnA11C3Joo= github.com/go-critic/go-critic v0.3.5-0.20190526074819-1df300866540/go.mod h1:+sE8vrLDS2M0pZkBk0wy6+nLdKexVDrl/jBqQOTDThA= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-lintpack/lintpack v0.5.2/go.mod h1:NwZuYi2nUHho8XEIZ6SIxihrnPoqBTDqfpXvXAN0sXM= @@ -328,6 +353,9 @@ github.com/golang/mock v1.0.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfb github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -378,25 +406,28 @@ github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= github.com/googleapis/gnostic v0.3.1 h1:WeAefnSUHlBb0iJKwxFDZdbfGwkd7xRNuV+IpXMJhYk= github.com/googleapis/gnostic v0.3.1/go.mod h1:on+2t9HRStVgn95RSsFWFz+6Q0Snyqv1awfrALZdbtU= github.com/gophercloud/gophercloud v0.1.0 h1:P/nh25+rzXouhytV2pUHBb65fnds26Ghl8/391+sT5o= github.com/gophercloud/gophercloud v0.1.0/go.mod h1:vxM41WHh5uqHVBMZHzuwNOHh8XEoIEcSTewFxm1c5g8= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.7.0 h1:tOSd0UKHQd6urX6ApfOn4XdBMY6Sh1MfxV3kmaazO+U= github.com/gorilla/mux v1.7.0/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= -github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7 h1:pdN6V1QBWetyv/0+wjACpqVH+eVULgEjkurDLq3goeM= @@ -413,11 +444,22 @@ github.com/grpc-ecosystem/grpc-gateway v1.14.1 h1:YuM9SXYy583fxvSOkzCDyBPCtY+/IM github.com/grpc-ecosystem/grpc-gateway v1.14.1/go.mod h1:6CwZWGDSPRJidgKAtJVvND6soZe6fT7iteq8wDPdhb0= github.com/hanwen/go-fuse v0.0.0-20190111173210-425e8d5301f6 h1:tS7rIYOq1UkeH2eCa0ShovjqYa/7+NYAIxspqA9gkOU= github.com/hanwen/go-fuse v0.0.0-20190111173210-425e8d5301f6/go.mod h1:unqXarDXqzAk0rt98O2tVndEPIpUgLD9+rwFisZH3Ok= +github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q= +github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= github.com/hashicorp/golang-lru v0.0.0-20180201235237-0fb14efe8c47/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -426,6 +468,10 @@ github.com/hashicorp/golang-lru v0.5.3/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uG github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= github.com/heketi/heketi v9.0.1-0.20190917153846-c2e2a4ab7ab9+incompatible/go.mod h1:bB9ly3RchcQqsQ9CpyaQwvva7RS5ytVoSoholZQON6o= github.com/heketi/tests v0.0.0-20151005000721-f3775cbcefd6/go.mod h1:xGMAM8JLi7UkZt1i4FQeQy0R2T8GLUwQhOP5M1gBhy4= github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= @@ -456,6 +502,7 @@ github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/u github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/karrick/godirwalk v1.7.5/go.mod h1:2c9FRhkDxdIbgkOnCEvnSWs71Bhugbl46shStcFDJ34= @@ -538,15 +585,22 @@ github.com/mgechev/dots v0.0.0-20190921121421-c36f7dcfbb81/go.mod h1:KQ7+USdGKfp github.com/mgechev/revive v1.0.2-0.20200225072153-6219ca02fffb h1:EabZ4SffLYB6FcYN8VDMk1TCMahjhEhEqKcOxBNbPmY= github.com/mgechev/revive v1.0.2-0.20200225072153-6219ca02fffb/go.mod h1:E9j8UNyHeYo/uUXIIUOAehxf5B69UwZ5u3qj7wEn8J0= github.com/mholt/certmagic v0.6.2-0.20190624175158-6a42ef9fe8c2/go.mod h1:g4cOPxcjV0oFq3qwpjSA30LReKD8AoIfwAY9VvG35NY= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.3/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/miekg/dns v1.1.4/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mindprince/gonvml v0.0.0-20190828220739-9ebdce4bb989/go.mod h1:2eu9pRWp8mo84xCg6KswZ+USQHjwgRhNp06sozOdsTY= github.com/mistifyio/go-zfs v2.1.1+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-ps v0.0.0-20170309133038-4fdf99ab2936/go.mod h1:r1VsdOzOPt1ZSrGZWFoNhsAedKnEd6r9Np1+5blZCWk= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= @@ -604,6 +658,7 @@ github.com/opencontainers/runtime-spec v1.0.2 h1:UfAcuLBJB9Coz72x1hgl8O5RVzTdNia github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= github.com/opencontainers/selinux v1.3.1-0.20190929122143-5215b1806f52/go.mod h1:+BLncwf63G4dgOzykXAxcmnFlUaOlkDdmw/CqsW6pjs= github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.1.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= @@ -630,6 +685,7 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= github.com/pquerna/ffjson v0.0.0-20180717144149-af8b230fcd20/go.mod h1:YARuvh7BUWHNhzDq2OM5tzR2RiCcN2D7sapiKyCel/M= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= @@ -669,8 +725,10 @@ github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNue github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v0.0.0-20170128012129-256dc444b735/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/sergi/go-diff v1.0.1-0.20180205163309-da645544ed44 h1:tB9NOR21++IjLyVx3/PCPhWMwqGNCMQEH96A6dMZ/gc= @@ -694,8 +752,11 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/sourcegraph/go-diff v0.5.1/go.mod h1:j2dHj3m8aZgQO8lMTcTnBcXkRRRqi34cd2MNlA9u1mE= @@ -711,8 +772,8 @@ github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKv github.com/spf13/cobra v0.0.2/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/cobra v0.0.6 h1:breEStsVwemnKh2/s6gMvSdMEkwW0sK8vGStnlVBMCs= -github.com/spf13/cobra v0.0.6/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= +github.com/spf13/cobra v1.1.1 h1:KfztREH0tPxJJ+geloSLaAkaPkr4ki2Er5quFV1TDo4= +github.com/spf13/cobra v1.1.1/go.mod h1:WnodtKOvamDL/PwE2M4iKs8aMDBZ5Q5klgD3qfVJQMI= github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= @@ -725,8 +786,8 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.0.2/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= -github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= +github.com/spf13/viper v1.7.0 h1:xVKxvI7ouOI5I+U9s2eeiUfMaWBVoXA3AWskkrqK0VM= +github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg= github.com/storageos/go-api v0.0.0-20180912212459-343b3eff91fc/go.mod h1:ZrLn+e0ZuF3Y65PNF6dIwbJPZqfmtCXxFm9ckv0agOY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -737,6 +798,8 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= +github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14 h1:PyYN9JH5jY9j6av01SpfRMb+1DWg/i3MbGOKPxJ2wjM= github.com/swaggo/files v0.0.0-20190704085106-630677cd5c14/go.mod h1:gxQT6pBGRuIGunNf/+tSOB5OHvguWi8Tbt82WOkf35E= github.com/swaggo/gin-swagger v1.2.0 h1:YskZXEiv51fjOMTsXrOetAjrMDfFaXD79PEoQBOe2W0= @@ -795,6 +858,7 @@ go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qL go.mongodb.org/mongo-driver v1.1.1/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.2/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= @@ -823,6 +887,7 @@ golang.org/x/build v0.0.0-20190927031335-2835ba2e683f/go.mod h1:fYw7AShPAhGMdXqA golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180426230345-b49d69b5da94/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190123085648-057139ce5d2b/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190211182817-74369b46fc67/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -833,6 +898,7 @@ golang.org/x/crypto v0.0.0-20190320223903-b7391e95e576/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190424203555-c05e17bb3b2d/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190617133340-57b3e21c3d56/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -841,16 +907,24 @@ golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd h1:GGJVjV8waZKRHrgwvtH66z golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190312203227-4b39c73a6495/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0 h1:KU7oHjnv3XNWfa5COkzUifxZmxp1TyI7ImMXqFxLwvQ= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= @@ -861,6 +935,7 @@ golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181005035420-146acd28ed58/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181102091132-c10e9556a7bc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -872,9 +947,10 @@ golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190320064053-1272bf9dcd53/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190328230028-74de082e2cca/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190502183928-7f726cade0ab/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190611141213-3f473d35a33a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -901,9 +977,11 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20171026204733-164713f0dfce/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180117170059-2c42eef0765b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -918,10 +996,13 @@ golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190321052220-f7bb7a8bee54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190502175342-a43fa875dd82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190610200419-93c9922d18ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -963,19 +1044,26 @@ golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3 golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190521203540-521d6ed310dd/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190606050223-4d9ae51c2468/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190611222205-d73e1c7e250b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190617190820-da514acc4774/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190909030654-5b82db07426d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20190920225731-5eefd052ad72/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191030062658-86caa796c7ab/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191107010934-f79515f33823/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191114200427-caa0b0f7d508/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= @@ -994,16 +1082,27 @@ gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6d gonum.org/v1/netlib v0.0.0-20190331212654-76723241ea4e/go.mod h1:kS+toOQn6AQKjmKJ7gzohV1XkqsFehRA2FbsbkopSuQ= google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= google.golang.org/api v0.6.1-0.20190607001116-5213b8090861/go.mod h1:btoxGiFvQNVUZQ8W08zLtrVS08CNpINPEfxXxgJL1Q4= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.5.0 h1:KxkO13IPW4Lslp2bz+KHP2E3gtFlrIGNThxkZQ3g+4c= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c h1:hrpEMCZ2O7DR5gC1n2AJGVhrwiEjOi35+jxtIuZpTMo= google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a h1:Ob5/580gVHBJZgXnff1cZDbG+xLtMVE5mDRTe+nIsX4= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -1032,6 +1131,8 @@ gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8 gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno= +gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/mcuadros/go-syslog.v2 v2.2.1/go.mod h1:l5LPIyOOyIdQquNg+oU6Z3524YwrcqEm0aKH+5zpt2U= gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= @@ -1105,6 +1206,7 @@ k8s.io/kubelet v0.17.0/go.mod h1:e/JBCxucKuEV6JO6zYW+e72ib9eMsGO2Fah3iT5tiiI= k8s.io/kubernetes v1.17.2 h1:g1UFZqFQsYx88xMUks4PKC6tsNcekxe0v06fcVGRwVE= k8s.io/kubernetes v1.17.2/go.mod h1:NbNV+69yL3eKiKDJ+ZEjqOplN3BFXKBeunzkoOy8WLo= k8s.io/legacy-cloud-providers v0.17.0/go.mod h1:DdzaepJ3RtRy+e5YhNtrCYwlgyK87j/5+Yfp0L9Syp8= +k8s.io/metrics v0.17.0 h1:n6FH2RmlE7yJCvGKczQNpwHQ1DbCw5SevEfqII9EeIo= k8s.io/metrics v0.17.0/go.mod h1:EH1D3YAwN6d7bMelrElnLhLg72l/ERStyv2SIQVt6Do= k8s.io/repo-infra v0.0.1-alpha.1/go.mod h1:wO1t9WaB99V80ljbeENTnayuEEwNZt7gECYh/CEyOJ8= k8s.io/sample-apiserver v0.17.0/go.mod h1:SAkguNIe/gJik7VlkFu62oGlWltW3c0mAP9WQYUMEJo= @@ -1120,6 +1222,7 @@ modernc.org/xc v1.0.0/go.mod h1:mRNCo0bvLjGhHO9WsyuKVU4q0ceiDDDoEeWDJHrNx8I= mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= mvdan.cc/unparam v0.0.0-20190209190245-fbb59629db34/go.mod h1:H6SUd1XjIs+qQCyskXg5OFSrilMRUkD8ePJpHKDPaeY= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= sigs.k8s.io/controller-runtime v0.4.0 h1:wATM6/m+3w8lj8FXNaO6Fs/rq/vqoOjO1Q116Z9NPsg= sigs.k8s.io/controller-runtime v0.4.0/go.mod h1:ApC79lpY3PHW9xj/w9pj+lYkLgwAAUZwfXkME1Lajns= sigs.k8s.io/controller-tools v0.2.5 h1:kH7HKWed9XO42OTxyhUtqyImiefdZV2Q9Jbrytvhf18= diff --git a/images/chaos-daemon/Dockerfile b/images/chaos-daemon/Dockerfile index dc451cf8e8..4911482f78 100644 --- a/images/chaos-daemon/Dockerfile +++ b/images/chaos-daemon/Dockerfile @@ -6,7 +6,7 @@ ARG HTTP_PROXY ENV http_proxy $HTTP_PROXY ENV https_proxy $HTTPS_PROXY -RUN apt-get update && apt-get install -y tzdata iptables ipset stress-ng iproute2 fuse util-linux && rm -rf /var/lib/apt/lists/* +RUN apt-get update && apt-get install -y tzdata iptables ipset stress-ng iproute2 fuse util-linux procps && rm -rf /var/lib/apt/lists/* RUN update-alternatives --set iptables /usr/sbin/iptables-legacy @@ -16,4 +16,4 @@ COPY --from=pingcap/chaos-binary /bin/chaos-daemon /usr/local/bin/chaos-daemon COPY --from=pingcap/chaos-binary /bin/toda /usr/local/bin/toda COPY --from=pingcap/chaos-binary /bin/nsexec /usr/local/bin/nsexec COPY --from=pingcap/chaos-binary /bin/libnsenter.so /usr/local/lib/libnsenter.so -COPY --from=pingcap/chaos-binary /bin/pause /usr/local/bin/pause \ No newline at end of file +COPY --from=pingcap/chaos-binary /bin/pause /usr/local/bin/pause diff --git a/pkg/chaosctl/README.md b/pkg/chaosctl/README.md new file mode 100644 index 0000000000..be8bebe551 --- /dev/null +++ b/pkg/chaosctl/README.md @@ -0,0 +1,33 @@ +# chaosctl + +Chaostl is a tool (currently) used to print debug info. Maintainers would make use of it to better provide their suggestions for issues. + +## How to build +```shell +cd $CHAOSMESH_DIR +make chaosctl +./bin/chaosctl --help #to see if build succeed. +``` +Chaoctl support shell autocompletion, which could save you some typing. Do `./bin/chaosctl completion -h` for detail. + +## How to use +**Debug** +`chaoctl debug` is used to print debug info of certain chaos. Currently, chaosctl support networkchaos, stresschaos and iochaos. +```shell +#to print info of each networkchaos +./bin/chaosctl debug networkchaos +#to print info of certain chaos in default namespace +./bin/chaosctl debug networkchaos CHAOSNAME +#to print info of each networkchaos in certain namespace +./bin/chaosctl debug networkchaos -n NAMESPACE +``` + +**Logs** +`chaoctl log` is used to easily print log from all chaos-mesh components, including controller-manager, chaos-daemon and chaos-dashboard. +```shell +# Default print all log of all chaosmesh components +chaosctl logs + +# to print 100 log lines for chaosmesh components in node NODENAME +chaosctl logs -t 100 -n NODENAME +``` diff --git a/pkg/chaosctl/cmd/completion.go b/pkg/chaosctl/cmd/completion.go new file mode 100644 index 0000000000..e65459f759 --- /dev/null +++ b/pkg/chaosctl/cmd/completion.go @@ -0,0 +1,88 @@ +// Copyright 2020 Chaos Mesh Authors. +// +// 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, +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "os" + + "github.com/spf13/cobra" +) + +// completionCmd represents the completion command +var completionCmd = &cobra.Command{ + Use: "completion [bash|zsh|fish|powershell]", + Short: "Generate completion script", + Long: `To load completions: + +Bash: + +$ source <(chaosctl completion bash) + +# To load completions for each session, execute once: +Linux: + $ ./bin/chaosctl completion bash > /etc/bash_completion.d/chaosctl +MacOS: + $ ./bin/chaosctl completion bash > /usr/local/etc/bash_completion.d/chaosctl + +Zsh: + +$ compdef _chaosctl ./bin/chaosctl + +# If shell completion is not already enabled in your environment you will need +# to enable it. You can execute the following once: + +$ echo "autoload -U compinit; compinit" >> ~/.zshrc + +# To load completions for each session, execute once: +$ ./bin/chaosctl completion zsh > "${fpath[1]}/_chaosctl" + +# You will need to start a new shell for this setup to take effect. + +Fish: + +$ ./bin/chaosctl completion fish | source + +# To load completions for each session, execute once: +$ ./bin/chaosctl completion fish > ~/.config/fish/completions/chaosctl.fish + +Powershell: + +PS> ./bin/chaosctl completion powershell | Out-String | Invoke-Expression + +# To load completions for every new session, run: +PS> ./bin/chaosctl completion powershell > chaosctl.ps1 +# and source this file from your powershell profile. +`, + DisableFlagsInUseLine: true, + ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, + Args: cobra.ExactValidArgs(1), + Run: func(cmd *cobra.Command, args []string) { + switch args[0] { + case "bash": + cmd.Root().GenBashCompletion(os.Stdout) + case "zsh": + cmd.Root().GenZshCompletion(os.Stdout) + case "fish": + cmd.Root().GenFishCompletion(os.Stdout, true) + // TODO: powershell completion is still not fully supported, see https://github.com/spf13/cobra/pull/1208 + // Need to update cobra version when this PR is merged + case "powershell": + cmd.Root().GenPowerShellCompletion(os.Stdout) + } + }, +} + +func init() { + rootCmd.AddCommand(completionCmd) +} diff --git a/pkg/chaosctl/cmd/debug.go b/pkg/chaosctl/cmd/debug.go new file mode 100644 index 0000000000..9154bffad7 --- /dev/null +++ b/pkg/chaosctl/cmd/debug.go @@ -0,0 +1,207 @@ +// Copyright 2020 Chaos Mesh Authors. +// +// 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, +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "context" + "fmt" + "log" + "strings" + + "github.com/spf13/cobra" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "sigs.k8s.io/controller-runtime/pkg/client" + + cm "github.com/chaos-mesh/chaos-mesh/pkg/chaosctl/common" + "github.com/chaos-mesh/chaos-mesh/pkg/chaosctl/debug/iochaos" + "github.com/chaos-mesh/chaos-mesh/pkg/chaosctl/debug/networkchaos" + "github.com/chaos-mesh/chaos-mesh/pkg/chaosctl/debug/stresschaos" +) + +type debugOptions struct { + namespace string +} + +type chaosT int + +const ( + networkChaos = "networkchaos" + stressChaos = "stresschaos" + ioChaos = "iochaos" +) + +func init() { + o := &debugOptions{} + + c, err := cm.InitClientSet() + if err != nil { + log.Fatal(err) + } + + debugCmd := &cobra.Command{ + Use: `debug (CHAOSTYPE) [-c CHAOSNAME] [-n NAMESPACE]`, + Short: `Print the debug information for certain chaos`, + Long: `Print the debug information for certain chaos. +Currently support networkchaos, stresschaos and iochaos. + +Examples: + # Return debug information from all networkchaos in default namespace + chaosctl debug networkchaos + + # Return debug information from certain networkchaos + chaosctl debug networkchaos CHAOSNAME -n NAMESPACE`, + ValidArgsFunction: noCompletions, + } + + // Need to separately support chaos-level completion, so split each chaos apart + networkCmd := &cobra.Command{ + Use: `networkchaos (CHAOSNAME) [-n NAMESPACE]`, + Short: `Print the debug information for certain network chaos`, + Long: `Print the debug information for certain network chaos`, + Run: func(cmd *cobra.Command, args []string) { + if err := o.Run("networkchaos", args, c); err != nil { + log.Fatal(err) + } + }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return listChaos("networkchaos", o.namespace, toComplete, c.CtrlCli) + }, + } + + stressCmd := &cobra.Command{ + Use: `stresschaos (CHAOSNAME) [-n NAMESPACE]`, + Short: `Print the debug information for certain stress chaos`, + Long: `Print the debug information for certain stress chaos`, + Run: func(cmd *cobra.Command, args []string) { + if err := o.Run("stresschaos", args, c); err != nil { + log.Fatal(err) + } + }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return listChaos("stresschaos", o.namespace, toComplete, c.CtrlCli) + }, + } + + ioCmd := &cobra.Command{ + Use: `iochaos (CHAOSNAME) [-n NAMESPACE]`, + Short: `Print the debug information for certain io chaos`, + Long: `Print the debug information for certain io chaos`, + Run: func(cmd *cobra.Command, args []string) { + if err := o.Run("iochaos", args, c); err != nil { + log.Fatal(err) + } + }, + ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + if len(args) != 0 { + return nil, cobra.ShellCompDirectiveNoFileComp + } + return listChaos("iochaos", o.namespace, toComplete, c.CtrlCli) + }, + } + + debugCmd.AddCommand(networkCmd) + debugCmd.AddCommand(stressCmd) + debugCmd.AddCommand(ioCmd) + + debugCmd.PersistentFlags().StringVarP(&o.namespace, "namespace", "n", "default", "namespace to find chaos") + err = debugCmd.RegisterFlagCompletionFunc("namespace", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return listNamespace(toComplete, c.KubeCli) + }) + if err != nil { + log.Fatal(err) + } + + rootCmd.AddCommand(debugCmd) +} + +// Run debug +func (o *debugOptions) Run(chaosType string, args []string, c *cm.ClientSet) error { + if len(args) > 1 { + return fmt.Errorf("Only one chaos could be specified") + } + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + chaosName := "" + if len(args) == 1 { + chaosName = args[0] + } + + chaosList, chaosNameList, err := cm.GetChaosList(ctx, chaosType, chaosName, o.namespace, c.CtrlCli) + if err != nil { + return fmt.Errorf(err.Error()) + } + var result []cm.ChaosResult + + for i, chaos := range chaosList { + var chaosResult cm.ChaosResult + chaosResult.Name = chaosNameList[i] + + var err error + switch chaosType { + case networkChaos: + err = networkchaos.Debug(ctx, chaos, c, &chaosResult) + case stressChaos: + err = stresschaos.Debug(ctx, chaos, c, &chaosResult) + case ioChaos: + err = iochaos.Debug(ctx, chaos, c, &chaosResult) + default: + return fmt.Errorf("chaos type not supported") + } + result = append(result, chaosResult) + if err != nil { + cm.PrintResult(result) + return err + } + } + cm.PrintResult(result) + return nil +} + +func listNamespace(toComplete string, c *kubernetes.Clientset) ([]string, cobra.ShellCompDirective) { + namespaces, err := c.CoreV1().Namespaces().List(v1.ListOptions{}) + if err != nil { + return nil, cobra.ShellCompDirectiveDefault + } + var ret []string + for _, ns := range namespaces.Items { + if strings.HasPrefix(ns.Name, toComplete) { + ret = append(ret, ns.Name) + } + } + return ret, cobra.ShellCompDirectiveNoFileComp +} + +func listChaos(chaosType string, namespace string, toComplete string, c client.Client) ([]string, cobra.ShellCompDirective) { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + _, chaosList, err := cm.GetChaosList(ctx, chaosType, "", namespace, c) + if err != nil { + return nil, cobra.ShellCompDirectiveDefault + } + var ret []string + for _, chaos := range chaosList { + if strings.HasPrefix(chaos, toComplete) { + ret = append(ret, chaos) + } + } + return ret, cobra.ShellCompDirectiveNoFileComp +} diff --git a/pkg/chaosctl/cmd/logs.go b/pkg/chaosctl/cmd/logs.go new file mode 100644 index 0000000000..26dff5c419 --- /dev/null +++ b/pkg/chaosctl/cmd/logs.go @@ -0,0 +1,119 @@ +// Copyright 2020 Chaos Mesh Authors. +// +// 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, +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "context" + "fmt" + "log" + "strings" + + "github.com/spf13/cobra" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + + "github.com/chaos-mesh/chaos-mesh/api/v1alpha1" + "github.com/chaos-mesh/chaos-mesh/controllers/config" + cm "github.com/chaos-mesh/chaos-mesh/pkg/chaosctl/common" + "github.com/chaos-mesh/chaos-mesh/pkg/utils" +) + +type logsOptions struct { + tail int64 + node string +} + +func init() { + o := &logsOptions{} + + c, err := cm.InitClientSet() + if err != nil { + log.Fatal(err) + } + + logsCmd := &cobra.Command{ + Use: `logs [-t LINE]`, + Short: `Print logs of controller-manager, chaos-daemon and chaos-dashboard`, + Long: `Print logs of controller-manager, chaos-daemon and chaos-dashboard, to provide debug information. + +Examples: + # Default print all log of all chaosmesh components + chaosctl logs + + # Print 100 log lines for chaosmesh components in node NODENAME + chaosctl logs -t 100 -n NODENAME`, + Run: func(cmd *cobra.Command, args []string) { + if err := o.Run(args, c); err != nil { + log.Fatal(err) + } + }, + ValidArgsFunction: noCompletions, + } + + logsCmd.Flags().Int64VarP(&o.tail, "tail", "t", -1, "Number of lines of recent log") + logsCmd.Flags().StringVarP(&o.node, "node", "n", "", "Number of lines of recent log") + err = logsCmd.RegisterFlagCompletionFunc("node", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return listNodes(toComplete, c.KubeCli) + }) + if err != nil { + log.Fatal(err) + } + + rootCmd.AddCommand(logsCmd) +} + +// Run logs +func (o *logsOptions) Run(args []string, c *cm.ClientSet) error { + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + + componentsNeeded := []string{"controller-manager", "chaos-daemon", "chaos-dashboard"} + for _, name := range componentsNeeded { + selector := v1alpha1.SelectorSpec{ + LabelSelectors: map[string]string{"app.kubernetes.io/component": name}, + } + if o.node != "" { + selector.Nodes = []string{o.node} + } + + components, err := utils.SelectPods(ctx, c.CtrlCli, nil, selector, config.ControllerCfg.ClusterScoped, config.ControllerCfg.TargetNamespace, config.ControllerCfg.AllowedNamespaces, config.ControllerCfg.IgnoredNamespaces) + if err != nil { + return fmt.Errorf("failed to SelectPods with: %s", err.Error()) + } + for _, comp := range components { + cm.PrettyPrint(fmt.Sprintf("[%s]", comp.Name), 0, "Cyan") + comLog, err := cm.Log(comp, o.tail, c.KubeCli) + if err != nil { + cm.PrettyPrint(err.Error(), 1, "Red") + } else { + cm.PrettyPrint(comLog, 1, "") + } + } + } + return nil +} + +func listNodes(toComplete string, c *kubernetes.Clientset) ([]string, cobra.ShellCompDirective) { + nodes, err := c.CoreV1().Nodes().List(v1.ListOptions{}) + if err != nil { + return nil, cobra.ShellCompDirectiveDefault + } + var ret []string + for _, ns := range nodes.Items { + if strings.HasPrefix(ns.Name, toComplete) { + ret = append(ret, ns.Name) + } + } + return ret, cobra.ShellCompDirectiveNoFileComp +} diff --git a/pkg/chaosctl/cmd/root.go b/pkg/chaosctl/cmd/root.go new file mode 100644 index 0000000000..a9ea4a77e9 --- /dev/null +++ b/pkg/chaosctl/cmd/root.go @@ -0,0 +1,48 @@ +// Copyright 2020 Chaos Mesh Authors. +// +// 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, +// See the License for the specific language governing permissions and +// limitations under the License. + +package cmd + +import ( + "fmt" + "os" + + "github.com/spf13/cobra" +) + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "chaosctl [command] [options]", + Short: "Interacting with chaos mesh", + Long: ` +Interacting with chaos mesh + + # show debug info + chaosctl debug networkchaos + + # show logs of all chaos-mesh componentes + chaosctl logs`, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func noCompletions(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { + return nil, cobra.ShellCompDirectiveNoFileComp +} diff --git a/pkg/chaosctl/common/common.go b/pkg/chaosctl/common/common.go new file mode 100644 index 0000000000..f7c0957b28 --- /dev/null +++ b/pkg/chaosctl/common/common.go @@ -0,0 +1,389 @@ +// Copyright 2019 Chaos Mesh Authors. +// +// 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, +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "log" + "regexp" + "strings" + "time" + + "github.com/fatih/color" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/kubernetes" + clientgoscheme "k8s.io/client-go/kubernetes/scheme" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/client/config" + + "github.com/chaos-mesh/chaos-mesh/api/v1alpha1" + ctrlconfig "github.com/chaos-mesh/chaos-mesh/controllers/config" + "github.com/chaos-mesh/chaos-mesh/pkg/chaosdaemon/pb" + "github.com/chaos-mesh/chaos-mesh/pkg/portforward" + "github.com/chaos-mesh/chaos-mesh/pkg/utils" + e2econfig "github.com/chaos-mesh/chaos-mesh/test/e2e/config" +) + +var ( + colorFunc = map[string]func(string, ...interface{}){ + "Blue": color.Blue, + "Red": color.Red, + "Green": color.Green, + "Cyan": color.Cyan, + } + scheme = runtime.NewScheme() +) + +// ClientSet contains two different clients +type ClientSet struct { + CtrlCli client.Client + KubeCli *kubernetes.Clientset +} + +type ChaosResult struct { + Name string + Pods []PodResult +} + +type PodResult struct { + Name string + Items []ItemResult +} + +const ( + ItemSuccess = iota + 1 + ItemFailure +) + +type ItemResult struct { + Name string + Value string + Status int `json:",omitempty"` + SucInfo string `json:",omitempty"` + ErrInfo string `json:",omitempty"` +} + +func init() { + _ = v1alpha1.AddToScheme(scheme) + _ = clientgoscheme.AddToScheme(scheme) +} + +func upperCaseChaos(str string) string { + parts := regexp.MustCompile("(.*)(chaos)").FindStringSubmatch(str) + return strings.Title(parts[1]) + strings.Title(parts[2]) +} + +// PrettyPrint print with tab number and color +func PrettyPrint(s string, num int, color string) { + var tabStr string + for i := 0; i < num; i++ { + tabStr += "\t" + } + str := fmt.Sprintf("%s%s\n\n", tabStr, regexp.MustCompile("\n").ReplaceAllString(s, "\n"+tabStr)) + if color != "" { + if cfunc, ok := colorFunc[color]; !ok { + fmt.Printf("COLOR NOT SUPPORTED") + } else { + cfunc(str) + } + } else { + fmt.Printf(str) + } +} + +// PrintResult prints result to users in prettier format +func PrintResult(result []ChaosResult) { + for _, chaos := range result { + PrettyPrint("[Chaos]: "+chaos.Name, 0, "Blue") + for _, pod := range chaos.Pods { + PrettyPrint("[Pod]: "+pod.Name, 0, "Blue") + for i, item := range pod.Items { + PrettyPrint(fmt.Sprintf("%d. [%s]", i+1, item.Name), 1, "Cyan") + PrettyPrint(item.Value, 1, "") + if item.Status == ItemSuccess { + if item.SucInfo != "" { + PrettyPrint(item.SucInfo, 1, "Green") + } else { + PrettyPrint("Execute as expected", 1, "Green") + } + } else if item.Status == ItemFailure { + PrettyPrint(fmt.Sprintf("Failed: %s ", item.ErrInfo), 1, "Red") + } + } + } + } +} + +// MarshalChaos returns json in readable format +func MarshalChaos(s interface{}) (string, error) { + b, err := json.MarshalIndent(s, "", " ") + if err != nil { + return "", fmt.Errorf("failed to marshal indent: %s", err.Error()) + } + return string(b), nil +} + +// InitClientSet inits two different clients that would be used +func InitClientSet() (*ClientSet, error) { + ctrlClient, err := client.New(config.GetConfigOrDie(), client.Options{Scheme: scheme}) + if err != nil { + return nil, fmt.Errorf("failed to create client") + } + kubeClient, err := kubernetes.NewForConfig(config.GetConfigOrDie()) + if err != nil { + return nil, fmt.Errorf("error in getting access to K8S: %s", err.Error()) + } + return &ClientSet{ctrlClient, kubeClient}, nil +} + +// GetPods returns pod list and corresponding chaos daemon +func GetPods(ctx context.Context, status v1alpha1.ChaosStatus, selector v1alpha1.SelectorSpec, c client.Client) ([]v1.Pod, []v1.Pod, error) { + // get podName + failedMessage := status.FailedMessage + if failedMessage != "" { + PrettyPrint(fmt.Sprintf("chaos failed with: %s", failedMessage), 0, "Red") + } + + phase := status.Experiment.Phase + nextStart := status.Scheduler.NextStart + + if phase == v1alpha1.ExperimentPhaseWaiting { + waitTime := nextStart.Sub(time.Now()) + fmt.Printf("Waiting for chaos to start, in %s\n", waitTime) + time.Sleep(waitTime) + } + + pods, err := utils.SelectPods(ctx, c, c, selector, ctrlconfig.ControllerCfg.ClusterScoped, ctrlconfig.ControllerCfg.TargetNamespace, ctrlconfig.ControllerCfg.AllowedNamespaces, ctrlconfig.ControllerCfg.IgnoredNamespaces) + if err != nil { + return nil, nil, fmt.Errorf("failed to SelectPods with: %s", err.Error()) + } + if len(pods) == 0 { + return nil, nil, fmt.Errorf("no pods found for selector: %s", selector) + } + + var chaosDaemons []v1.Pod + // get chaos daemon + for _, pod := range pods { + nodeName := pod.Spec.NodeName + daemonSelector := v1alpha1.SelectorSpec{ + Nodes: []string{nodeName}, + LabelSelectors: map[string]string{"app.kubernetes.io/component": "chaos-daemon"}, + } + daemons, err := utils.SelectPods(ctx, c, nil, daemonSelector, ctrlconfig.ControllerCfg.ClusterScoped, ctrlconfig.ControllerCfg.TargetNamespace, ctrlconfig.ControllerCfg.AllowedNamespaces, ctrlconfig.ControllerCfg.IgnoredNamespaces) + if err != nil { + return nil, nil, fmt.Errorf("failed to SelectPods with: %s", err.Error()) + } + if len(daemons) == 0 { + return nil, nil, fmt.Errorf("no daemons found for selector: %s", daemonSelector) + } + chaosDaemons = append(chaosDaemons, daemons[0]) + } + + return pods, chaosDaemons, nil +} + +// GetChaosList returns chaos list limited by input +func GetChaosList(ctx context.Context, chaosType string, chaosName string, ns string, c client.Client) ([]runtime.Object, []string, error) { + chaosType = upperCaseChaos(strings.ToLower(chaosType)) + allKinds := v1alpha1.AllKinds() + chaosListInterface := allKinds[chaosType].ChaosList + + if err := c.List(ctx, chaosListInterface, client.InNamespace(ns)); err != nil { + return nil, nil, fmt.Errorf("failed to get chaosList: %s", err.Error()) + } + chaosList := chaosListInterface.ListChaos() + if len(chaosList) == 0 { + return nil, nil, fmt.Errorf("no chaos is found, please check your input") + } + + var retList []runtime.Object + var retNameList []string + for _, ch := range chaosList { + if chaosName == "" || chaosName == ch.Name { + chaos, err := getChaos(ctx, chaosType, ch.Name, ns, c) + if err != nil { + return nil, nil, err + } + retList = append(retList, chaos) + retNameList = append(retNameList, ch.Name) + } + } + if len(retList) == 0 { + return nil, nil, fmt.Errorf("no chaos is found, please check your input") + } + + return retList, retNameList, nil +} + +func getChaos(ctx context.Context, chaosType string, chaosName string, ns string, c client.Client) (runtime.Object, error) { + allKinds := v1alpha1.AllKinds() + chaos := allKinds[chaosType].Chaos + objectKey := client.ObjectKey{ + Namespace: ns, + Name: chaosName, + } + if err := c.Get(ctx, objectKey, chaos); err != nil { + return nil, fmt.Errorf("failed to get chaos %s: %s", chaosName, err.Error()) + } + return chaos, nil +} + +// GetPidFromPS returns pid-command pairs +func GetPidFromPS(ctx context.Context, pod v1.Pod, daemon v1.Pod, c *kubernetes.Clientset) ([]string, []string, error) { + cmd := fmt.Sprintf("ps") + out, err := ExecBypass(ctx, pod, daemon, cmd, c) + if err != nil { + return nil, nil, fmt.Errorf("run command '%s' failed with: %s", cmd, err.Error()) + } + outLines := strings.Split(string(out), "\n") + if len(outLines) < 2 { + return nil, nil, fmt.Errorf("ps returns empty") + } + titles := strings.Fields(outLines[0]) + var pidColumn, cmdColumn int + for i, t := range titles { + if t == "PID" { + pidColumn = i + } + if t == "COMMAND" || t == "CMD" { + cmdColumn = i + } + } + if pidColumn == 0 && cmdColumn == 0 { + return nil, nil, fmt.Errorf("Parsing ps error: could not get PID and COMMAND column") + } + var pids, commands []string + for _, line := range outLines[1:] { + item := strings.Fields(line) + // break when got empty line + if len(item) == 0 { + break + } + pids = append(pids, item[pidColumn]) + commands = append(commands, item[cmdColumn]) + } + return pids, commands, nil +} + +// GetPidFromPod returns pid given containerd ID in pod +func GetPidFromPod(ctx context.Context, pod v1.Pod, daemon v1.Pod) (int, error) { + pfCancel, localPort, err := forwardPorts(ctx, daemon, uint16(ctrlconfig.ControllerCfg.ChaosDaemonPort)) + if err != nil { + return 0, fmt.Errorf("forward ports failed: %s", err.Error()) + } + defer func() { + pfCancel() + }() + + daemonClient, err := utils.NewChaosDaemonClientLocally(int(localPort)) + if err != nil { + return 0, fmt.Errorf("new chaos daemon client failed: %s", err.Error()) + } + defer daemonClient.Close() + + if len(pod.Status.ContainerStatuses) == 0 { + return 0, fmt.Errorf("%s %s can't get the state of container", pod.Namespace, pod.Name) + } + + res, err := daemonClient.ContainerGetPid(ctx, &pb.ContainerRequest{ + Action: &pb.ContainerAction{ + Action: pb.ContainerAction_GETPID, + }, + ContainerId: pod.Status.ContainerStatuses[0].ContainerID, + }) + if err != nil { + return 0, fmt.Errorf("container get pid failed: %s", err.Error()) + } + return int(res.Pid), nil +} + +func forwardPorts(ctx context.Context, pod v1.Pod, port uint16) (context.CancelFunc, uint16, error) { + clientRawConfig, err := e2econfig.LoadClientRawConfig() + if err != nil { + log.Fatal("failed to load raw config", err.Error()) + } + fw, err := portforward.NewPortForwarder(ctx, e2econfig.NewSimpleRESTClientGetter(clientRawConfig), false) + if err != nil { + log.Fatal("failed to create port forwarder", err.Error()) + } + _, localPort, pfCancel, err := portforward.ForwardOnePort(fw, pod.Namespace, pod.Name, port) + return pfCancel, localPort, err +} + +// Log print log of pod +func Log(pod v1.Pod, tail int64, c *kubernetes.Clientset) (string, error) { + podLogOpts := v1.PodLogOptions{} + //use negative tail to indicate no tail limit is needed + if tail >= 0 { + podLogOpts.TailLines = func(i int64) *int64 { return &i }(tail) + } + + req := c.CoreV1().Pods(pod.Namespace).GetLogs(pod.Name, &podLogOpts) + podLogs, err := req.Stream() + if err != nil { + return "", fmt.Errorf("failed to open stream: %v", err.Error()) + } + defer podLogs.Close() + + buf := new(bytes.Buffer) + _, err = io.Copy(buf, podLogs) + if err != nil { + return "", fmt.Errorf("failed to copy information from podLogs to buf: %v", err.Error()) + } + return buf.String(), nil +} + +// CheckFailedMessage provide debug info and suggestions from failed message +func CheckFailedMessage(ctx context.Context, failedMessage string, daemons []v1.Pod, c *ClientSet) error { + if strings.Contains(failedMessage, "rpc error: code = Unavailable desc = connection error") || strings.Contains(failedMessage, "connect: connection refused") { + if err := checkConnForCtrlAndDaemon(ctx, daemons, c); err != nil { + return fmt.Errorf("Error occurs when check failed message: %s", err) + } + } + return nil +} + +func checkConnForCtrlAndDaemon(ctx context.Context, daemons []v1.Pod, c *ClientSet) error { + ctrlSelector := v1alpha1.SelectorSpec{ + LabelSelectors: map[string]string{"app.kubernetes.io/component": "controller-manager"}, + } + ctrlMgrs, err := utils.SelectPods(ctx, c.CtrlCli, c.CtrlCli, ctrlSelector, ctrlconfig.ControllerCfg.ClusterScoped, ctrlconfig.ControllerCfg.TargetNamespace, ctrlconfig.ControllerCfg.AllowedNamespaces, ctrlconfig.ControllerCfg.IgnoredNamespaces) + if err != nil { + return fmt.Errorf("failed to SelectPods with: %s", err.Error()) + } + if len(ctrlMgrs) == 0 { + return fmt.Errorf("Could not found controller manager") + } + for _, daemon := range daemons { + daemonIP := daemon.Status.PodIP + cmd := fmt.Sprintf("ping -c 1 %s > /dev/null; echo $?", daemonIP) + out, err := Exec(ctx, ctrlMgrs[0], cmd, c.KubeCli) + if err != nil { + return fmt.Errorf("run command '%s' failed with: %s", cmd, err.Error()) + } + if string(out) == "0" { + PrettyPrint(fmt.Sprintf("Connection between Controller-Manager and Daemon %s (ip address: %s) works well", daemon.Name, daemonIP), 0, "Green") + } else { + PrettyPrint(fmt.Sprintf(`Connection between Controller-Manager and Daemon %s (ip address: %s) is blocked. +Please check network policy / firewall, or see FAQ on website`, daemon.Name, daemonIP), 0, "Red") + } + + } + return nil +} diff --git a/pkg/chaosctl/common/common_test.go b/pkg/chaosctl/common/common_test.go new file mode 100644 index 0000000000..c0cd8b6e63 --- /dev/null +++ b/pkg/chaosctl/common/common_test.go @@ -0,0 +1,208 @@ +// Copyright 2020 Chaos Mesh Authors. +// +// 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, +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "context" + "testing" + "time" + + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + kubectlscheme "k8s.io/kubectl/pkg/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "github.com/chaos-mesh/chaos-mesh/api/v1alpha1" + "github.com/chaos-mesh/chaos-mesh/pkg/utils" +) + +func TestGetChaosList(t *testing.T) { + g := NewWithT(t) + + chaos1 := v1alpha1.NetworkChaos{ + TypeMeta: metav1.TypeMeta{ + Kind: "NetworkChaos", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: metav1.NamespaceDefault, + Name: "fakechaos-1", + }, + } + + chaos2 := v1alpha1.NetworkChaos{ + TypeMeta: metav1.TypeMeta{ + Kind: "NetworkChaos", + APIVersion: "v1", + }, + ObjectMeta: metav1.ObjectMeta{ + Namespace: metav1.NamespaceDefault, + Name: "fakechaos-2", + }, + } + + v1alpha1.SchemeBuilder.AddToScheme(kubectlscheme.Scheme) + + client := fake.NewFakeClientWithScheme(kubectlscheme.Scheme, &chaos1, &chaos2) + + tests := []struct { + name string + chaosType string + chaosName string + ns string + expectedNum int + expectedErr bool + }{ + { + name: "Only specify chaosType", + chaosType: "networkchaos", + chaosName: "", + ns: "default", + expectedNum: 2, + expectedErr: false, + }, + { + name: "Specify chaos type, chaos name and namespace", + chaosType: "networkchaos", + chaosName: "fakechaos-1", + ns: "default", + expectedNum: 1, + expectedErr: false, + }, + { + name: "Specify non-exist chaos name", + chaosType: "networkchaos", + chaosName: "fakechaos-oops", + ns: "default", + expectedErr: true, + }, + { + name: "Specify non-exist chaos types", + chaosType: "stresschaos", + chaosName: "fakechaos-1", + ns: "default", + expectedErr: true, + }, + { + name: "Specify non-exist namespace", + chaosType: "networkchaos", + chaosName: "fakechaos-1", + ns: "oops", + expectedErr: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + chaos, _, err := GetChaosList(context.Background(), test.chaosType, test.chaosName, test.ns, client) + if test.expectedErr { + g.Expect(err).NotTo(BeNil()) + } else { + g.Expect(err).To(BeNil()) + g.Expect(len(chaos)).To(Equal(test.expectedNum)) + } + }) + } +} + +func TestGetPods(t *testing.T) { + g := NewWithT(t) + + _ = v1alpha1.ChaosStatus{ + Experiment: v1alpha1.ExperimentStatus{ + Phase: v1alpha1.ExperimentPhaseRunning, + }, + Scheduler: v1alpha1.ScheduleStatus{}, + } + + nodeObjects, _ := utils.GenerateNNodes("node", 2, nil) + podObjects0, _ := utils.GenerateNPods("pod-node0", 1, utils.PodArg{Labels: map[string]string{"app": "pod"}, Nodename: "node0"}) + podObjects1, _ := utils.GenerateNPods("pod-node1", 1, utils.PodArg{Labels: map[string]string{"app": "pod"}, Nodename: "node1"}) + daemonObjects0, _ := utils.GenerateNPods("daemon-node0", 1, utils.PodArg{Labels: map[string]string{"app.kubernetes.io/component": "chaos-daemon"}, Nodename: "node0"}) + daemonObjects1, _ := utils.GenerateNPods("daemon-node1", 1, utils.PodArg{Labels: map[string]string{"app.kubernetes.io/component": "chaos-daemon"}, Nodename: "node1"}) + + allObjects := append(nodeObjects, daemonObjects0[0], podObjects0[0], daemonObjects1[0], podObjects1[0]) + + v1alpha1.SchemeBuilder.AddToScheme(kubectlscheme.Scheme) + client := fake.NewFakeClientWithScheme(kubectlscheme.Scheme, allObjects...) + + tests := []struct { + name string + chaosSelector v1alpha1.SelectorSpec + chaosStatus v1alpha1.ChaosStatus + wait bool + expectedPodNum int + expectedDaemonNum int + expectedErr bool + }{ + { + name: "chaos on two pods", + chaosSelector: v1alpha1.SelectorSpec{LabelSelectors: map[string]string{"app": "pod"}}, + chaosStatus: v1alpha1.ChaosStatus{}, + expectedPodNum: 2, + expectedDaemonNum: 2, + expectedErr: false, + }, + { + name: "chaos on one pod", + chaosSelector: v1alpha1.SelectorSpec{ + Nodes: []string{"node0"}, + LabelSelectors: map[string]string{"app": "pod"}, + }, + chaosStatus: v1alpha1.ChaosStatus{}, + expectedPodNum: 1, + expectedDaemonNum: 1, + expectedErr: false, + }, + { + name: "wait for 100ms for chaos to start", + chaosSelector: v1alpha1.SelectorSpec{LabelSelectors: map[string]string{"app": "pod"}}, + chaosStatus: v1alpha1.ChaosStatus{ + Experiment: v1alpha1.ExperimentStatus{ + Phase: v1alpha1.ExperimentPhaseWaiting, + }, + Scheduler: v1alpha1.ScheduleStatus{ + NextStart: &metav1.Time{Time: time.Now().Add(time.Millisecond * 50)}, + }, + }, + wait: true, + expectedPodNum: 2, + expectedDaemonNum: 2, + expectedErr: false, + }, + { + name: "wrong selector to get pod", + chaosSelector: v1alpha1.SelectorSpec{LabelSelectors: map[string]string{"app": "oops"}}, + chaosStatus: v1alpha1.ChaosStatus{}, + expectedErr: true, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + timeBefore := time.Now() + pods, daemons, err := GetPods(context.Background(), test.chaosStatus, test.chaosSelector, client) + if test.wait { + g.Expect(time.Now().Add(time.Millisecond * -50).Before(timeBefore)).To(BeTrue()) + } + if test.expectedErr { + g.Expect(err).NotTo(BeNil()) + } else { + g.Expect(err).To(BeNil()) + g.Expect(len(pods)).To(Equal(test.expectedPodNum)) + g.Expect(len(daemons)).To(Equal(test.expectedDaemonNum)) + } + }) + } +} diff --git a/pkg/chaosctl/common/exec.go b/pkg/chaosctl/common/exec.go new file mode 100644 index 0000000000..f046c6aa98 --- /dev/null +++ b/pkg/chaosctl/common/exec.go @@ -0,0 +1,103 @@ +// Copyright 2019 Chaos Mesh Authors. +// +// 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, +// See the License for the specific language governing permissions and +// limitations under the License. + +package common + +import ( + "bytes" + "context" + "fmt" + "io/ioutil" + "strconv" + "strings" + + "google.golang.org/grpc/grpclog" + v1 "k8s.io/api/core/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/tools/remotecommand" + kubectlscheme "k8s.io/kubectl/pkg/scheme" + "sigs.k8s.io/controller-runtime/pkg/client/config" +) + +// Exec executes certain command and returns the result +// Only commands in chaos-mesh components should use this way +// for target pod, use ExecBypass +func Exec(ctx context.Context, pod v1.Pod, cmd string, c *kubernetes.Clientset) (string, error) { + name := pod.GetObjectMeta().GetName() + namespace := pod.GetObjectMeta().GetNamespace() + // TODO: if `containerNames` is set and specific container is injected chaos, + // need to use THE name rather than the first one. + // till 20/11/10 only podchaos and kernelchaos support `containerNames`, so not set it for now + containerName := pod.Spec.Containers[0].Name + + req := c.CoreV1().RESTClient().Post(). + Resource("pods"). + Name(name). + Namespace(namespace). + SubResource("exec") + + req.VersionedParams(&v1.PodExecOptions{ + Container: containerName, + Command: []string{"/bin/sh", "-c", cmd}, + Stdin: false, + Stdout: true, + Stderr: true, + TTY: false, + }, kubectlscheme.ParameterCodec) + + var stdout, stderr bytes.Buffer + exec, err := remotecommand.NewSPDYExecutor(config.GetConfigOrDie(), "POST", req.URL()) + if err != nil { + return "", fmt.Errorf("error in creating NewSPDYExecutor: %s", err.Error()) + } + err = exec.Stream(remotecommand.StreamOptions{ + Stdin: nil, + Stdout: &stdout, + Stderr: &stderr, + }) + if err != nil { + return "", fmt.Errorf("error in streaming remotecommand: %s, pod: %s, command: %s, std error: %s", err.Error(), pod.Name, cmd, stderr.String()) + } + if stderr.String() != "" { + return "", fmt.Errorf("error of command %s: %s", cmd, stderr.String()) + } + return stdout.String(), nil +} + +// ExecBypass use chaos-daemon to enter namespace and execute command in target pod +func ExecBypass(ctx context.Context, pod v1.Pod, daemon v1.Pod, cmd string, c *kubernetes.Clientset) (string, error) { + cmdSubSlice := strings.Fields(cmd) + if len(cmdSubSlice) == 0 { + return "", fmt.Errorf("command should not be empty") + } + // To disable printing irrelevant log from grpc/clientconn.go + // See grpc/grpc-go#3918 for detail. Could be resolved in the future + grpclog.SetLoggerV2(grpclog.NewLoggerV2(ioutil.Discard, ioutil.Discard, ioutil.Discard)) + pid, err := GetPidFromPod(ctx, pod, daemon) + if err != nil { + return "", err + } + switch cmdSubSlice[0] { + case "ps": + newCmd := fmt.Sprintf("/usr/local/bin/nsexec -m /proc/%s/ns/mnt -p /proc/%s/ns/pid -l -- %s", strconv.Itoa(pid), strconv.Itoa(pid), cmd) + return Exec(ctx, daemon, newCmd, c) + case "cat", "ls": + // we need to enter mount namespace to get file related infomation + // but enter mnt ns would prevent us to access `cat`/`ls` in daemon + // so use `nsexec` to achieve using nsenter and cat together + newCmd := fmt.Sprintf("/usr/local/bin/nsexec -m /proc/%s/ns/mnt -l -- %s", strconv.Itoa(pid), cmd) + return Exec(ctx, daemon, newCmd, c) + default: + return "", fmt.Errorf("command not supported for nsenter") + } +} diff --git a/pkg/chaosctl/debug/iochaos/iochaos.go b/pkg/chaosctl/debug/iochaos/iochaos.go new file mode 100644 index 0000000000..a5bb62f753 --- /dev/null +++ b/pkg/chaosctl/debug/iochaos/iochaos.go @@ -0,0 +1,78 @@ +// Copyright 2019 Chaos Mesh Authors. +// +// 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, +// See the License for the specific language governing permissions and +// limitations under the License. + +package iochaos + +import ( + "context" + "fmt" + + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + + "github.com/chaos-mesh/chaos-mesh/api/v1alpha1" + cm "github.com/chaos-mesh/chaos-mesh/pkg/chaosctl/common" +) + +// Debug get chaos debug information +func Debug(ctx context.Context, chaos runtime.Object, c *cm.ClientSet, result *cm.ChaosResult) error { + ioChaos, ok := chaos.(*v1alpha1.IoChaos) + if !ok { + return fmt.Errorf("chaos is not iochaos") + } + chaosStatus := ioChaos.Status.ChaosStatus + chaosSelector := ioChaos.Spec.GetSelector() + + pods, daemons, err := cm.GetPods(ctx, chaosStatus, chaosSelector, c.CtrlCli) + if err != nil { + return err + } + + if err := cm.CheckFailedMessage(ctx, chaosStatus.FailedMessage, daemons, c); err != nil { + return err + } + + for i := range pods { + podName := pods[i].Name + podResult := cm.PodResult{Name: podName} + err := debugEachPod(ctx, pods[i], daemons[i], ioChaos, c, &podResult) + result.Pods = append(result.Pods, podResult) + if err != nil { + return fmt.Errorf("for %s: %s", podName, err.Error()) + } + } + return nil +} + +func debugEachPod(ctx context.Context, pod v1.Pod, daemon v1.Pod, chaos *v1alpha1.IoChaos, c *cm.ClientSet, result *cm.PodResult) error { + // print out debug info + cmd := fmt.Sprintf("cat /proc/mounts") + out, err := cm.ExecBypass(ctx, pod, daemon, cmd, c.KubeCli) + if err != nil { + return fmt.Errorf("run command '%s' failed with: %s", cmd, err.Error()) + } + result.Items = append(result.Items, cm.ItemResult{Name: "mount information", Value: string(out)}) + + pids, commands, err := cm.GetPidFromPS(ctx, pod, daemon, c.KubeCli) + + for i := range pids { + cmd = fmt.Sprintf("ls -l /proc/%s/fd", pids[i]) + out, err = cm.ExecBypass(ctx, pod, daemon, cmd, c.KubeCli) + if err != nil { + return fmt.Errorf("run command '%s' failed with: %s", cmd, err.Error()) + } + result.Items = append(result.Items, cm.ItemResult{Name: fmt.Sprintf("file discriptors of PID: %s, COMMAND: %s", pids[i], commands[i]), Value: string(out)}) + } + + return nil +} diff --git a/pkg/chaosctl/debug/networkchaos/networkchaos.go b/pkg/chaosctl/debug/networkchaos/networkchaos.go new file mode 100644 index 0000000000..d11bc1b9b1 --- /dev/null +++ b/pkg/chaosctl/debug/networkchaos/networkchaos.go @@ -0,0 +1,158 @@ +// Copyright 2019 Chaos Mesh Authors. +// +// 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, +// See the License for the specific language governing permissions and +// limitations under the License. + +package networkchaos + +import ( + "context" + "fmt" + "io/ioutil" + "regexp" + "strconv" + "strings" + + "google.golang.org/grpc/grpclog" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/chaos-mesh/chaos-mesh/api/v1alpha1" + cm "github.com/chaos-mesh/chaos-mesh/pkg/chaosctl/common" +) + +// Debug get chaos debug information +func Debug(ctx context.Context, chaos runtime.Object, c *cm.ClientSet, result *cm.ChaosResult) error { + networkChaos, ok := chaos.(*v1alpha1.NetworkChaos) + if !ok { + return fmt.Errorf("chaos is not network") + } + chaosStatus := networkChaos.Status.ChaosStatus + chaosSelector := networkChaos.Spec.GetSelector() + + pods, daemons, err := cm.GetPods(ctx, chaosStatus, chaosSelector, c.CtrlCli) + if err != nil { + return err + } + + if err := cm.CheckFailedMessage(ctx, chaosStatus.FailedMessage, daemons, c); err != nil { + return err + } + + for i := range pods { + podName := pods[i].Name + podResult := cm.PodResult{Name: podName} + err := debugEachPod(ctx, pods[i], daemons[i], networkChaos, c, &podResult) + result.Pods = append(result.Pods, podResult) + if err != nil { + return fmt.Errorf("for %s: %s", podName, err.Error()) + } + } + return nil +} + +func debugEachPod(ctx context.Context, pod v1.Pod, daemon v1.Pod, chaos *v1alpha1.NetworkChaos, c *cm.ClientSet, result *cm.PodResult) error { + // To disable printing irrelevant log from grpc/clientconn.go + // see grpc/grpc-go#3918 for detail. could be resolved in the future + grpclog.SetLoggerV2(grpclog.NewLoggerV2(ioutil.Discard, ioutil.Discard, ioutil.Discard)) + pid, err := cm.GetPidFromPod(ctx, pod, daemon) + if err != nil { + return err + } + nsenterPath := "-n/proc/" + strconv.Itoa(pid) + "/ns/net" + + // print out debug info + cmd := fmt.Sprintf("/usr/bin/nsenter %s -- ipset list", nsenterPath) + out, err := cm.Exec(ctx, daemon, cmd, c.KubeCli) + if err != nil { + return fmt.Errorf("run command '%s' failed with: %s", cmd, err.Error()) + } + result.Items = append(result.Items, cm.ItemResult{Name: "ipset list", Value: string(out)}) + + cmd = fmt.Sprintf("/usr/bin/nsenter %s -- tc qdisc list", nsenterPath) + out, err = cm.Exec(ctx, daemon, cmd, c.KubeCli) + if err != nil { + return fmt.Errorf("run command '%s' failed with: %s", cmd, err.Error()) + } + itemResult := cm.ItemResult{Name: "tc qdisc list", Value: string(out)} + + // A demo for comparison with expected. A bit messy actually, don't know if we still need this + action := chaos.Spec.Action + var netemExpect string + switch action { + case "delay": + latency := chaos.Spec.Delay.Latency + jitter := chaos.Spec.Delay.Jitter + correlation := chaos.Spec.Delay.Correlation + netemExpect = fmt.Sprintf("%v %v %v %v%%", action, latency, jitter, correlation) + + netemCurrent := regexp.MustCompile("(?:limit 1000)(.*)").FindStringSubmatch(string(out)) + if len(netemCurrent) == 0 { + return fmt.Errorf("No NetworkChaos is applied") + } + for i, netem := range strings.Fields(netemCurrent[1]) { + itemCurrent := netem + itemExpect := strings.Fields(netemExpect)[i] + if itemCurrent != itemExpect { + r := regexp.MustCompile("([0-9]*[.])?[0-9]+") + // digit could be different, so parse string to float + numCurrent, err := strconv.ParseFloat(r.FindString(itemCurrent), 64) + if err != nil { + return fmt.Errorf("parse itemCurrent failed: %s", err.Error()) + } + numExpect, err := strconv.ParseFloat(r.FindString(itemExpect), 64) + if err != nil { + return fmt.Errorf("parse itemExpect failed: %s", err.Error()) + } + if numCurrent == numExpect { + continue + } + // alphabetic charactors + alpCurrent := regexp.MustCompile("[[:alpha:]]+").FindString(itemCurrent) + alpExpect := regexp.MustCompile("[[:alpha:]]+").FindString(itemExpect) + if alpCurrent == alpExpect { + continue + } + itemResult.Status = cm.ItemFailure + itemResult.ErrInfo = fmt.Sprintf("expect: %s, got: %v", netemExpect, netemCurrent) + } + } + if itemResult.Status != cm.ItemFailure { + itemResult.Status = cm.ItemSuccess + } + } + result.Items = append(result.Items, itemResult) + + cmd = fmt.Sprintf("/usr/bin/nsenter %s -- iptables --list", nsenterPath) + out, err = cm.Exec(ctx, daemon, cmd, c.KubeCli) + if err != nil { + return fmt.Errorf("run command '%s' failed with: %s", cmd, err.Error()) + } + result.Items = append(result.Items, cm.ItemResult{Name: "iptables list", Value: string(out)}) + + podNetworkChaos := &v1alpha1.PodNetworkChaos{} + objectKey := client.ObjectKey{ + Namespace: pod.Namespace, + Name: pod.Name, + } + + if err = c.CtrlCli.Get(ctx, objectKey, podNetworkChaos); err != nil { + return fmt.Errorf("failed to get chaos: %s", err.Error()) + } + output, err := cm.MarshalChaos(podNetworkChaos.Spec) + if err != nil { + return err + } + result.Items = append(result.Items, cm.ItemResult{Name: "podnetworkchaos", Value: output}) + + return nil +} diff --git a/pkg/chaosctl/debug/stresschaos/stresschaos.go b/pkg/chaosctl/debug/stresschaos/stresschaos.go new file mode 100644 index 0000000000..7178cb1723 --- /dev/null +++ b/pkg/chaosctl/debug/stresschaos/stresschaos.go @@ -0,0 +1,168 @@ +// Copyright 2019 Chaos Mesh Authors. +// +// 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, +// See the License for the specific language governing permissions and +// limitations under the License. + +package stresschaos + +import ( + "context" + "fmt" + "regexp" + "strconv" + "strings" + + "code.cloudfoundry.org/bytefmt" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + + "github.com/chaos-mesh/chaos-mesh/api/v1alpha1" + cm "github.com/chaos-mesh/chaos-mesh/pkg/chaosctl/common" +) + +// Debug get chaos debug information +func Debug(ctx context.Context, chaos runtime.Object, c *cm.ClientSet, result *cm.ChaosResult) error { + stressChaos, ok := chaos.(*v1alpha1.StressChaos) + if !ok { + return fmt.Errorf("chaos is not stresschaos") + } + chaosStatus := stressChaos.Status.ChaosStatus + chaosSelector := stressChaos.Spec.GetSelector() + + pods, daemons, err := cm.GetPods(ctx, chaosStatus, chaosSelector, c.CtrlCli) + if err != nil { + return err + } + + if err := cm.CheckFailedMessage(ctx, chaosStatus.FailedMessage, daemons, c); err != nil { + return err + } + + for i := range pods { + podName := pods[i].Name + podResult := cm.PodResult{Name: podName} + err := debugEachPod(ctx, pods[i], daemons[i], stressChaos, c, &podResult) + result.Pods = append(result.Pods, podResult) + if err != nil { + return fmt.Errorf("for %s: %s", podName, err.Error()) + } + } + return nil +} + +func debugEachPod(ctx context.Context, pod v1.Pod, daemon v1.Pod, chaos *v1alpha1.StressChaos, c *cm.ClientSet, result *cm.PodResult) error { + // get process path + cmd := fmt.Sprintf("cat /proc/cgroups") + out, err := cm.ExecBypass(ctx, pod, daemon, cmd, c.KubeCli) + if err != nil { + return fmt.Errorf("run command '%s' failed with: %s", cmd, err.Error()) + } + + cmd = fmt.Sprintf("ps") + out, err = cm.ExecBypass(ctx, pod, daemon, cmd, c.KubeCli) + if err != nil { + return fmt.Errorf("run command '%s' failed with: %s", cmd, err.Error()) + } + result.Items = append(result.Items, cm.ItemResult{Name: "ps", Value: string(out)}) + stressngLine := regexp.MustCompile("(.*)(stress-ng)").FindStringSubmatch(string(out)) + if len(stressngLine) == 0 { + return fmt.Errorf("Could not find stress-ng, StressChaos failed") + } + stressngPid := strings.Fields(stressngLine[0])[0] + + pids, commands, err := cm.GetPidFromPS(ctx, pod, daemon, c.KubeCli) + + for i := range pids { + cmd = fmt.Sprintf("cat /proc/%s/cgroup", pids[i]) + out, err = cm.ExecBypass(ctx, pod, daemon, cmd, c.KubeCli) + if err != nil { + result.Items = append(result.Items, cm.ItemResult{Name: fmt.Sprintf("/proc/%s/cgroup of %s", pids[i], commands[i]), Value: "No cgroup found"}) + } else { + result.Items = append(result.Items, cm.ItemResult{Name: fmt.Sprintf("/proc/%s/cgroup of %s", pids[i], commands[i]), Value: string(out)}) + } + } + + cmd = fmt.Sprintf("cat /proc/%s/cgroup", stressngPid) + out, err = cm.ExecBypass(ctx, pod, daemon, cmd, c.KubeCli) + if err != nil { + return fmt.Errorf("run command '%s' failed with: %s", cmd, err.Error()) + } + itemResult := cm.ItemResult{Name: "/proc/(stress-ng pid)/cgroup", Value: string(out)} + + // no more info for StressngStressors + if chaos.Spec.StressngStressors != "" { + return nil + } + + isCPU := true + if cpuSpec := chaos.Spec.Stressors.CPUStressor; cpuSpec == nil { + isCPU = false + } + + var expr, cpuMountType string + if isCPU { + if regexp.MustCompile("(cpu,cpuacct)").MatchString(string(out)) { + cpuMountType = "cpu,cpuacct" + } else { + cpuMountType = "cpu" + } + expr = "(?::" + cpuMountType + ":)(.*)" + } else { + expr = "(?::memory:)(.*)" + } + processPath := regexp.MustCompile(expr).FindStringSubmatch(string(out))[1] + + // print out debug info + if isCPU { + cmd = fmt.Sprintf("cat /sys/fs/cgroup/%s/%s/cpu.cfs_quota_us", cpuMountType, processPath) + out, err = cm.Exec(ctx, daemon, cmd, c.KubeCli) + if err != nil { + return fmt.Errorf("run command '%s' failed with: %s", cmd, err.Error()) + } + result.Items = append(result.Items, cm.ItemResult{Name: "cpu.cfs_quota_us", Value: string(out)}) + quota, err := strconv.Atoi(strings.TrimSuffix(string(out), "\n")) + if err != nil { + return fmt.Errorf("could not get cpu.cfs_quota_us with: %s", err.Error()) + } + + cmd = fmt.Sprintf("cat /sys/fs/cgroup/%s/%s/cpu.cfs_period_us", cpuMountType, processPath) + out, err = cm.Exec(ctx, daemon, cmd, c.KubeCli) + if err != nil { + return fmt.Errorf("run command '%s' failed with: %s", cmd, err.Error()) + } + period, err := strconv.Atoi(strings.TrimSuffix(string(out), "\n")) + if err != nil { + return fmt.Errorf("could not get cpu.cfs_period_us with: %s", err.Error()) + } + itemResult = cm.ItemResult{Name: "cpu.cfs_period_us", Value: string(out)} + + if quota == -1 { + itemResult.Status = cm.ItemFailure + itemResult.ErrInfo = "no cpu limit is set for now" + } else { + itemResult.Status = cm.ItemSuccess + itemResult.SucInfo = fmt.Sprintf("cpu limit is equals to %.2f", float64(quota)/float64(period)) + } + result.Items = append(result.Items, itemResult) + } else { + cmd = fmt.Sprintf("cat /sys/fs/cgroup/memory/%s/memory.limit_in_bytes", processPath) + out, err = cm.Exec(ctx, daemon, cmd, c.KubeCli) + if err != nil { + return fmt.Errorf("run command '%s' failed with: %s", cmd, err.Error()) + } + limit, err := strconv.ParseUint(strings.TrimSuffix(string(out), "\n"), 10, 64) + if err != nil { + return fmt.Errorf("could not get memory.limit_in_bytes with: %s", err.Error()) + } + result.Items = append(result.Items, cm.ItemResult{Name: "memory.limit_in_bytes", Value: bytefmt.ByteSize(limit) + "B"}) + } + return nil +} diff --git a/test/e2e/util/portforward/portforward.go b/pkg/portforward/portforward.go similarity index 94% rename from test/e2e/util/portforward/portforward.go rename to pkg/portforward/portforward.go index be0f803716..fac6531d79 100644 --- a/test/e2e/util/portforward/portforward.go +++ b/pkg/portforward/portforward.go @@ -49,9 +49,10 @@ type PortForward interface { // portForwarder implements PortForward interface type portForwarder struct { genericclioptions.RESTClientGetter - ctx context.Context - config *rest.Config - client kubernetes.Interface + ctx context.Context + config *rest.Config + client kubernetes.Interface + enableLog bool } var _ PortForward = &portForwarder{} @@ -80,7 +81,9 @@ func (f *portForwarder) forwardPorts(podKey, method string, url *url.URL, addres go func() { lineScanner := bufio.NewScanner(r) for lineScanner.Scan() { - klog.Infof("log from port forwarding %q: %s", podKey, lineScanner.Text()) + if f.enableLog { + klog.Infof("log from port forwarding %q: %s", podKey, lineScanner.Text()) + } } }() @@ -150,7 +153,7 @@ func (f *portForwarder) ForwardPod(pod *corev1.Pod, addresses []string, ports [] } // NewPortForwarder would create a new port-forward -func NewPortForwarder(ctx context.Context, restClientGetter genericclioptions.RESTClientGetter) (PortForward, error) { +func NewPortForwarder(ctx context.Context, restClientGetter genericclioptions.RESTClientGetter, enableLog bool) (PortForward, error) { config, err := restClientGetter.ToRESTConfig() if err != nil { return nil, err @@ -164,6 +167,7 @@ func NewPortForwarder(ctx context.Context, restClientGetter genericclioptions.RE ctx: ctx, config: config, client: client, + enableLog: enableLog, } return f, nil } diff --git a/pkg/utils/chaosdaemon.go b/pkg/utils/chaosdaemon.go index 2ce319102d..93d50c1f77 100644 --- a/pkg/utils/chaosdaemon.go +++ b/pkg/utils/chaosdaemon.go @@ -60,6 +60,25 @@ func NewChaosDaemonClient(ctx context.Context, c client.Client, pod *v1.Pod, por }, nil } +// NewChaosDaemonClientLocally would create ChaosDaemonClient in localhost +func NewChaosDaemonClientLocally(port int) (ChaosDaemonClientInterface, error) { + if cli := mock.On("MockChaosDaemonClient"); cli != nil { + return cli.(ChaosDaemonClientInterface), nil + } + if err := mock.On("NewChaosDaemonClientError"); err != nil { + return nil, err.(error) + } + + cc, err := CreateGrpcConnectionWithAddress("localhost", port) + if err != nil { + return nil, err + } + return &GrpcChaosDaemonClient{ + ChaosDaemonClient: chaosdaemon.NewChaosDaemonClient(cc), + conn: cc, + }, nil +} + // MergeNetem merges two Netem protos into a new one. // REMEMBER to assign the return value, i.e. merged = utils.MergeNetm(merged, em) // For each field it takes the bigger value of the two. diff --git a/pkg/utils/grpc.go b/pkg/utils/grpc.go index 478e19a448..e49e479a61 100644 --- a/pkg/utils/grpc.go +++ b/pkg/utils/grpc.go @@ -52,12 +52,15 @@ func CreateGrpcConnection(ctx context.Context, c client.Client, pod *v1.Pod, por } daemonIP := findIPOnEndpoints(&endpoints, nodeName) - if len(daemonIP) == 0 { return nil, errors.Errorf("cannot find daemonIP on node %s in related Endpoints %v", nodeName, endpoints) } + return CreateGrpcConnectionWithAddress(daemonIP, port) +} - conn, err := grpc.Dial(fmt.Sprintf("%s:%d", daemonIP, port), +// CreateGrpcConnectionWithAddress create a grpc connection with given port and address +func CreateGrpcConnectionWithAddress(address string, port int) (*grpc.ClientConn, error) { + conn, err := grpc.Dial(fmt.Sprintf("%s:%d", address, port), grpc.WithInsecure(), grpc.WithUnaryInterceptor(TimeoutClientInterceptor)) if err != nil { diff --git a/pkg/utils/selector.go b/pkg/utils/selector.go index aeeda0fbb4..158504c2ec 100644 --- a/pkg/utils/selector.go +++ b/pkg/utils/selector.go @@ -158,13 +158,10 @@ func SelectPods(ctx context.Context, c client.Client, r client.Reader, selector if len(selector.Nodes) > 0 { for _, nodename := range selector.Nodes { var node v1.Node - err := c.Get(ctx, types.NamespacedName{ - Name: nodename, - }, &node) - if err == nil { - nodes = append(nodes, node) - continue + if err := c.Get(ctx, types.NamespacedName{Name: nodename}, &node); err != nil { + return nil, err } + nodes = append(nodes, node) } } if len(selector.NodeSelectors) > 0 { diff --git a/test/e2e/chaos/basic.go b/test/e2e/chaos/basic.go index c5de598128..999abe7645 100644 --- a/test/e2e/chaos/basic.go +++ b/test/e2e/chaos/basic.go @@ -44,7 +44,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "github.com/chaos-mesh/chaos-mesh/api/v1alpha1" + "github.com/chaos-mesh/chaos-mesh/pkg/portforward" e2econfig "github.com/chaos-mesh/chaos-mesh/test/e2e/config" + "github.com/chaos-mesh/chaos-mesh/test/e2e/e2econst" + "github.com/chaos-mesh/chaos-mesh/test/e2e/util" "github.com/chaos-mesh/chaos-mesh/test/e2e/util/portforward" "github.com/chaos-mesh/chaos-mesh/test/pkg/fixture" ) @@ -72,7 +75,7 @@ var _ = ginkgo.Describe("[Basic]", func() { ctx, cancel := context.WithCancel(context.Background()) clientRawConfig, err := e2econfig.LoadClientRawConfig() framework.ExpectNoError(err, "failed to load raw config") - fw, err = portforward.NewPortForwarder(ctx, e2econfig.NewSimpleRESTClientGetter(clientRawConfig)) + fw, err = portforward.NewPortForwarder(ctx, e2econfig.NewSimpleRESTClientGetter(clientRawConfig), true) framework.ExpectNoError(err, "failed to create port forwarder") fwCancel = cancel kubeCli = f.ClientSet