2016-02-22 18:52:20 +00:00
|
|
|
/*
|
|
|
|
Copyright 2016 The Kubernetes Authors All rights reserved.
|
|
|
|
|
|
|
|
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 e2e_node
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"flag"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/golang/glog"
|
|
|
|
)
|
|
|
|
|
2016-03-04 22:07:35 +00:00
|
|
|
var serverStartTimeout = flag.Duration("server-start-timeout", time.Second*120, "Time to wait for each server to become healthy.")
|
2016-02-22 18:52:20 +00:00
|
|
|
|
|
|
|
type e2eService struct {
|
|
|
|
etcdCmd *exec.Cmd
|
|
|
|
etcdCombinedOut bytes.Buffer
|
|
|
|
etcdDataDir string
|
|
|
|
apiServerCmd *exec.Cmd
|
|
|
|
apiServerCombinedOut bytes.Buffer
|
|
|
|
kubeletCmd *exec.Cmd
|
|
|
|
kubeletCombinedOut bytes.Buffer
|
2016-02-24 21:12:42 +00:00
|
|
|
nodeName string
|
2016-02-22 18:52:20 +00:00
|
|
|
}
|
|
|
|
|
2016-02-24 21:12:42 +00:00
|
|
|
func newE2eService(nodeName string) *e2eService {
|
|
|
|
return &e2eService{nodeName: nodeName}
|
2016-02-22 18:52:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (es *e2eService) start() error {
|
|
|
|
if _, err := getK8sBin("kubelet"); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if _, err := getK8sBin("kube-apiserver"); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
cmd, err := es.startEtcd()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
es.etcdCmd = cmd
|
|
|
|
|
|
|
|
cmd, err = es.startApiServer()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
es.apiServerCmd = cmd
|
|
|
|
|
|
|
|
cmd, err = es.startKubeletServer()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
es.kubeletCmd = cmd
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (es *e2eService) stop() {
|
|
|
|
if es.kubeletCmd != nil {
|
|
|
|
err := es.kubeletCmd.Process.Kill()
|
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("Failed to stop kubelet.\n%v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if es.apiServerCmd != nil {
|
|
|
|
err := es.apiServerCmd.Process.Kill()
|
|
|
|
if err != nil {
|
2016-04-18 20:56:07 +00:00
|
|
|
glog.Errorf("Failed to stop kube-apiserver.\n%v", err)
|
2016-02-22 18:52:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if es.etcdCmd != nil {
|
|
|
|
err := es.etcdCmd.Process.Kill()
|
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("Failed to stop etcd.\n%v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if es.etcdDataDir != "" {
|
|
|
|
err := os.RemoveAll(es.etcdDataDir)
|
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("Failed to delete etcd data directory %s.\n%v", es.etcdDataDir, err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (es *e2eService) startEtcd() (*exec.Cmd, error) {
|
|
|
|
dataDir, err := ioutil.TempDir("", "node-e2e")
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
es.etcdDataDir = dataDir
|
2016-02-26 23:06:25 +00:00
|
|
|
cmd := exec.Command("etcd")
|
|
|
|
// Execute etcd in the data directory instead of using --data-dir because the flag sometimes requires additional
|
|
|
|
// configuration (e.g. --name in version 0.4.9)
|
|
|
|
cmd.Dir = es.etcdDataDir
|
|
|
|
hcc := newHealthCheckCommand(
|
|
|
|
"http://127.0.0.1:4001/v2/keys/", // Trailing slash is required,
|
|
|
|
cmd,
|
|
|
|
&es.etcdCombinedOut)
|
|
|
|
return cmd, es.startServer(hcc)
|
2016-02-22 18:52:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (es *e2eService) startApiServer() (*exec.Cmd, error) {
|
2016-02-26 23:06:25 +00:00
|
|
|
cmd := exec.Command("sudo", getApiServerBin(),
|
|
|
|
"--v", "2", "--logtostderr", "--log_dir", "./",
|
|
|
|
"--etcd-servers", "http://127.0.0.1:4001",
|
|
|
|
"--insecure-bind-address", "0.0.0.0",
|
|
|
|
"--service-cluster-ip-range", "10.0.0.1/24",
|
2016-04-07 00:11:13 +00:00
|
|
|
"--kubelet-port", "10250",
|
|
|
|
"--allow-privileged", "true",
|
|
|
|
)
|
2016-02-26 23:06:25 +00:00
|
|
|
hcc := newHealthCheckCommand(
|
|
|
|
"http://127.0.0.1:8080/healthz",
|
|
|
|
cmd,
|
|
|
|
&es.apiServerCombinedOut)
|
|
|
|
return cmd, es.startServer(hcc)
|
2016-02-22 18:52:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (es *e2eService) startKubeletServer() (*exec.Cmd, error) {
|
2016-02-26 23:06:25 +00:00
|
|
|
cmd := exec.Command("sudo", getKubeletServerBin(),
|
|
|
|
"--v", "2", "--logtostderr", "--log_dir", "./",
|
|
|
|
"--api-servers", "http://127.0.0.1:8080",
|
|
|
|
"--address", "0.0.0.0",
|
|
|
|
"--port", "10250",
|
2016-03-14 18:18:27 +00:00
|
|
|
"--hostname-override", es.nodeName, // Required because hostname is inconsistent across hosts
|
|
|
|
"--volume-stats-agg-period", "10s", // Aggregate volumes frequently so tests don't need to wait as long
|
2016-04-07 00:11:13 +00:00
|
|
|
"--allow-privileged", "true",
|
2016-03-14 18:18:27 +00:00
|
|
|
)
|
2016-02-26 23:06:25 +00:00
|
|
|
hcc := newHealthCheckCommand(
|
|
|
|
"http://127.0.0.1:10255/healthz",
|
|
|
|
cmd,
|
|
|
|
&es.kubeletCombinedOut)
|
|
|
|
return cmd, es.startServer(hcc)
|
2016-02-22 18:52:20 +00:00
|
|
|
}
|
|
|
|
|
2016-02-26 23:06:25 +00:00
|
|
|
func (es *e2eService) startServer(cmd *healthCheckCommand) error {
|
2016-02-22 18:52:20 +00:00
|
|
|
cmdErrorChan := make(chan error)
|
|
|
|
go func() {
|
|
|
|
err := cmd.Run()
|
|
|
|
if err != nil {
|
2016-04-15 19:18:27 +00:00
|
|
|
cmdErrorChan <- fmt.Errorf("%s Failed with error \"%v\". Command output:\n%v", cmd, err, *cmd.OutputBuffer)
|
2016-02-22 18:52:20 +00:00
|
|
|
}
|
|
|
|
close(cmdErrorChan)
|
|
|
|
}()
|
|
|
|
|
|
|
|
endTime := time.Now().Add(*serverStartTimeout)
|
|
|
|
for endTime.After(time.Now()) {
|
|
|
|
select {
|
|
|
|
case err := <-cmdErrorChan:
|
2016-02-26 23:06:25 +00:00
|
|
|
return err
|
2016-02-22 18:52:20 +00:00
|
|
|
case <-time.After(time.Second):
|
2016-02-26 23:06:25 +00:00
|
|
|
resp, err := http.Get(cmd.HealthCheckUrl)
|
2016-02-22 18:52:20 +00:00
|
|
|
if err == nil && resp.StatusCode == http.StatusOK {
|
2016-02-26 23:06:25 +00:00
|
|
|
return nil
|
2016-02-22 18:52:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-02-26 23:06:25 +00:00
|
|
|
return fmt.Errorf("Timeout waiting for service %s", cmd)
|
2016-02-22 18:52:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type healthCheckCommand struct {
|
2016-02-26 23:06:25 +00:00
|
|
|
*exec.Cmd
|
|
|
|
HealthCheckUrl string
|
|
|
|
OutputBuffer *bytes.Buffer
|
|
|
|
}
|
|
|
|
|
|
|
|
func newHealthCheckCommand(healthCheckUrl string, cmd *exec.Cmd, combinedOutput *bytes.Buffer) *healthCheckCommand {
|
|
|
|
cmd.Stdout = combinedOutput
|
|
|
|
cmd.Stderr = combinedOutput
|
|
|
|
return &healthCheckCommand{
|
|
|
|
HealthCheckUrl: healthCheckUrl,
|
|
|
|
Cmd: cmd,
|
|
|
|
OutputBuffer: combinedOutput,
|
|
|
|
}
|
2016-02-22 18:52:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (hcc *healthCheckCommand) String() string {
|
2016-02-26 23:06:25 +00:00
|
|
|
return fmt.Sprintf("`%s %s` health-check: %s", hcc.Path, strings.Join(hcc.Args, " "), hcc.HealthCheckUrl)
|
2016-02-22 18:52:20 +00:00
|
|
|
}
|