Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[tools] CLI tool to simplify some tasks, and eliminate the curl commands from the docs #2097

Merged
merged 70 commits into from
Apr 15, 2020
Merged
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
f3b7ccf
[script] remove DS_PROMETHEUS variable interpolation syntax in Docker…
xmcqueen Dec 12, 2019
062e74d
Merge remote-tracking branch 'upstream/master'
xmcqueen Jan 4, 2020
96f2d6e
start of tool for some operational tasks
xmcqueen Jan 5, 2020
d01a473
looks like yaml inputs works nicely
xmcqueen Jan 7, 2020
30fd1a5
cleaned up a lot and added many api calls
xmcqueen Jan 7, 2020
040a2b4
added placement init
xmcqueen Jan 8, 2020
8e86a94
factored
xmcqueen Jan 8, 2020
f857964
factoring
xmcqueen Jan 8, 2020
4330b01
cleanup
xmcqueen Jan 8, 2020
9038a0a
docs
xmcqueen Jan 8, 2020
e32ba1d
rename and add to Makefilf
xmcqueen Jan 8, 2020
4e8ed42
Merge remote-tracking branch 'upstream/master'
xmcqueen Jan 8, 2020
5ff5b0c
Merge branch 'tool-sample'
xmcqueen Jan 8, 2020
9714424
factor flags into main per PR comment
xmcqueen Jan 9, 2020
b2af08e
many of the PR comments are done
xmcqueen Jan 9, 2020
66c577a
more PR comments
xmcqueen Jan 9, 2020
9c73f0f
last code comments, next add some tests
xmcqueen Jan 9, 2020
a70df4d
factoring
xmcqueen Jan 9, 2020
3685e07
add yaml loader test
xmcqueen Jan 10, 2020
62a7368
checkpoint refactor to redo the cli syntax
xmcqueen Jan 10, 2020
bc86828
finished putting in new cli syntax
xmcqueen Jan 11, 2020
9e7f3fb
factoring
xmcqueen Jan 11, 2020
f9981ff
renaming and README
xmcqueen Jan 11, 2020
385ddf0
working test for arg processing
xmcqueen Jan 12, 2020
024e500
added test for namespaces command line parsing
xmcqueen Jan 12, 2020
75e49d2
add placements cli args test
xmcqueen Jan 13, 2020
0e13026
cleaning up
xmcqueen Jan 13, 2020
099d24d
address more PR comments
xmcqueen Jan 13, 2020
2ef9297
checkpoint
xmcqueen Jan 28, 2020
8a6a793
better arg handling started and test passes
xmcqueen Jan 29, 2020
e7651f5
make it more test-friendly and make the test less sucky
xmcqueen Jan 30, 2020
becd223
rename some items
xmcqueen Jan 31, 2020
0fc035f
checkpoint
xmcqueen Feb 1, 2020
e77f0ba
checkpoint
xmcqueen Feb 2, 2020
51f92dd
add an operation type to the yaml and wrap the existing pb
xmcqueen Feb 2, 2020
95f239f
add an operation type to the yaml and wrap the existing pb
xmcqueen Feb 2, 2020
383f9e8
checkpoint
xmcqueen Feb 3, 2020
d2bbeb4
checkpoint
xmcqueen Feb 3, 2020
be670f1
loader is working and tests are passing
xmcqueen Feb 3, 2020
1f8bfe5
all commands work and tests pass. next add zap then cleanup
xmcqueen Feb 4, 2020
fa9dc7a
factor
xmcqueen Feb 4, 2020
f38dda5
remove the old code
xmcqueen Feb 4, 2020
f08e1c6
deleted cruft
xmcqueen Feb 4, 2020
915a5b5
cleaning up names
xmcqueen Feb 4, 2020
f6c462e
cleaning up
xmcqueen Feb 4, 2020
8258385
add zap
xmcqueen Feb 5, 2020
47d28b1
add usage messages and zap
xmcqueen Feb 5, 2020
3467e72
readme
xmcqueen Feb 5, 2020
b3384e3
readme and go fmt
xmcqueen Feb 5, 2020
7e56aae
restore a deleted tab from the Makefile
xmcqueen Feb 5, 2020
bd3be5f
make the bottom return errors too
xmcqueen Feb 5, 2020
400bfe3
fix bug in apply yaml
xmcqueen Feb 6, 2020
79a82f0
reorg and fix peeker
xmcqueen Mar 11, 2020
40b339e
probably working again with cobra now
xmcqueen Mar 16, 2020
49bb639
tested with cobra and added some aliases
xmcqueen Mar 16, 2020
ee2e442
add delete namespace
xmcqueen Mar 16, 2020
17dffdc
move output to logger per comment
xmcqueen Mar 16, 2020
b0a787f
factor out the logger instantiation
xmcqueen Mar 16, 2020
79a1090
address pr comments
xmcqueen Mar 25, 2020
e9835db
go gmt and fix statusCode check
xmcqueen Mar 25, 2020
79a6805
fix peeker test
xmcqueen Mar 26, 2020
e0caad9
fix example yamls which didnt match the tests
xmcqueen Mar 26, 2020
02a952a
clean it up a bit
xmcqueen Mar 26, 2020
fcafd11
go fmt
xmcqueen Mar 26, 2020
0a613b2
tiny cleanup
xmcqueen Mar 26, 2020
d597f51
Merge branch 'master' into master
robskillington Apr 6, 2020
0a6e318
fixed nil data case in checkForAndHandleError error log line
xmcqueen Apr 7, 2020
63969be
Merge branch 'master' into master
robskillington Apr 14, 2020
c654e44
Address feedback
robskillington Apr 14, 2020
a11e6c1
Delete unused
robskillington Apr 14, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ TOOLS := \
verify_index_files \
carbon_load \
docs_test \
m3ctl \

