diff --git a/libpod/container.go b/libpod/container.go index 45ee5647..f4df9a0e 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -30,11 +30,6 @@ func (c *Container) ID() string { // Name returns the container's name func (c *Container) Name() string { - // Name can potentially be changed while a container is running - // So lock access to it - c.lock.RLock() - defer c.lock.RUnlock() - return c.name } diff --git a/libpod/errors.go b/libpod/errors.go index d50db574..31eddc46 100644 --- a/libpod/errors.go +++ b/libpod/errors.go @@ -32,6 +32,11 @@ var ( // ErrInvalidArg indicates that an invalid argument was passed ErrInvalidArg = errors.New("invalid argument") + // ErrEmptyID indicates that an empty ID was passed + ErrEmptyID = errors.New("name or ID cannot be empty") + + // ErrInternal indicates an internal library error + ErrInternal = errors.New("internal libpod error") // ErrRuntimeStopped indicates that the runtime has already been shut // down and no further operations can be performed on it diff --git a/libpod/in_memory_state.go b/libpod/in_memory_state.go new file mode 100644 index 00000000..b8747d4e --- /dev/null +++ b/libpod/in_memory_state.go @@ -0,0 +1,273 @@ +package libpod + +import ( + "github.com/docker/docker/pkg/truncindex" + "github.com/kubernetes-incubator/cri-o/pkg/registrar" + "github.com/pkg/errors" +) + +// An InMemoryState is a purely in-memory state store +type InMemoryState struct { + pods map[string]*Pod + containers map[string]*Container + podNameIndex *registrar.Registrar + podIDIndex *truncindex.TruncIndex + ctrNameIndex *registrar.Registrar + ctrIDIndex *truncindex.TruncIndex +} + +// NewInMemoryState initializes a new, empty in-memory state +func NewInMemoryState() (State, error) { + state := new(InMemoryState) + + state.pods = make(map[string]*Pod) + state.containers = make(map[string]*Container) + + state.podNameIndex = registrar.NewRegistrar() + state.ctrNameIndex = registrar.NewRegistrar() + + state.podIDIndex = truncindex.NewTruncIndex([]string{}) + state.ctrIDIndex = truncindex.NewTruncIndex([]string{}) + + return state, nil +} + +// Container retrieves a container from its full ID +func (s *InMemoryState) Container(id string) (*Container, error) { + if id == "" { + return nil, ErrEmptyID + } + + ctr, ok := s.containers[id] + if !ok { + return nil, errors.Wrapf(ErrNoSuchCtr, "no container with ID %s found", id) + } + + return ctr, nil +} + +// LookupContainer retrieves a container by full ID, unique partial ID, or name +func (s *InMemoryState) LookupContainer(idOrName string) (*Container, error) { + if idOrName == "" { + return nil, ErrEmptyID + } + + fullID, err := s.ctrNameIndex.Get(idOrName) + if err != nil { + if err == registrar.ErrNameNotReserved { + // What was passed is not a name, assume it's an ID + fullID, err = s.ctrIDIndex.Get(idOrName) + if err != nil { + if err == truncindex.ErrNotExist { + return nil, errors.Wrapf(ErrNoSuchCtr, "no container found with name or ID %s", idOrName) + } + return nil, errors.Wrapf(err, "error performing truncindex lookup for ID %s", idOrName) + } + } else { + return nil, errors.Wrapf(err, "error performing registry lookup for ID %s", idOrName) + } + } + + ctr, ok := s.containers[fullID] + if !ok { + // This should never happen + return nil, errors.Wrapf(ErrInternal, "mismatch in container ID registry and containers map for ID %s", fullID) + } + + return ctr, nil +} + +// HasContainer checks if a container with the given ID is present in the state +func (s *InMemoryState) HasContainer(id string) (bool, error) { + if id == "" { + return false, ErrEmptyID + } + + _, ok := s.containers[id] + + return ok, nil +} + +// AddContainer adds a container to the state +// If the container belongs to a pod, the pod must already be present when the +// container is added, and the container must be present in the pod +func (s *InMemoryState) AddContainer(ctr *Container) error { + if !ctr.valid { + return errors.Wrapf(ErrCtrRemoved, "container with ID %s is not valid", ctr.ID()) + } + + _, ok := s.containers[ctr.ID()] + if ok { + return errors.Wrapf(ErrCtrExists, "container with ID %s already exists in state", ctr.ID()) + } + + if ctr.pod != nil { + if _, ok := s.pods[ctr.pod.ID()]; !ok { + return errors.Wrapf(ErrNoSuchPod, "pod %s does not exist, cannot add container %s", ctr.pod.ID(), ctr.ID()) + } + + hasCtr, err := ctr.pod.HasContainer(ctr.ID()) + if err != nil { + return errors.Wrapf(err, "error checking if container %s is present in pod %s", ctr.ID(), ctr.pod.ID()) + } else if !hasCtr { + return errors.Wrapf(ErrNoSuchCtr, "container %s is not present in pod %s", ctr.ID(), ctr.pod.ID()) + } + } + + if err := s.ctrNameIndex.Reserve(ctr.Name(), ctr.ID()); err != nil { + return errors.Wrapf(err, "error registering container name %s", ctr.Name()) + } + + if err := s.ctrIDIndex.Add(ctr.ID()); err != nil { + s.ctrNameIndex.Release(ctr.Name()) + return errors.Wrapf(err, "error registering container ID %s", ctr.ID()) + } + + s.containers[ctr.ID()] = ctr + + return nil +} + +// RemoveContainer removes a container from the state +// The container will only be removed from the state, not from the pod the container belongs to +func (s *InMemoryState) RemoveContainer(ctr *Container) error { + // Almost no validity checks are performed, to ensure we can kick + // misbehaving containers out of the state + + if _, ok := s.containers[ctr.ID()]; !ok { + return errors.Wrapf(ErrNoSuchCtr, "no container exists in state with ID %s", ctr.ID()) + } + + if err := s.ctrIDIndex.Delete(ctr.ID()); err != nil { + return errors.Wrapf(err, "error removing container ID from index") + } + delete(s.containers, ctr.ID()) + s.ctrNameIndex.Release(ctr.Name()) + + return nil +} + +// AllContainers retrieves all containers from the state +func (s *InMemoryState) AllContainers() ([]*Container, error) { + ctrs := make([]*Container, 0, len(s.containers)) + for _, ctr := range s.containers { + ctrs = append(ctrs, ctr) + } + + return ctrs, nil +} + +// Pod retrieves a pod from the state from its full ID +func (s *InMemoryState) Pod(id string) (*Pod, error) { + if id == "" { + return nil, ErrEmptyID + } + + pod, ok := s.pods[id] + if !ok { + return nil, errors.Wrapf(ErrNoSuchPod, "no pod with id %s found", id) + } + + return pod, nil +} + +// LookupPod retrieves a pod from the state from a full or unique partial ID or +// a full name +func (s *InMemoryState) LookupPod(idOrName string) (*Pod, error) { + if idOrName == "" { + return nil, ErrEmptyID + } + + fullID, err := s.podNameIndex.Get(idOrName) + if err != nil { + if err == registrar.ErrNameNotReserved { + // What was passed is not a name, assume it's an ID + fullID, err = s.podIDIndex.Get(idOrName) + if err != nil { + if err == truncindex.ErrNotExist { + return nil, errors.Wrapf(ErrNoSuchPod, "no pod found with name or ID %s", idOrName) + } + return nil, errors.Wrapf(err, "error performing truncindex lookup for ID %s", idOrName) + } + } else { + return nil, errors.Wrapf(err, "error performing registry lookup for ID %s", idOrName) + } + } + + pod, ok := s.pods[fullID] + if !ok { + // This should never happen + return nil, errors.Wrapf(ErrInternal, "mismatch in pod ID registry and pod map for ID %s", fullID) + } + + return pod, nil +} + +// HasPod checks if a pod with the given ID is present in the state +func (s *InMemoryState) HasPod(id string) (bool, error) { + if id == "" { + return false, ErrEmptyID + } + + _, ok := s.pods[id] + + return ok, nil +} + +// AddPod adds a given pod to the state +// Only empty pods can be added to the state +func (s *InMemoryState) AddPod(pod *Pod) error { + if !pod.valid { + return errors.Wrapf(ErrPodRemoved, "pod %s is not valid and cannot be added", pod.ID()) + } + + if _, ok := s.pods[pod.ID()]; ok { + return errors.Wrapf(ErrPodExists, "pod with ID %s already exists in state", pod.ID()) + } + + if len(pod.containers) != 0 { + return errors.Wrapf(ErrInternal, "only empty pods can be added to the state") + } + + if err := s.podNameIndex.Reserve(pod.Name(), pod.ID()); err != nil { + return errors.Wrapf(err, "error registering pod name %s", pod.Name()) + } + + if err := s.podIDIndex.Add(pod.ID()); err != nil { + s.podNameIndex.Release(pod.Name()) + return errors.Wrapf(err, "error registering pod ID %s", pod.ID()) + } + + s.pods[pod.ID()] = pod + + return nil +} + +// RemovePod removes a given pod from the state +// Containers within the pod will not be removed or changed +func (s *InMemoryState) RemovePod(pod *Pod) error { + // Don't make many validity checks to ensure we can kick badly formed + // pods out of the state + + if _, ok := s.pods[pod.ID()]; !ok { + return errors.Wrapf(ErrNoSuchPod, "no pod exists in state with ID %s", pod.ID()) + } + + if err := s.podIDIndex.Delete(pod.ID()); err != nil { + return errors.Wrapf(err, "error removing pod ID %s from index", pod.ID()) + } + delete(s.pods, pod.ID()) + s.podNameIndex.Release(pod.Name()) + + return nil +} + +// AllPods retrieves all pods currently in the state +func (s *InMemoryState) AllPods() ([]*Pod, error) { + pods := make([]*Pod, 0, len(s.pods)) + for _, pod := range s.pods { + pods = append(pods, pod) + } + + return pods, nil +} diff --git a/libpod/options.go b/libpod/options.go index 03248274..49a3adfd 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -228,12 +228,12 @@ func (r *Runtime) WithPod(pod *Pod) CtrCreateOption { } } -// WithLabels adds labels to the pod +// WithLabels adds labels to the container func WithLabels(labels map[string]string) CtrCreateOption { return ctrNotImplemented } -// WithAnnotations adds annotations to the pod +// WithAnnotations adds annotations to the container func WithAnnotations(annotations map[string]string) CtrCreateOption { return ctrNotImplemented } diff --git a/libpod/pod.go b/libpod/pod.go index 451747aa..b7fcab4b 100644 --- a/libpod/pod.go +++ b/libpod/pod.go @@ -93,6 +93,20 @@ func (p *Pod) Kill(signal uint) error { return ErrNotImplemented } +// HasContainer checks if a container is present in the pod +func (p *Pod) HasContainer(id string) (bool, error) { + p.lock.RLock() + defer p.lock.RUnlock() + + if !p.valid { + return false, ErrPodRemoved + } + + _, ok := p.containers[id] + + return ok, nil +} + // GetContainers retrieves the containers in the pod func (p *Pod) GetContainers() ([]*Container, error) { p.lock.RLock() diff --git a/libpod/runtime.go b/libpod/runtime.go index cfa96527..cf7ef3f1 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -86,6 +86,13 @@ func NewRuntime(options ...RuntimeOption) (*Runtime, error) { SignaturePolicyPath: runtime.config.SignaturePolicyPath, } + // Set up the state + state, err := NewInMemoryState() + if err != nil { + return nil, err + } + runtime.state = state + runtime.seccompEnabled = seccomp.IsEnabled() runtime.apparmorEnabled = apparmor.IsEnabled() diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 4b506233..fc788227 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -60,7 +60,42 @@ func (r *Runtime) NewContainer(spec *spec.Spec, options ...CtrCreateOption) (*Co // If force is specified, the container will be stopped first // Otherwise, RemoveContainer will return an error if the container is running func (r *Runtime) RemoveContainer(c *Container, force bool) error { - return ErrNotImplemented + r.lock.Lock() + defer r.lock.Unlock() + + c.lock.Lock() + defer c.lock.Unlock() + + if !r.valid { + return ErrRuntimeStopped + } + + if !c.valid { + return ErrCtrRemoved + } + + // TODO check container status and unmount storage + // TODO check that no other containers depend on this container's + // namespaces + if err := c.Status(); err != nil { + return err + } + + if err := r.state.RemoveContainer(c); err != nil { + return errors.Wrapf(err, "error removing container from state") + } + + // Set container as invalid so it can no longer be used + c.valid = false + + // Remove container from pod, if it joined one + if c.pod != nil { + if err := c.pod.removeContainer(c); err != nil { + return errors.Wrapf(err, "error removing container from pod %s", c.pod.ID()) + } + } + + return nil } // GetContainer retrieves a container by its ID @@ -72,7 +107,7 @@ func (r *Runtime) GetContainer(id string) (*Container, error) { return nil, ErrRuntimeStopped } - return r.state.GetContainer(id) + return r.state.Container(id) } // HasContainer checks if a container with the given ID is present @@ -112,7 +147,7 @@ func (r *Runtime) GetContainers(filters ...ContainerFilter) ([]*Container, error return nil, ErrRuntimeStopped } - ctrs, err := r.state.GetAllContainers() + ctrs, err := r.state.AllContainers() if err != nil { return nil, err } diff --git a/libpod/runtime_pod.go b/libpod/runtime_pod.go index e14c1b45..162b353e 100644 --- a/libpod/runtime_pod.go +++ b/libpod/runtime_pod.go @@ -61,7 +61,7 @@ func (r *Runtime) GetPod(id string) (*Pod, error) { return nil, ErrRuntimeStopped } - return r.state.GetPod(id) + return r.state.Pod(id) } // HasPod checks to see if a pod with the given ID exists @@ -101,7 +101,7 @@ func (r *Runtime) Pods(filters ...PodFilter) ([]*Pod, error) { return nil, ErrRuntimeStopped } - pods, err := r.state.GetAllPods() + pods, err := r.state.AllPods() if err != nil { return nil, err } diff --git a/libpod/state.go b/libpod/state.go index e7194c32..1c21911b 100644 --- a/libpod/state.go +++ b/libpod/state.go @@ -3,35 +3,36 @@ package libpod // State is a storage backend for libpod's current state type State interface { // Accepts full ID of container - GetContainer(id string) (*Container, error) + Container(id string) (*Container, error) // Accepts full or partial IDs (as long as they are unique) and names LookupContainer(idOrName string) (*Container, error) // Checks if a container with the given ID is present in the state HasContainer(id string) (bool, error) // Adds container to state // If the container belongs to a pod, that pod must already be present - // in the state when the container is added + // in the state when the container is added, and the container must be + // present in the pod AddContainer(ctr *Container) error // Removes container from state - // If the container belongs to a pod, it will be removed from the pod - // as well + // The container will only be removed from the state, not from the pod + // which the container belongs to RemoveContainer(ctr *Container) error // Retrieves all containers presently in state - GetAllContainers() ([]*Container, error) + AllContainers() ([]*Container, error) // Accepts full ID of pod - GetPod(id string) (*Pod, error) + Pod(id string) (*Pod, error) // Accepts full or partial IDs (as long as they are unique) and names LookupPod(idOrName string) (*Pod, error) // Checks if a pod with the given ID is present in the state HasPod(id string) (bool, error) // Adds pod to state - // Any containers within the pod not already in the state will be added - // with it + // Only empty pods can be added to the state AddPod(pod *Pod) error // Removes pod from state - // All containers within the pod will also be removed + // Containers within a pod will not be removed from the state, and will + // not be changed to remove them from the now-removed pod RemovePod(pod *Pod) error // Retrieves all pods presently in state - GetAllPods() ([]*Pod, error) + AllPods() ([]*Pod, error) }