diff options
Diffstat (limited to 'libpod')
-rw-r--r-- | libpod/boltdb_state.go | 167 | ||||
-rw-r--r-- | libpod/container.go | 15 | ||||
-rw-r--r-- | libpod/healthcheck.go | 92 | ||||
-rw-r--r-- | libpod/image/image.go | 29 | ||||
-rw-r--r-- | libpod/options.go | 12 | ||||
-rw-r--r-- | libpod/runtime_pod_linux.go | 5 |
6 files changed, 257 insertions, 63 deletions
diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index c226a0617..92a7b1538 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -382,6 +382,11 @@ func (s *BoltState) LookupContainer(idOrName string) (*Container, error) { return err } + namesBucket, err := getNamesBucket(tx) + if err != nil { + return err + } + nsBucket, err := getNSBucket(tx) if err != nil { return err @@ -395,41 +400,59 @@ func (s *BoltState) LookupContainer(idOrName string) (*Container, error) { // It might not be in our namespace, but // getContainerFromDB() will handle that case. id = []byte(idOrName) - } else { - // They did not give us a full container ID. - // Search for partial ID or full name matches - // Use else-if in case the name is set to a partial ID - exists := false - err = idBucket.ForEach(func(checkID, checkName []byte) error { - // If the container isn't in our namespace, we - // can't match it - if s.namespaceBytes != nil { - ns := nsBucket.Get(checkID) - if !bytes.Equal(ns, s.namespaceBytes) { - return nil - } + return s.getContainerFromDB(id, ctr, ctrBucket) + } + + // Next, check if the full name was given + isPod := false + fullID := namesBucket.Get([]byte(idOrName)) + if fullID != nil { + // The name exists and maps to an ID. + // However, we are not yet certain the ID is a + // container. + ctrExists = ctrBucket.Bucket(fullID) + if ctrExists != nil { + // A container bucket matching the full ID was + // found. + return s.getContainerFromDB(fullID, ctr, ctrBucket) + } + // Don't error if we have a name match but it's not a + // container - there's a chance we have a container with + // an ID starting with those characters. + // However, so we can return a good error, note whether + // this is a pod. + isPod = true + } + + // We were not given a full container ID or name. + // Search for partial ID matches. + exists := false + err = idBucket.ForEach(func(checkID, checkName []byte) error { + // If the container isn't in our namespace, we + // can't match it + if s.namespaceBytes != nil { + ns := nsBucket.Get(checkID) + if !bytes.Equal(ns, s.namespaceBytes) { + return nil } - if string(checkName) == idOrName { - if exists { - return errors.Wrapf(ErrCtrExists, "more than one result for ID or name %s", idOrName) - } - id = checkID - exists = true - } else if strings.HasPrefix(string(checkID), idOrName) { - if exists { - return errors.Wrapf(ErrCtrExists, "more than one result for ID or name %s", idOrName) - } - id = checkID - exists = true + } + if strings.HasPrefix(string(checkID), idOrName) { + if exists { + return errors.Wrapf(ErrCtrExists, "more than one result for container ID %s", idOrName) } + id = checkID + exists = true + } - return nil - }) - if err != nil { - return err - } else if !exists { - return errors.Wrapf(ErrNoSuchCtr, "no container with name or ID %s found", idOrName) + return nil + }) + if err != nil { + return err + } else if !exists { + if isPod { + return errors.Wrapf(ErrNoSuchCtr, "%s is a pod, not a container", idOrName) } + return errors.Wrapf(ErrNoSuchCtr, "no container with name or ID %s found", idOrName) } return s.getContainerFromDB(id, ctr, ctrBucket) @@ -941,6 +964,11 @@ func (s *BoltState) LookupPod(idOrName string) (*Pod, error) { return err } + namesBkt, err := getNamesBucket(tx) + if err != nil { + return err + } + nsBkt, err := getNSBucket(tx) if err != nil { return err @@ -954,41 +982,56 @@ func (s *BoltState) LookupPod(idOrName string) (*Pod, error) { // It might not be in our namespace, but getPodFromDB() // will handle that case. id = []byte(idOrName) - } else { - // They did not give us a full pod ID. - // Search for partial ID or full name matches - // Use else-if in case the name is set to a partial ID - exists := false - err = idBucket.ForEach(func(checkID, checkName []byte) error { - // If the pod isn't in our namespace, we - // can't match it - if s.namespaceBytes != nil { - ns := nsBkt.Get(checkID) - if !bytes.Equal(ns, s.namespaceBytes) { - return nil - } + return s.getPodFromDB(id, pod, podBkt) + } + + // Next, check if the full name was given + isCtr := false + fullID := namesBkt.Get([]byte(idOrName)) + if fullID != nil { + // The name exists and maps to an ID. + // However, we aren't yet sure if the ID is a pod. + podExists = podBkt.Bucket(fullID) + if podExists != nil { + // A pod bucket matching the full ID was found. + return s.getPodFromDB(fullID, pod, podBkt) + } + // Don't error if we have a name match but it's not a + // pod - there's a chance we have a pod with an ID + // starting with those characters. + // However, so we can return a good error, note whether + // this is a container. + isCtr = true + } + // They did not give us a full pod name or ID. + // Search for partial ID matches. + exists := false + err = idBucket.ForEach(func(checkID, checkName []byte) error { + // If the pod isn't in our namespace, we + // can't match it + if s.namespaceBytes != nil { + ns := nsBkt.Get(checkID) + if !bytes.Equal(ns, s.namespaceBytes) { + return nil } - if string(checkName) == idOrName { - if exists { - return errors.Wrapf(ErrPodExists, "more than one result for ID or name %s", idOrName) - } - id = checkID - exists = true - } else if strings.HasPrefix(string(checkID), idOrName) { - if exists { - return errors.Wrapf(ErrPodExists, "more than one result for ID or name %s", idOrName) - } - id = checkID - exists = true + } + if strings.HasPrefix(string(checkID), idOrName) { + if exists { + return errors.Wrapf(ErrPodExists, "more than one result for ID or name %s", idOrName) } + id = checkID + exists = true + } - return nil - }) - if err != nil { - return err - } else if !exists { - return errors.Wrapf(ErrNoSuchPod, "no pod with name or ID %s found", idOrName) + return nil + }) + if err != nil { + return err + } else if !exists { + if isCtr { + return errors.Wrapf(ErrNoSuchPod, "%s is a container, not a pod", idOrName) } + return errors.Wrapf(ErrNoSuchPod, "no pod with name or ID %s found", idOrName) } // We might have found a container ID, but it's OK diff --git a/libpod/container.go b/libpod/container.go index 75f4a4a4f..2381f53ad 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -10,6 +10,7 @@ import ( "github.com/containernetworking/cni/pkg/types" cnitypes "github.com/containernetworking/cni/pkg/types/current" + "github.com/containers/image/manifest" "github.com/containers/libpod/libpod/lock" "github.com/containers/libpod/pkg/namespaces" "github.com/containers/storage" @@ -365,6 +366,9 @@ type ContainerConfig struct { // Systemd tells libpod to setup the container in systemd mode Systemd bool `json:"systemd"` + + // HealtchCheckConfig has the health check command and related timings + HealthCheckConfig *manifest.Schema2HealthConfig } // ContainerStatus returns a string representation for users @@ -1085,3 +1089,14 @@ func (c *Container) ContainerState() (*ContainerState, error) { deepcopier.Copy(c.state).To(returnConfig) return c.state, nil } + +// HasHealthCheck returns bool as to whether there is a health check +// defined for the container +func (c *Container) HasHealthCheck() bool { + return c.config.HealthCheckConfig != nil +} + +// HealthCheckConfig returns the command and timing attributes of the health check +func (c *Container) HealthCheckConfig() *manifest.Schema2HealthConfig { + return c.config.HealthCheckConfig +} diff --git a/libpod/healthcheck.go b/libpod/healthcheck.go new file mode 100644 index 000000000..81addb9a8 --- /dev/null +++ b/libpod/healthcheck.go @@ -0,0 +1,92 @@ +package libpod + +import ( + "os" + "strings" + + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +// HealthCheckStatus represents the current state of a container +type HealthCheckStatus int + +const ( + // HealthCheckSuccess means the health worked + HealthCheckSuccess HealthCheckStatus = iota + // HealthCheckFailure means the health ran and failed + HealthCheckFailure HealthCheckStatus = iota + // HealthCheckContainerStopped means the health check cannot + // be run because the container is stopped + HealthCheckContainerStopped HealthCheckStatus = iota + // HealthCheckContainerNotFound means the container could + // not be found in local store + HealthCheckContainerNotFound HealthCheckStatus = iota + // HealthCheckNotDefined means the container has no health + // check defined in it + HealthCheckNotDefined HealthCheckStatus = iota + // HealthCheckInternalError means somes something failed obtaining or running + // a given health check + HealthCheckInternalError HealthCheckStatus = iota + // HealthCheckDefined means the healthcheck was found on the container + HealthCheckDefined HealthCheckStatus = iota +) + +// HealthCheck verifies the state and validity of the healthcheck configuration +// on the container and then executes the healthcheck +func (r *Runtime) HealthCheck(name string) (HealthCheckStatus, error) { + container, err := r.LookupContainer(name) + if err != nil { + return HealthCheckContainerNotFound, errors.Wrapf(err, "unable to lookup %s to perform a health check", name) + } + hcStatus, err := checkHealthCheckCanBeRun(container) + if err == nil { + return container.RunHealthCheck() + } + return hcStatus, err +} + +// RunHealthCheck runs the health check as defined by the container +func (c *Container) RunHealthCheck() (HealthCheckStatus, error) { + var newCommand []string + hcStatus, err := checkHealthCheckCanBeRun(c) + if err != nil { + return hcStatus, err + } + hcCommand := c.HealthCheckConfig().Test + if len(hcCommand) > 0 && hcCommand[0] == "CMD-SHELL" { + newCommand = []string{"sh", "-c"} + newCommand = append(newCommand, hcCommand[1:]...) + } else { + newCommand = hcCommand + } + // TODO when history/logging is implemented for healthcheck, we need to change the output streams + // so we can capture i/o + streams := new(AttachStreams) + streams.OutputStream = os.Stdout + streams.ErrorStream = os.Stderr + streams.InputStream = os.Stdin + streams.AttachOutput = true + streams.AttachError = true + streams.AttachInput = true + + logrus.Debugf("executing health check command %s for %s", strings.Join(newCommand, " "), c.ID()) + if err := c.Exec(false, false, []string{}, newCommand, "", "", streams, 0); err != nil { + return HealthCheckFailure, err + } + return HealthCheckSuccess, nil +} + +func checkHealthCheckCanBeRun(c *Container) (HealthCheckStatus, error) { + cstate, err := c.State() + if err != nil { + return HealthCheckInternalError, err + } + if cstate != ContainerStateRunning { + return HealthCheckContainerStopped, errors.Errorf("container %s is not running", c.ID()) + } + if !c.HasHealthCheck() { + return HealthCheckNotDefined, errors.Errorf("container %s has no defined healthcheck", c.ID()) + } + return HealthCheckDefined, nil +} diff --git a/libpod/image/image.go b/libpod/image/image.go index b20419d7b..8c98de3d3 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -1151,3 +1151,32 @@ func (i *Image) Save(ctx context.Context, source, format, output string, moreTag return nil } + +// GetConfigBlob returns a schema2image. If the image is not a schema2, then +// it will return an error +func (i *Image) GetConfigBlob(ctx context.Context) (*manifest.Schema2Image, error) { + imageRef, err := i.toImageRef(ctx) + if err != nil { + return nil, err + } + b, err := imageRef.ConfigBlob(ctx) + if err != nil { + return nil, errors.Wrapf(err, "unable to get config blob for %s", i.ID()) + } + blob := manifest.Schema2Image{} + if err := json.Unmarshal(b, &blob); err != nil { + return nil, errors.Wrapf(err, "unable to parse image blob for %s", i.ID()) + } + return &blob, nil + +} + +// GetHealthCheck returns a HealthConfig for an image. This function only works with +// schema2 images. +func (i *Image) GetHealthCheck(ctx context.Context) (*manifest.Schema2HealthConfig, error) { + configBlob, err := i.GetConfigBlob(ctx) + if err != nil { + return nil, err + } + return configBlob.ContainerConfig.Healthcheck, nil +} diff --git a/libpod/options.go b/libpod/options.go index 1e8592a25..5ad2824d9 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -7,6 +7,7 @@ import ( "regexp" "syscall" + "github.com/containers/image/manifest" "github.com/containers/libpod/pkg/namespaces" "github.com/containers/storage" "github.com/containers/storage/pkg/idtools" @@ -1469,3 +1470,14 @@ func WithInfraContainerPorts(bindings []ocicni.PortMapping) PodCreateOption { return nil } } + +// WithHealthCheck adds the healthcheck to the container config +func WithHealthCheck(healthCheck *manifest.Schema2HealthConfig) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return ErrCtrFinalized + } + ctr.config.HealthCheckConfig = healthCheck + return nil + } +} diff --git a/libpod/runtime_pod_linux.go b/libpod/runtime_pod_linux.go index c378d18e4..9063390bd 100644 --- a/libpod/runtime_pod_linux.go +++ b/libpod/runtime_pod_linux.go @@ -95,9 +95,12 @@ func (r *Runtime) NewPod(ctx context.Context, options ...PodCreateOption) (*Pod, if pod.config.UsePodCgroup { logrus.Debugf("Got pod cgroup as %s", pod.state.CgroupPath) } - if pod.HasInfraContainer() != pod.SharesNamespaces() { + if !pod.HasInfraContainer() && pod.SharesNamespaces() { return nil, errors.Errorf("Pods must have an infra container to share namespaces") } + if pod.HasInfraContainer() && !pod.SharesNamespaces() { + logrus.Warnf("Pod has an infra container, but shares no namespaces") + } if err := r.state.AddPod(pod); err != nil { return nil, errors.Wrapf(err, "error adding pod to state") |