Files
kata-containers/virtcontainers/hook.go
Archana Shinde a301a9e641 hooks: Send the bundle path in the state that is sent with hooks
We currently just send the pid in the state. While OCI specifies
a few other fields as well, this commit just adds the bundle path
and the container id to the state. This should fix the errors seen
with hooks that rely on the bundle path.

Other fields like running "state" string have been left out. As this
would need sending the strings that OCI recognises. Hooks have been
implemented in virtcontainers and sending the state string would
require calling into OCI specific code in virtcontainers.

The bundle path again is OCI specific, but this can be accessed
using annotations. Hooks really need to be moved to the cli as they
are OCI specific. This however needs network hotplug to be implemented
first so that the hooks can be called from the cli after the
VM has been created.

Fixes #271

Signed-off-by: Archana Shinde <archana.m.shinde@intel.com>
2018-04-27 15:48:58 -07:00

161 lines
3.1 KiB
Go

// Copyright (c) 2017 Intel Corporation
//
// SPDX-License-Identifier: Apache-2.0
//
package virtcontainers
import (
"bytes"
"encoding/json"
"fmt"
"os"
"os/exec"
"syscall"
"time"
vcAnnotations "github.com/kata-containers/runtime/virtcontainers/pkg/annotations"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
)
// Hook represents an OCI hook, including its required parameters.
type Hook struct {
Path string
Args []string
Env []string
Timeout int
}
// Hooks gathers all existing OCI hooks list.
type Hooks struct {
PreStartHooks []Hook
PostStartHooks []Hook
PostStopHooks []Hook
}
// Logger returns a logrus logger appropriate for logging Hooks messages
func (h *Hooks) Logger() *logrus.Entry {
return virtLog.WithField("subsystem", "hooks")
}
func buildHookState(processID int, s *Sandbox) specs.State {
annotations := s.GetAnnotations()
return specs.State{
Pid: processID,
Bundle: annotations[vcAnnotations.BundlePathKey],
ID: s.id,
}
}
func (h *Hook) runHook(s *Sandbox) error {
state := buildHookState(os.Getpid(), s)
stateJSON, err := json.Marshal(state)
if err != nil {
return err
}
var stdout, stderr bytes.Buffer
cmd := &exec.Cmd{
Path: h.Path,
Args: h.Args,
Env: h.Env,
Stdin: bytes.NewReader(stateJSON),
Stdout: &stdout,
Stderr: &stderr,
}
err = cmd.Start()
if err != nil {
return err
}
if h.Timeout == 0 {
err = cmd.Wait()
if err != nil {
return fmt.Errorf("%s: stdout: %s, stderr: %s", err, stdout.String(), stderr.String())
}
} else {
done := make(chan error, 1)
go func() {
done <- cmd.Wait()
close(done)
}()
select {
case err := <-done:
if err != nil {
return fmt.Errorf("%s: stdout: %s, stderr: %s", err, stdout.String(), stderr.String())
}
case <-time.After(time.Duration(h.Timeout) * time.Second):
if err := syscall.Kill(cmd.Process.Pid, syscall.SIGKILL); err != nil {
return err
}
return fmt.Errorf("Hook timeout")
}
}
return nil
}
func (h *Hooks) preStartHooks(s *Sandbox) error {
if len(h.PreStartHooks) == 0 {
return nil
}
for _, hook := range h.PreStartHooks {
err := hook.runHook(s)
if err != nil {
h.Logger().WithFields(logrus.Fields{
"hook-type": "pre-start",
"error": err,
}).Error("hook error")
return err
}
}
return nil
}
func (h *Hooks) postStartHooks(s *Sandbox) error {
if len(h.PostStartHooks) == 0 {
return nil
}
for _, hook := range h.PostStartHooks {
err := hook.runHook(s)
if err != nil {
// In case of post start hook, the error is not fatal,
// just need to be logged.
h.Logger().WithFields(logrus.Fields{
"hook-type": "post-start",
"error": err,
}).Info("hook error")
}
}
return nil
}
func (h *Hooks) postStopHooks(s *Sandbox) error {
if len(h.PostStopHooks) == 0 {
return nil
}
for _, hook := range h.PostStopHooks {
err := hook.runHook(s)
if err != nil {
// In case of post stop hook, the error is not fatal,
// just need to be logged.
h.Logger().WithFields(logrus.Fields{
"hook-type": "post-stop",
"error": err,
}).Info("hook error")
}
}
return nil
}