You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
175 lines
4.7 KiB
175 lines
4.7 KiB
/*
|
|
* Minio Cloud Storage, (C) 2019 Minio, Inc.
|
|
*
|
|
* 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,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"crypto/tls"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
"time"
|
|
|
|
xhttp "github.com/minio/minio/cmd/http"
|
|
)
|
|
|
|
const (
|
|
initGraceTime = 300
|
|
healthPath = "/minio/health/live"
|
|
timeout = time.Duration(30 * time.Second)
|
|
minioProcess = "minio"
|
|
)
|
|
|
|
// returns container boot time by finding
|
|
// modtime of /proc/1 directory
|
|
func getStartTime() time.Time {
|
|
di, err := os.Stat("/proc/1")
|
|
if err != nil {
|
|
// Cant stat proc dir successfully, exit with error
|
|
log.Fatal(err.Error())
|
|
}
|
|
return di.ModTime()
|
|
}
|
|
|
|
// Returns the ip:port of the Minio process
|
|
// running in the container
|
|
func findEndpoint() string {
|
|
cmd := exec.Command("netstat", "-ntlp")
|
|
stdout, err := cmd.StdoutPipe()
|
|
if err != nil {
|
|
// error getting stdout pipe
|
|
log.Fatal(err.Error())
|
|
}
|
|
if err = cmd.Start(); err != nil {
|
|
// error starting the command
|
|
log.Fatal(err.Error())
|
|
}
|
|
// split netstat output in rows
|
|
scanner := bufio.NewScanner(stdout)
|
|
scanner.Split(bufio.ScanLines)
|
|
// loop over the rows to find Minio process
|
|
for scanner.Scan() {
|
|
if strings.Contains(scanner.Text(), minioProcess) {
|
|
line := scanner.Text()
|
|
newLine := strings.Replace(line, ":::", "127.0.0.1:", 1)
|
|
fields := strings.Fields(newLine)
|
|
// index 3 in the row has the Local address
|
|
// find the last index of ":" - address will
|
|
// have port number after this index
|
|
i := strings.LastIndex(fields[3], ":")
|
|
// split address and port
|
|
addr := fields[3][:i]
|
|
port := fields[3][i+1:]
|
|
// add surrounding [] for ip6 address
|
|
if strings.Count(addr, ":") > 0 {
|
|
addr = strings.Join([]string{"[", addr, "]"}, "")
|
|
}
|
|
// wait for cmd to complete before return
|
|
if err = cmd.Wait(); err != nil {
|
|
// command failed to run
|
|
log.Fatal(err.Error())
|
|
}
|
|
// return joint address and port
|
|
return strings.Join([]string{addr, port}, ":")
|
|
}
|
|
}
|
|
if err = cmd.Wait(); err != nil {
|
|
// command failed to run
|
|
log.Fatal(err.Error())
|
|
}
|
|
// minio process not found, exit with error
|
|
os.Exit(1)
|
|
return ""
|
|
}
|
|
|
|
func main() {
|
|
startTime := getStartTime()
|
|
|
|
// In distributed environment like Swarm, traffic is routed
|
|
// to a container only when it reports a `healthy` status. So, we exit
|
|
// with 0 to ensure healthy status till distributed Minio starts (120s).
|
|
|
|
// Refer: https://github.com/moby/moby/pull/28938#issuecomment-301753272
|
|
|
|
if (time.Now().Sub(startTime) / time.Second) < initGraceTime {
|
|
os.Exit(0)
|
|
} else {
|
|
endPoint := findEndpoint()
|
|
u, err := url.Parse(fmt.Sprintf("http://%s%s", endPoint, healthPath))
|
|
if err != nil {
|
|
// Could not parse URL successfully
|
|
log.Fatal(err.Error())
|
|
}
|
|
|
|
// Minio server may be using self-signed or CA certificates. To avoid
|
|
// making Docker setup complicated, we skip verifying certificates here.
|
|
// This is because, following request tests for health status within
|
|
// containerized environment, i.e. requests are always made to the Minio
|
|
// server running on the same host.
|
|
tr := &http.Transport{
|
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
|
|
}
|
|
client := &http.Client{Transport: tr, Timeout: timeout}
|
|
resp, err := client.Get(u.String())
|
|
if err != nil {
|
|
// GET failed exit
|
|
log.Fatal(err.Error())
|
|
}
|
|
if resp.StatusCode == http.StatusOK {
|
|
// Drain any response.
|
|
xhttp.DrainBody(resp.Body)
|
|
// exit with success
|
|
os.Exit(0)
|
|
}
|
|
bodyBytes, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
// Drain any response.
|
|
xhttp.DrainBody(resp.Body)
|
|
// GET failed exit
|
|
log.Fatal(err.Error())
|
|
}
|
|
bodyString := string(bodyBytes)
|
|
// Drain any response.
|
|
xhttp.DrainBody(resp.Body)
|
|
// This means sever is configured with https
|
|
if resp.StatusCode == http.StatusForbidden && bodyString == "SSL required" {
|
|
// Try with https
|
|
u.Scheme = "https"
|
|
resp, err = client.Get(u.String())
|
|
if err != nil {
|
|
// GET failed exit
|
|
log.Fatal(err.Error())
|
|
}
|
|
if resp.StatusCode == http.StatusOK {
|
|
// Drain any response.
|
|
xhttp.DrainBody(resp.Body)
|
|
// exit with success
|
|
os.Exit(0)
|
|
}
|
|
// Drain any response.
|
|
xhttp.DrainBody(resp.Body)
|
|
}
|
|
}
|
|
// Execution reaching here means none of
|
|
// the success cases were satisfied
|
|
os.Exit(1)
|
|
}
|
|
|