.PHONY: setup
setup:
Expand All @@ -117,7 +118,7 @@ $(SERVICE)-linux-amd64:
.PHONY: $(SERVICE)-docker-dev
$(SERVICE)-docker-dev: clean-build $(SERVICE)-linux-amd64
mkdir -p ./bin/config

# Hacky way to find all configs and put into ./bin/config/
find ./src | fgrep config | fgrep ".yml" | xargs -I{} cp {} ./bin/config/
find ./src | fgrep config | fgrep ".yaml" | xargs -I{} cp {} ./bin/config/
Expand Down
2 changes: 1 addition & 1 deletion docs/operational_guide/placement_configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ curl -X POST localhost:7201/api/v1/services/m3db/placement/init -d '{
"endpoint": "<NODE_3_HOST_NAME>:<NODE_3_PORT>",
"hostname": "<NODE_3_HOST_NAME>",
"port": <NODE_3_PORT>
},
}
]
}'
```
Expand Down
71 changes: 71 additions & 0 deletions src/cmd/tools/m3ctl/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
M3DB Tool
========

This is a CLI tool to do some things that may be desirable for
cluster introspection, or for operational purposes.


Where configuration data is required its provided via YAML.

You can:

* create a database per the simplified database create API
* list namespaces
* delete namespaces
* list placements
* delete placements
* add nodes
* remove nodes

NOTE: This tool can delete namespaces and placements. It can be
quite hazardous if used without adequate understanding of your m3db
cluster's topology, or without a decent understanding of how m3db
works.

Examples
-------

# show help
m3ctl -h
# create a database
m3ctl db create -f ./database/examples/devel.yaml
# list namespaces
m3ctl ns
# delete a namespace
m3ctl ns delete -name default
# list placements
m3ctl pl
# point to some remote and list namespaces
m3ctl -endpoint http://localhost:7201 ns
# check the namespaces in a kubernetes cluster
# first setup a tunnel via kubectl port-forward ... 7201
m3ctl -endpoint http://localhost:7201 ns
# list the ids of the placements
m3ctl -endpoint http://localhost:7201 pl | jq .placement.instances[].id

Some example yaml files are provided in the examples directories.
Here's one for database creation:

---
type: cluster
namespace_name: default
retention_time: 168h
num_shards: 64
replication_factor: 1
hosts:
- id: m3db_seed
isolation_group: rack-a
zone: embedded
weight: 1024
endpoint: m3db_seed:9000
hostname: m3db_seed
port: 9000


See the examples directories below.

References
==========

[https://m3db.github.io/m3/operational_guide](operational guide)
[api docs](https://www.m3db.io/openapi/)
95 changes: 95 additions & 0 deletions src/cmd/tools/m3ctl/main/database/cmd.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package database

import (
"flag"
"fmt"
"github.com/m3db/m3/src/cmd/tools/m3ctl/main/errors"
"github.com/m3db/m3/src/x/config/configflag"
"os"
xmcqueen marked this conversation as resolved.
Show resolved Hide resolved
)

const (
defaultPath = "/api/v1/database"
)

type DatabaseFlagSets struct {
Database *flag.FlagSet
Create *flag.FlagSet
}

func SetupFlags(createDatabaseYAML *configflag.FlagStringSlice) DatabaseFlagSets {
databaseFlags := flag.NewFlagSet("db", flag.ExitOnError)
createFlags := flag.NewFlagSet("create", flag.ExitOnError)

databaseFlags.Usage = func() {
fmt.Fprintf(databaseFlags.Output(), `
This is the "%s" subcommand for database scoped operations.

