├── .github └── workflows │ └── go.yml ├── .gitignore ├── INSTALL.md ├── LICENSE ├── README.md ├── check_orchestrator.go ├── clusterHealth.go ├── clusterInfo.go ├── go.mod ├── go.sum └── status.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | release: 9 | types: [published] 10 | 11 | jobs: 12 | 13 | build: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v3 17 | 18 | - name: Set up Go 19 | uses: actions/setup-go@v3 20 | with: 21 | go-version: 1.18 22 | 23 | - name: Build 24 | run: go build -v . 25 | 26 | - name: Test 27 | run: go test -v . 28 | 29 | - name: Upload binary artifact 30 | uses: actions/upload-artifact@v1 31 | with: 32 | name: check-orchestrator 33 | path: check-orchestrator 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | check_orchestrator 2 | check-orchestrator 3 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | # Install 2 | 3 | ## Prerequisites 4 | 5 | You need a working [Go](https://golang.org/doc/install) installation. You will have to set a GOPATH. 6 | 7 | ## How to build 8 | 9 | - Go to you $GOPATH 10 | - `git clone git@github.com:mcrauwel/go-check-orchestrator.git src/check_orchestrator` 11 | - `cd src/check_orchestrator` 12 | - `go get` 13 | - `go build` 14 | 15 | Your binary will be placed in $GOPATH/bin 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Matthias Crauwels / Ghent University 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # check_orchestrator 2 | [![Go](https://github.com/mcrauwel/go-check-orchestrator/actions/workflows/go.yml/badge.svg)](https://github.com/mcrauwel/go-check-orchestrator/actions/workflows/go.yml) 3 | 4 | This repository contains a Nagios / Icinga check to monitor [Orchestrator](https://github.com/openark/orchestrator). 5 | 6 | This check was written by Matthias Crauwels at Ghent University. It is published with an [MIT license](LICENSE) 7 | 8 | ## Usage 9 | ``` 10 | $ bin/check_orchestrator 11 | Usage: 12 | check_orchestrator [subcommand] [OPTIONS] 13 | SubCommands: 14 | clusterhealth 15 | clusterinfo 16 | status 17 | ``` 18 | 19 | ## Commands 20 | ### status 21 | #### Usage 22 | ``` 23 | $ bin/check_orchestrator status -h 24 | Usage: 25 | check_orchestrator status [OPTIONS] 26 | Application Options: 27 | -H, --host= Hostname (default: localhost) 28 | -p, --port= Port (default: 3000) 29 | -S, --ssl Use SSL 30 | -I, --insecure Do not check SSL cert 31 | -U, --uri= URI (default: api/health) 32 | --http-auth-name Name for http auth 33 | --http-auth-password Password for http auth 34 | Help Options: 35 | -h, --help Show this help message 36 | ``` 37 | 38 | #### Success 39 | ``` 40 | $ bin/check_orchestrator status 41 | ORCHESTRATOR_STATUS OK: Application node is healthy 42 | ``` 43 | 44 | #### Errors 45 | ``` 46 | $ bin/check_orchestrator status 47 | ORCHESTRATOR_STATUS CRITICAL: Application node is unhealthy dial tcp 127.0.0.1:20192: getsockopt: connection refused 48 | ``` 49 | 50 | ### clusterinfo 51 | #### Usage 52 | ``` 53 | $ bin/check_orchestrator clusterinfo -h 54 | Usage: 55 | check_orchestrator clusterinfo [OPTIONS] 56 | Application Options: 57 | -H, --host= Hostname (default: localhost) 58 | -p, --port= Port (default: 3000) 59 | -S, --ssl Use SSL 60 | -I, --insecure Do not check SSL cert 61 | -U, --uri= URI (default: api/clusters-info) 62 | --http-auth-name Name for http auth 63 | --http-auth-password Password for http auth 64 | Help Options: 65 | -h, --help Show this help message 66 | ``` 67 | 68 | #### Success 69 | ``` 70 | $ bin/check_orchestrator clusterinfo 71 | ORCHESTRATOR_CLUSTERINFO OK: This instance manages following clusters: 127.0.0.1:20192 (HasAutomatedMasterRecovery = false) (HasAutomtedIntermediateMasterRecovery = false), localhost:20192 (HasAutomatedMasterRecovery = false) (HasAutomtedIntermediateMasterRecovery = false) 72 | ``` 73 | #### Errors 74 | - Orchestrator has no clusters configured 75 | ``` 76 | $ /tmp/check_orchestrator clusterinfo 77 | ORCHESTRATOR_CLUSTERINFO WARNING: This Orchestrator is responding correctly but is not managing any clusters. 78 | ``` 79 | 80 | ### clusterhealth 81 | #### Usage 82 | ``` 83 | $ bin/check_orchestrator clusterhealth --help 84 | Usage: 85 | check_orchestrator clusterhealth --alias= [OPTIONS] 86 | Application Options: 87 | -a, --alias= ClusterAlias 88 | -H, --host= Hostname (default: localhost) 89 | -p, --port= Port (default: 3000) 90 | -S, --ssl Use SSL 91 | -I, --insecure Do not check SSL cert 92 | -t, --timeout= Timeout for SecondsSinceLastSeen (default: 300) 93 | -w, --lag-warning= Slave lag warning threshold (default: 300) 94 | -c, --lag-critical= Slave lag critical threshold (default: 600) 95 | --http-auth-name Name for http auth 96 | --http-auth-password Password for http auth 97 | Help Options: 98 | -h, --help Show this help message 99 | ``` 100 | 101 | #### Success 102 | ``` 103 | $ bin/check_orchestrator clusterhealth --alias=127.0.0.1:20192 104 | ORCHESTRATOR_CLUSTERHEALTH OK: Cluster 127.0.0.1:20192 is doing OK 105 | ``` 106 | 107 | #### Errors 108 | - no alias 109 | ``` 110 | $ bin/check_orchestrator clusterhealth 111 | the required flag `-a, --alias' was not specified 112 | ``` 113 | 114 | - multiple writers 115 | ``` 116 | $ bin/check_orchestrator clusterhealth --alias=127.0.0.1:20192 117 | ORCHESTRATOR_CLUSTERHEALTH CRITICAL: [SPLIT BRAIN] There are 2 writable servers in cluster 127.0.0.1:20192 118 | ``` 119 | 120 | - Slave thread(s) not running 121 | ``` 122 | $ bin/check_orchestrator clusterhealth --alias=127.0.0.1:20192 123 | ORCHESTRATOR_CLUSTERHEALTH CRITICAL: In cluster 127.0.0.1:20192 the Slave_IO-thread is not running on host 127.0.0.1:20195 124 | ``` 125 | ``` 126 | $ bin/check_orchestrator clusterhealth --alias=127.0.0.1:20192 127 | ORCHESTRATOR_CLUSTERHEALTH CRITICAL: In cluster 127.0.0.1:20192 the Slave_SQL-thread is not running on host 127.0.0.1:20195 128 | ``` 129 | 130 | - Slave lag 131 | ``` 132 | $ bin/check_orchestrator clusterhealth --alias 127.0.0.1:20192 -w 30 -c 60 133 | ORCHESTRATOR_CLUSTERHEALTH WARNING: In cluster 127.0.0.1:20192 host 127.0.0.1:20195 is 53 seconds lagging (warning threshold 30) 134 | 135 | $ bin/check_orchestrator clusterhealth --alias 127.0.0.1:20192 -w 30 -c 60 136 | ORCHESTRATOR_CLUSTERHEALTH CRITICAL: In cluster 127.0.0.1:20192 host 127.0.0.1:20195 is 65 seconds lagging (critical threshold 60) 137 | ``` 138 | 139 | *note* the clusterhealth-command take the `downtime` setting in Orchestrator into account... 140 | 141 | ## Nagios Installation 142 | 143 | ### Configuration 144 | 145 | Assuming a standard installation of Nagios, the plugin can be executed from the machine that Nagios is running on. 146 | 147 | ``` 148 | cp check_orchestrator /usr/local/nagios/libexec/plugins/check_orchestrator 149 | chmod +x /usr/local/nagios/libexec/plugins/check_orchestrator 150 | ``` 151 | 152 | Add the following service definition to your server config: 153 | 154 | ``` 155 | define service { 156 | use local-service 157 | host_name localhost 158 | service_description 159 | check_command 160 | } 161 | ``` 162 | 163 | Add the following command definition to your commands config (commands.config): 164 | 165 | 166 | ``` 167 | define command{ 168 | command_name 169 | command_line /usr/local/nagios/libexec/plugins/check_orchestrator 170 | } 171 | ``` 172 | 173 | More info about options in Commands. 174 | -------------------------------------------------------------------------------- /check_orchestrator.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "crypto/tls" 5 | "fmt" 6 | "net/http" 7 | "os" 8 | "strings" 9 | 10 | "github.com/mackerelio/checkers" 11 | ) 12 | 13 | var commands = map[string](func([]string) *checkers.Checker){ 14 | // "replication": checkReplication, 15 | "clusterhealth": checkClusterHealth, 16 | "clusterinfo": checkClusterInfo, 17 | "status": checkStatus, 18 | } 19 | 20 | type StatusResponse struct { 21 | Code string 22 | Message string 23 | Details []string 24 | } 25 | 26 | type orchestratorOpts struct { 27 | Host string `short:"H" long:"host" default:"localhost" description:"Hostname"` 28 | Port string `short:"p" long:"port" default:"3000" description:"Port"` 29 | SSL bool `short:"S" long:"ssl" description:"Use SSL"` 30 | NoCert bool `short:"I" long:"insecure" description:"Do not check SSL cert"` 31 | HttpAuthName string `long:"http-auth-name" description:"Http authorization name"` 32 | HttpAuthPass string `long:"http-auth-password" description:"Http authorization password"` 33 | } 34 | 35 | func separateSub(argv []string) (string, []string) { 36 | if len(argv) == 0 || strings.HasPrefix(argv[0], "-") { 37 | return "", argv 38 | } 39 | return argv[0], argv[1:] 40 | } 41 | 42 | func sslPrefix(useSSL bool) string { 43 | if useSSL { 44 | return "https" 45 | } 46 | 47 | return "http" 48 | } 49 | 50 | func getHttpTransport(allowInsecure bool) *http.Transport { 51 | return &http.Transport{ 52 | TLSClientConfig: &tls.Config{InsecureSkipVerify: allowInsecure}, 53 | } 54 | } 55 | 56 | func main() { 57 | subCmd, argv := separateSub(os.Args[1:]) 58 | fn, ok := commands[subCmd] 59 | if !ok { 60 | fmt.Println(`Usage: 61 | check_orchestrator [subcommand] [OPTIONS] 62 | SubCommands:`) 63 | for k := range commands { 64 | fmt.Printf(" %s\n", k) 65 | } 66 | os.Exit(1) 67 | } 68 | 69 | ckr := fn(argv) 70 | //fmt.Println(result) 71 | ckr.Name = fmt.Sprintf("ORCHESTRATOR_%s", strings.ToUpper(subCmd)) 72 | ckr.Exit() 73 | 74 | } 75 | -------------------------------------------------------------------------------- /clusterHealth.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "os" 9 | 10 | "github.com/jessevdk/go-flags" 11 | "github.com/mackerelio/checkers" 12 | ) 13 | 14 | type ClusterDetailResponse struct { 15 | Key ServerKey 16 | InstanceAlias string 17 | Uptime int 18 | ServerID int 19 | ServerUUID string 20 | Version string 21 | VersonComment string 22 | FlavorName string 23 | ReadOnly bool 24 | MasterKey ServerKey 25 | IsDetachedMaster bool 26 | Slave_SQL_Running bool 27 | Slave_IO_Running bool 28 | IsDetached bool 29 | SecondsBehindMaster JsonInt64 30 | SlaveLagSeconds JsonInt64 31 | SecondsSinceLastSeen JsonInt64 32 | IsDowntimed bool 33 | SQLDelay int 34 | } 35 | 36 | type ServerKey struct { 37 | Hostname string 38 | Port int 39 | } 40 | 41 | type JsonInt64 struct { 42 | Int64 int64 43 | Valid bool 44 | } 45 | 46 | type clusterHealtOpts struct { 47 | ClusterAlias string `required:"true" short:"a" long:"alias" description:"ClusterAlias"` 48 | ClusterNodesCount int `short:"n" long:"nodes" default:"0" description:"Number of nodes in cluster"` 49 | orchestratorOpts 50 | SecondsSinceLastSeenThreshold int64 `short:"t" long:"timeout" default:"300" description:"Timeout for SecondsSinceLastSeen"` 51 | SlaveLagWarningThreshold int64 `short:"w" long:"lag-warning" default:"300" description:"Slave lag warning threshold"` 52 | SlaveLagCriticalThreshold int64 `short:"c" long:"lag-critical" default:"600" description:"Slave lag critical threshold"` 53 | } 54 | 55 | func checkClusterHealth(args []string) *checkers.Checker { 56 | 57 | opts := clusterHealtOpts{} 58 | psr := flags.NewParser(&opts, flags.Default) 59 | psr.Usage = "clusterhealth --alias= [OPTIONS]" 60 | _, err := psr.ParseArgs(args) 61 | if err != nil { 62 | os.Exit(1) 63 | } 64 | 65 | clusterAlias := opts.ClusterAlias 66 | uri := fmt.Sprintf("%s://%s:%s/api/cluster/alias/%s", sslPrefix(opts.SSL), opts.Host, opts.Port, clusterAlias) 67 | client := &http.Client{Transport: getHttpTransport(opts.NoCert)} 68 | req, err := http.NewRequest("GET", uri, nil) 69 | if err != nil { 70 | return checkers.NewChecker(checkers.UNKNOWN, fmt.Sprintf("Could not connect to Orchestrator API on %s", uri)) 71 | } 72 | if opts.HttpAuthName != "" && opts.HttpAuthPass != "" { 73 | req.SetBasicAuth(opts.HttpAuthName, opts.HttpAuthPass) 74 | } 75 | resp, err := client.Do(req) 76 | if err != nil { 77 | return checkers.NewChecker(checkers.UNKNOWN, fmt.Sprintf("Could not connect to Orchestrator API on %s", uri)) 78 | } 79 | defer resp.Body.Close() 80 | body, err := ioutil.ReadAll(resp.Body) 81 | 82 | // check first if we might have gotten a StatusResponse instead of a ClusterDetailResponse (example: "No cluster found for alias") 83 | var status StatusResponse 84 | err = json.Unmarshal(body, &status) 85 | 86 | if err == nil { 87 | msg := status.Message 88 | checkSt := checkers.OK 89 | 90 | if status.Code != "OK" { 91 | checkSt = checkers.CRITICAL 92 | } 93 | 94 | return checkers.NewChecker(checkSt, msg) 95 | } 96 | 97 | // The response was not a StatusResponse, so try to process is as a ClusterDetailResponse 98 | var r []ClusterDetailResponse 99 | err = json.Unmarshal(body, &r) 100 | 101 | if err != nil { 102 | return checkers.NewChecker(checkers.UNKNOWN, fmt.Sprintf("Could read content for the Orchestrator API on %s\n%s", uri, err)) 103 | } 104 | 105 | nrOfWriters := 0 106 | nrOfNodes := len(r) 107 | for _, s := range r { 108 | if !s.ReadOnly { 109 | nrOfWriters++ 110 | } 111 | } 112 | 113 | if opts.ClusterNodesCount != 0 && nrOfNodes != opts.ClusterNodesCount { 114 | return checkers.NewChecker(checkers.CRITICAL, 115 | fmt.Sprintf("[NODE COUNT] There are %d nodes in cluster %s, but %d expected", nrOfNodes, clusterAlias, opts.ClusterNodesCount)) 116 | } 117 | 118 | if nrOfWriters > 1 { 119 | return checkers.NewChecker(checkers.CRITICAL, 120 | fmt.Sprintf("[SPLIT BRAIN] There are %d writable servers in cluster %s", nrOfWriters, clusterAlias)) 121 | } 122 | 123 | if nrOfWriters < 1 { 124 | return checkers.NewChecker(checkers.CRITICAL, 125 | fmt.Sprintf("[READ ONLY CLUSTER] There are %d writable servers in cluster %s", nrOfWriters, clusterAlias)) 126 | } 127 | 128 | for _, s := range r { 129 | if s.IsDowntimed { 130 | continue 131 | } 132 | 133 | if s.MasterKey.Hostname != "" { 134 | if !s.Slave_IO_Running { 135 | return checkers.NewChecker(checkers.CRITICAL, 136 | fmt.Sprintf("In cluster %s the Slave_IO-thread is not running on host %s:%d", 137 | clusterAlias, s.Key.Hostname, s.Key.Port)) 138 | } 139 | 140 | if !s.Slave_SQL_Running { 141 | return checkers.NewChecker(checkers.CRITICAL, 142 | fmt.Sprintf("In cluster %s the Slave_SQL-thread is not running on host %s:%d", 143 | clusterAlias, s.Key.Hostname, s.Key.Port)) 144 | } 145 | } 146 | 147 | slaveLagSeconds := s.SlaveLagSeconds.Int64 148 | if s.SQLDelay > 0 { 149 | slaveLagSeconds = s.SlaveLagSeconds.Int64 - int64(s.SQLDelay) 150 | } 151 | if slaveLagSeconds > opts.SlaveLagCriticalThreshold { 152 | return checkers.NewChecker(checkers.CRITICAL, 153 | fmt.Sprintf("In cluster %s host %s:%d is %d seconds lagging (critical threshold %d)", 154 | clusterAlias, s.Key.Hostname, s.Key.Port, s.SlaveLagSeconds.Int64, opts.SlaveLagCriticalThreshold)) 155 | } 156 | 157 | if slaveLagSeconds > opts.SlaveLagWarningThreshold { 158 | return checkers.NewChecker(checkers.WARNING, 159 | fmt.Sprintf("In cluster %s host %s:%d is %d seconds lagging (warning threshold %d)", 160 | clusterAlias, s.Key.Hostname, s.Key.Port, s.SlaveLagSeconds.Int64, opts.SlaveLagWarningThreshold)) 161 | } 162 | 163 | if s.SecondsSinceLastSeen.Int64 > opts.SecondsSinceLastSeenThreshold { 164 | return checkers.NewChecker(checkers.WARNING, 165 | fmt.Sprintf("In cluster %s the host %s:%d was not seen for %d seconds (warning limit %d)", 166 | clusterAlias, s.Key.Hostname, s.Key.Port, s.SecondsSinceLastSeen.Int64, opts.SecondsSinceLastSeenThreshold)) 167 | } 168 | } 169 | 170 | return checkers.NewChecker(checkers.OK, fmt.Sprintf("Cluster %s is doing OK", clusterAlias)) 171 | } 172 | -------------------------------------------------------------------------------- /clusterInfo.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "os" 9 | "strings" 10 | 11 | "github.com/jessevdk/go-flags" 12 | "github.com/mackerelio/checkers" 13 | ) 14 | 15 | type clusterInfoOpts struct { 16 | orchestratorOpts 17 | URI string `short:"U" long:"uri" default:"api/clusters-info" description:"URI"` 18 | } 19 | 20 | type ClusterInfoResponse struct { 21 | ClusterName string 22 | ClusterAlias string 23 | ClusterDomain string 24 | CountInstances int 25 | HeuristicLag int 26 | HasAutomatedMasterRecovery bool 27 | HasAutomatedIntermediateMasterRecovery bool 28 | } 29 | 30 | func checkClusterInfo(args []string) *checkers.Checker { 31 | 32 | opts := clusterInfoOpts{} 33 | psr := flags.NewParser(&opts, flags.Default) 34 | psr.Usage = "clusterinfo [OPTIONS]" 35 | _, err := psr.ParseArgs(args) 36 | if err != nil { 37 | os.Exit(1) 38 | } 39 | uri := fmt.Sprintf("%s://%s:%s/%s", sslPrefix(opts.SSL), opts.Host, opts.Port, opts.URI) 40 | client := &http.Client{Transport: getHttpTransport(opts.NoCert)} 41 | req, err := http.NewRequest("GET", uri, nil) 42 | if err != nil { 43 | return checkers.NewChecker(checkers.UNKNOWN, fmt.Sprintf("Could not connect to Orchestrator API on %s", uri)) 44 | } 45 | if opts.HttpAuthName != "" && opts.HttpAuthPass != "" { 46 | req.SetBasicAuth(opts.HttpAuthName, opts.HttpAuthPass) 47 | } 48 | resp, err := client.Do(req) 49 | if err != nil { 50 | return checkers.NewChecker(checkers.UNKNOWN, fmt.Sprintf("Could not connect to Orchestrator API on %s", uri)) 51 | } 52 | defer resp.Body.Close() 53 | body, err := ioutil.ReadAll(resp.Body) 54 | 55 | // check first if we might have gotten a StatusResponse instead of a ClusterInfoResponse 56 | var status StatusResponse 57 | err = json.Unmarshal(body, &status) 58 | 59 | if err == nil { 60 | msg := status.Message 61 | checkSt := checkers.OK 62 | 63 | if status.Code != "OK" { 64 | checkSt = checkers.CRITICAL 65 | } 66 | 67 | return checkers.NewChecker(checkSt, msg) 68 | } 69 | 70 | // The response was not a StatusResponse, so try to process is as a ClusterInfoResponse 71 | var r []ClusterInfoResponse 72 | err = json.Unmarshal(body, &r) 73 | 74 | if err != nil { 75 | return checkers.NewChecker(checkers.UNKNOWN, fmt.Sprintf("Could read content for the Orchestrator API on %s\n%s", uri, err)) 76 | } 77 | 78 | var aliases []string 79 | var aliasdetails []string 80 | for _, s := range r { 81 | alias := fmt.Sprintf("%s (HasAutomatedMasterRecovery = %t) (HasAutomtedIntermediateMasterRecovery = %t)", 82 | s.ClusterAlias, s.HasAutomatedMasterRecovery, s.HasAutomatedIntermediateMasterRecovery) 83 | 84 | aliases = append(aliases, s.ClusterAlias) 85 | aliasdetails = append(aliasdetails, alias) 86 | } 87 | 88 | if len(aliases) > 0 { 89 | return checkers.NewChecker(checkers.OK, fmt.Sprintf("This instance manages following clusters: %s\n%s", strings.Join(aliases, ", "), strings.Join(aliasdetails, "\n"))) 90 | } 91 | 92 | return checkers.NewChecker(checkers.WARNING, "This Orchestrator is responding correctly but is not managing any clusters.") 93 | } 94 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module check-orchestrator 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/jessevdk/go-flags v1.5.0 7 | github.com/mackerelio/checkers v0.0.4 8 | ) 9 | 10 | require golang.org/x/sys v0.1.0 // indirect 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= 2 | github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= 3 | github.com/mackerelio/checkers v0.0.4 h1:dLxl3szIA1uW/+pFefamBPaFT9MCKkdH3uQND7c64bk= 4 | github.com/mackerelio/checkers v0.0.4/go.mod h1:VEf9gFHvpvH7Zvcwjuj7x3ozQg5w3En6ww9UcWoWHeE= 5 | golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 6 | golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= 7 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 8 | -------------------------------------------------------------------------------- /status.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | "net/http" 8 | "os" 9 | //"strings" 10 | 11 | "github.com/jessevdk/go-flags" 12 | "github.com/mackerelio/checkers" 13 | ) 14 | 15 | type statusOpts struct { 16 | orchestratorOpts 17 | URI string `short:"U" long:"uri" default:"api/health" description:"URI"` 18 | } 19 | 20 | func checkStatus(args []string) *checkers.Checker { 21 | opts := statusOpts{} 22 | psr := flags.NewParser(&opts, flags.Default) 23 | psr.Usage = "status [OPTIONS]" 24 | _, err := psr.ParseArgs(args) 25 | if err != nil { 26 | os.Exit(1) 27 | } 28 | 29 | uri := fmt.Sprintf("%s://%s:%s/%s", sslPrefix(opts.SSL), opts.Host, opts.Port, opts.URI) 30 | client := &http.Client{Transport: getHttpTransport(opts.NoCert)} 31 | 32 | req, err := http.NewRequest("GET", uri, nil) 33 | if err != nil { 34 | return checkers.NewChecker(checkers.UNKNOWN, fmt.Sprintf("Could not connect to Orchestrator API on %s", uri)) 35 | } 36 | if opts.HttpAuthName != "" && opts.HttpAuthPass != "" { 37 | req.SetBasicAuth(opts.HttpAuthName, opts.HttpAuthPass) 38 | } 39 | resp, err := client.Do(req) 40 | 41 | if err != nil { 42 | return checkers.NewChecker(checkers.UNKNOWN, fmt.Sprintf("Could not connect to Orchestrator API on %s", uri)) 43 | } 44 | defer resp.Body.Close() 45 | body, err := ioutil.ReadAll(resp.Body) 46 | 47 | var r StatusResponse 48 | json.Unmarshal(body, &r) 49 | 50 | msg := r.Message 51 | checkSt := checkers.OK 52 | 53 | if r.Code != "OK" { 54 | checkSt = checkers.CRITICAL 55 | } 56 | 57 | return checkers.NewChecker(checkSt, msg) 58 | } 59 | --------------------------------------------------------------------------------