Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
46 changes: 38 additions & 8 deletions healthcheck/healthcheck.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,16 @@ func (c *Checker) checkHTTP(result chan bool) {
return
}

// Check status code: should be between 200 and 399
if response.StatusCode < 200 || response.StatusCode > 399 {
// Check status code within the taskinfo statuses field
var customStatusFound = false
for _, customStatus := range c.TaskInfo.GetHealthCheck().GetHTTP().GetStatuses() {
if uint32(response.StatusCode) == customStatus {
customStatusFound = true
break
}
}

if (response.StatusCode < 200 || response.StatusCode > 399) && !customStatusFound {
logger.GetInstance().Error("Unexpected status code",
zap.Int("code", response.StatusCode),
)
Expand Down Expand Up @@ -244,19 +252,41 @@ func (c *Checker) checkTCP(result chan bool) {
// checkCommand enters the container mount namespace and
// executes the given command using the containerizer
func (c *Checker) checkCommand(result chan bool) {
var cmd []string
wrapperCmd := []string{"su"}
command := c.TaskInfo.GetHealthCheck().GetCommand()
// Must be wrapped into a shell process
if command.GetShell() {
cmd = []string{"/bin/sh", "-c", command.GetValue()}
} else {
cmd = []string{command.GetValue()}
wrapperCmd = append(wrapperCmd, []string{"-s", "/bin/sh"}...)
}

// Add user. Use root if no user specified
var user = command.GetUser()
if user == "" {
user = "root"
}
wrapperCmd = append(wrapperCmd, []string{"-", user}...)
// Construct the command that should be wrapped
var cmd []string
// Add environment variable
if len(command.GetEnvironment().GetVariables()) > 0 {
for _, envVar := range command.GetEnvironment().GetVariables() {
cmd = append([]string{envVar.Name + "=" + envVar.Value}, cmd...)
}
}
// Add command arguments
cmd = append(cmd, command.GetValue())
if len(command.GetArguments()) > 0 {
for _, arg := range command.GetArguments() {
cmd = append(cmd, arg)
}
}
// Wrap the command
wrapperCmd = append(wrapperCmd, "-c \"")
wrapperCmd = append(wrapperCmd, cmd...)
wrapperCmd = append(wrapperCmd, "\"")
// Prepare timeout context and all the stuff needed to call the containerizer
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(c.TaskInfo.GetHealthCheck().GetTimeoutSeconds())*time.Second)
defer cancel()
execResult := c.Containerizer.ContainerExec(ctx, c.ContainerID, cmd)
execResult := c.Containerizer.ContainerExec(ctx, c.ContainerID, wrapperCmd)
select {
case err := <-execResult: // Done (with or without error)
if err != nil {
Expand Down
74 changes: 69 additions & 5 deletions healthcheck/healthcheck_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ func (s *HealthcheckTestSuite) TestRun() {
// Check that:
// - Do not enter in network namespace if not in bridge network mode
// - Result is false if server is not accessible
// - Result is false if HTTP code is not between 200 and 399
// - Result is false if HTTP code is not between 200 and 399 and is not wihtin custom status code
// - Result is true otherwise
func (s *HealthcheckTestSuite) TestCheckHTTP() {
// Prepare struct
Expand Down Expand Up @@ -176,7 +176,20 @@ func (s *HealthcheckTestSuite) TestCheckHTTP() {
port, _ = strconv.Atoi(portString[1])
s.taskInfo.HealthCheck.HTTP.Port = uint32(port)
go s.checker.checkHTTP(s.result)
assert.Equal(s.T(), true, <-s.result) // Should be unhealthy
assert.Equal(s.T(), true, <-s.result) // Should be healthy
server.Close()

// Result is ok with a custom status code
server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound) // 404
}))
serverURL, _ = url.Parse(server.URL)
portString = strings.Split(serverURL.Host, ":")
port, _ = strconv.Atoi(portString[1])
s.taskInfo.HealthCheck.HTTP.Statuses = []uint32{404}
s.taskInfo.HealthCheck.HTTP.Port = uint32(port)
go s.checker.checkHTTP(s.result)
assert.Equal(s.T(), true, <-s.result) // Should be healthy
server.Close()
}

Expand Down Expand Up @@ -243,7 +256,7 @@ func (s *HealthcheckTestSuite) TestCheckTCP() {
func (s *HealthcheckTestSuite) TestCheckCommand() {
// Prepare struct
shell := true
value := "sleep 1"
value := "ls"
s.taskInfo.HealthCheck.Command = &mesos.CommandInfo{
Value: &value,
Shell: &shell,
Expand All @@ -265,13 +278,64 @@ func (s *HealthcheckTestSuite) TestCheckCommand() {
// With wrapping
go s.checker.checkCommand(s.result)
<-s.result
assert.Equal(s.T(), []string{"/bin/sh", "-c", s.taskInfo.GetHealthCheck().GetCommand().GetValue()}, command) // Should be wrapped into a shell
assert.Equal(
s.T(),
[]string{"su", "-s", "/bin/sh", "-", "root", "-c \"", s.taskInfo.GetHealthCheck().GetCommand().GetValue(), "\""},
command,
) // Should be wrapped into a shell

// Without wrapping
shell = false
go s.checker.checkCommand(s.result)
<-s.result
assert.Equal(s.T(), []string{s.taskInfo.GetHealthCheck().GetCommand().GetValue()}, command) // Should not be wrapped into a shell
assert.Equal(
s.T(),
[]string{"su", "-", "root", "-c \"", s.taskInfo.GetHealthCheck().GetCommand().GetValue(), "\""},
command) // Should not be wrapped into a shell

//With arguments
s.taskInfo.HealthCheck.Command.Arguments = []string{"-l", "-i", "-a"}
expectedCommand := []string{"su", "-", "root", "-c \"", s.taskInfo.GetHealthCheck().GetCommand().GetValue()}
expectedCommand = append(expectedCommand, s.taskInfo.GetHealthCheck().GetCommand().GetArguments()...)
expectedCommand = append(expectedCommand, "\"")
go s.checker.checkCommand(s.result)
<-s.result
assert.Equal(
s.T(),
expectedCommand,
command,
)
s.taskInfo.HealthCheck.Command.Arguments = []string{}

//With environment variables
s.taskInfo.HealthCheck.Command.Environment = &mesos.Environment{
Variables: []mesos.Environment_Variable{
mesos.Environment_Variable{Name: "LOGLEVEL", Value: "DEBUG"},
},
}
go s.checker.checkCommand(s.result)
<-s.result
assert.Equal(
s.T(),
[]string{"su", "-", "root", "-c \"", "LOGLEVEL=DEBUG", s.taskInfo.GetHealthCheck().GetCommand().GetValue(), "\""},
command,
)
s.taskInfo.HealthCheck.Command.Environment.Variables = []mesos.Environment_Variable{}

//With specified user
user := "nobody"
s.taskInfo.HealthCheck.Command.User = &user
go s.checker.checkCommand(s.result)
<-s.result
assert.Equal(
s.T(),
[]string{
"su", "-", s.taskInfo.GetHealthCheck().GetCommand().GetUser(),
"-c \"", s.taskInfo.GetHealthCheck().GetCommand().GetValue(), "\"",
},
command,
)
user = ""

// Nominal case
go s.checker.checkCommand(s.result)
Expand Down