It has the following subcommands:

%s

`, databaseFlags.Name(), createFlags.Name())
databaseFlags.PrintDefaults()
}

createFlags.Var(createDatabaseYAML, "f", "Path to yaml for simplified db creation with sane defaults.")
createFlags.Usage = func() {
fmt.Fprintf(databaseFlags.Output(), `
This is the "%s" subcommand for %s scoped operations.

Description:

This subcommand allows the creation of a new database from a yaml specification.

Usage of %s:

`, createFlags.Name(), databaseFlags.Name(), createFlags.Name())
createFlags.PrintDefaults()
}

return DatabaseFlagSets{Database: databaseFlags, Create: createFlags}
}

func ParseAndDo(arg *configflag.FlagStringSlice, flags *DatabaseFlagSets, ep string) {
args := flag.Args()
// right here args should be like "db create -f somefile"
if len(args) < 2 {
flags.Database.Usage()
os.Exit(1)
}
// pop and parse
if err := parseAndDoCreate(args[1:], arg, flags, ep, Create); err != nil {
fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
}
func parseAndDoCreate(args []string, finalArgs *configflag.FlagStringSlice, flags *DatabaseFlagSets, ep string, doer func(string, string)) error {
if err := flags.Database.Parse(args); err != nil {
flags.Database.Usage()
return err
}
if flags.Database.NArg() == 0 {
flags.Database.Usage()
return &errors.FlagsError{}
}
switch flags.Database.Arg(0) {
case flags.Create.Name():
// pop and parse
createArgs := flags.Database.Args()[1:]
if err := flags.Create.Parse(createArgs); err != nil {
flags.Create.Usage()
return err
}
if flags.Create.NFlag() == 0 {
flags.Create.Usage()
return &errors.FlagsError{}
}
// the below finalArgs.Value has at least one value by this time per the parser
doer(finalArgs.Value[len(finalArgs.Value)-1], ep)
return nil
default:
flags.Database.Usage()
return &errors.FlagsError{}
}
}
32 changes: 32 additions & 0 deletions src/cmd/tools/m3ctl/main/database/cmd_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package database

import (
"github.com/m3db/m3/src/x/config/configflag"
"testing"
)

func TestBasic(t *testing.T) {

createDatabaseYAML := configflag.FlagStringSlice{}
databaseFlagSets := SetupFlags(&createDatabaseYAML)

if e := parseAndDoCreate([]string{}, &createDatabaseYAML, &databaseFlagSets, "", func(string, string) { return }); e == nil {
t.Error("It should return error on no args")
}

createDatabaseYAML = configflag.FlagStringSlice{}
databaseFlagSets = SetupFlags(&createDatabaseYAML)

if e := parseAndDoCreate([]string{"create", "-f", "somefile"}, &createDatabaseYAML, &databaseFlagSets, "", func(string, string) { return }); e != nil {
t.Error("It should NOT return error on sane args")
}

if len(createDatabaseYAML.Value) != 1 {
t.Fatalf("db create filename len is wrong:expected:1:but got:%d:\n", len(createDatabaseYAML.Value))
}

if createDatabaseYAML.Value[0] != "somefile" {
t.Errorf("db create filename value is wrong:expected:%s:but got:%s:\n", createDatabaseYAML.Value[0], "somefile")
}

}
17 changes: 17 additions & 0 deletions src/cmd/tools/m3ctl/main/database/create.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package database

import (
"fmt"
"github.com/m3db/m3/src/cmd/tools/m3ctl/main/http"
"github.com/m3db/m3/src/cmd/tools/m3ctl/main/yaml"
"github.com/m3db/m3/src/query/generated/proto/admin"
"log"
)

func Create(createYAML string, endpoint string) {
log.Printf("createYAML:%s:\n", createYAML)
data := yaml.Load(createYAML, &admin.DatabaseCreateRequest{})
url := fmt.Sprintf("%s%s/create", endpoint, defaultPath)
http.DoPost(url, data, http.DoDump)
return
}
26 changes: 26 additions & 0 deletions src/cmd/tools/m3ctl/main/database/examples/create.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
type: cluster
namespace_name: 1week_namespace
retention_time: 168h
num_shards: 1024
replication_factor: 3
hosts:
- id: m3db001
isolationGroup: us-east1-a
zone: embedded
weight: 100
address: 10.142.0.1
port: 9000
- id: m3db002
isolationGroup: us-east1-b
zone: embedded
weight: 100
address: 10.142.0.2
port: 9000
- id: m3db003
isolationGroup: us-east1-c
zone: embedded
weight: 100
address: 10.142.0.3
port: 9000

15 changes: 15 additions & 0 deletions src/cmd/tools/m3ctl/main/database/examples/devel.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
type: cluster
namespace_name: default
retention_time: 168h
num_shards: 64
replication_factor: 1
hosts:
- id: m3db_seed
isolation_group: rack-a
zone: embedded
weight: 1024
endpoint: m3db_seed:9000
hostname: m3db_seed
port: 9000

12 changes: 12 additions & 0 deletions src/cmd/tools/m3ctl/main/errors/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package errors

type FlagsError struct {
Message string
}

func (e *FlagsError) Error() string {
if e == nil {
return ""
}
return e.Message
}
19 changes: 19 additions & 0 deletions src/cmd/tools/m3ctl/main/http/checker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package http
xmcqueen marked this conversation as resolved.
Show resolved Hide resolved

import (
"fmt"
"io/ioutil"
"log"
"net/http"
)

func checkForAndHandleError(url string, resp *http.Response) {
log.Printf("resp.StatusCode:%d:\n", resp.StatusCode)
if resp.StatusCode > 299 {
dat, _ := ioutil.ReadAll(resp.Body)
if dat != nil {
fmt.Println(string(dat))
}
log.Fatalf("error from m3db:%s:url:%s:", resp.Status, url)
}
}
76 changes: 76 additions & 0 deletions src/cmd/tools/m3ctl/main/http/http.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package http

import (
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"time"
)

const timeout = time.Duration(5 * time.Second)

func DoGet(url string, getter func(reader io.Reader)) {
log.Printf("DoGet:url:%s:\n", url)
client := http.Client{
Timeout: timeout,
}
resp, err := client.Get(url)
if err != nil {
log.Fatal(err)
}
defer func() {
ioutil.ReadAll(resp.Body)
resp.Body.Close()
}()
checkForAndHandleError(url, resp)
getter(resp.Body)
}

func DoPost(url string, data io.Reader, getter func(reader io.Reader)) {
log.Printf("DoPost:url:%s:\n", url)
client := &http.Client{
Timeout: timeout,
}
req, err := http.NewRequest(http.MethodPost, url, data)
req.Header.Add("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)

}
defer func() {
ioutil.ReadAll(resp.Body)
resp.Body.Close()
}()
checkForAndHandleError(url, resp)
getter(resp.Body)
}

func DoDelete(url string, getter func(reader io.Reader)) {
log.Printf("DoDelete:url:%s:\n", url)
client := &http.Client{
Timeout: timeout,
}
req, err := http.NewRequest(http.MethodDelete, url, nil)
req.Header.Add("Content-Type", "application/json")
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer func() {
ioutil.ReadAll(resp.Body)
resp.Body.Close()
}()
checkForAndHandleError(url, resp)
getter(resp.Body)
}

func DoDump(in io.Reader) {
dat, err := ioutil.ReadAll(in)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(dat))
}
Loading