diff --git a/libpod/container.go b/libpod/container.go index 6c12fd58..f9496279 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -2,8 +2,12 @@ package libpod import ( "fmt" + "sync" "github.com/containers/storage" + "github.com/docker/docker/pkg/stringid" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/ulule/deepcopier" ) var ( @@ -13,7 +17,60 @@ var ( // Container is a single OCI container type Container struct { - // TODO populate + id string + name string + + spec *spec.Spec + pod *Pod + + valid bool + lock sync.RWMutex +} + +// ID returns the container's ID +func (c *Container) ID() string { + // No locking needed, ID will never mutate after a container is created + return c.id +} + +// 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 +} + +// Spec returns the container's OCI runtime spec +func (c *Container) Spec() *spec.Spec { + // The spec can potentially be altered when storage is configured and to + // add annotations at container create time + // As such, access to it is locked + c.lock.RLock() + defer c.lock.RUnlock() + + spec := new(spec.Spec) + deepcopier.Copy(c.spec).To(spec) + + return spec +} + +// Make a new container +func newContainer(rspec *spec.Spec) (*Container, error) { + if rspec == nil { + return nil, fmt.Errorf("must provide a valid spec to construct container") + } + + ctr := new(Container) + ctr.id = stringid.GenerateNonCryptoID() + ctr.name = ctr.id // TODO generate unique human-readable names + + ctr.spec = new(spec.Spec) + deepcopier.Copy(rspec).To(ctr.spec) + + return ctr, nil } // Create creates a container in the OCI runtime diff --git a/libpod/image.go b/libpod/image.go index 59df09d3..affb39da 100644 --- a/libpod/image.go +++ b/libpod/image.go @@ -18,7 +18,6 @@ import ( "github.com/containers/storage" "github.com/containers/storage/pkg/archive" "github.com/kubernetes-incubator/cri-o/libpod/common" - "github.com/kubernetes-incubator/cri-o/libpod/ctr" "github.com/kubernetes-incubator/cri-o/libpod/images" "github.com/pkg/errors" ) @@ -354,10 +353,10 @@ func (r *Runtime) GetImageRef(image string) (types.Image, error) { // output. Multiple filters are handled by ANDing their output, so only images // matching all filters are included func (r *Runtime) GetImages(filter ...ImageFilter) ([]*storage.Image, error) { - return nil, ctr.ErrNotImplemented + return nil, errNotImplemented } // ImportImage imports an OCI format image archive into storage as an image func (r *Runtime) ImportImage(path string) (*storage.Image, error) { - return nil, ctr.ErrNotImplemented + return nil, errNotImplemented } diff --git a/libpod/options.go b/libpod/options.go index b5248869..60cc1fe5 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -5,10 +5,12 @@ import ( "github.com/containers/storage" "github.com/containers/storage/pkg/idtools" + "github.com/pkg/errors" ) var ( errRuntimeFinalized = fmt.Errorf("runtime has already been finalized") + errCtrFinalized = fmt.Errorf("container has already been finalized") ctrNotImplemented = func(c *Container) error { return fmt.Errorf("NOT IMPLEMENTED") } @@ -201,8 +203,31 @@ func WithSharedNamespaces(from *Container, namespaces map[string]string) CtrCrea } // WithPod adds the container to a pod -func WithPod(pod *Pod) CtrCreateOption { - return ctrNotImplemented +func (r *Runtime) WithPod(pod *Pod) CtrCreateOption { + return func(ctr *Container) error { + if !ctr.valid { + return errCtrFinalized + } + + if ctr.pod != nil { + return fmt.Errorf("container has already been added to a pod") + } + + exists, err := r.state.HasPod(pod.ID()) + if err != nil { + return errors.Wrapf(err, "error searching state for pod %s", pod.ID()) + } else if !exists { + return fmt.Errorf("pod with id %s not found in state") + } + + if err := pod.addContainer(ctr); err != nil { + return errors.Wrapf(err, "error adding container to pod") + } + + ctr.pod = pod + + return nil + } } // WithLabels adds labels to the pod @@ -217,10 +242,33 @@ func WithAnnotations(annotations map[string]string) CtrCreateOption { // WithName sets the container's name func WithName(name string) CtrCreateOption { - return ctrNotImplemented + return func(ctr *Container) error { + if !ctr.valid { + return errCtrFinalized + } + + ctr.name = name + + return nil + } } // WithStopSignal sets the signal that will be sent to stop the container func WithStopSignal(signal uint) CtrCreateOption { return ctrNotImplemented } + +// Pod Creation Options + +// WithPodName sets the name of the pod +func WithPodName(name string) PodCreateOption { + return func(pod *Pod) error { + if pod.valid { + return fmt.Errorf("pod already finalized") + } + + pod.name = name + + return nil + } +} diff --git a/libpod/pod.go b/libpod/pod.go index 775441b7..f70c8313 100644 --- a/libpod/pod.go +++ b/libpod/pod.go @@ -1,12 +1,81 @@ package libpod import ( - "github.com/kubernetes-incubator/cri-o/libpod/ctr" + "fmt" + "sync" + + "github.com/docker/docker/pkg/stringid" ) // Pod represents a group of containers that may share namespaces type Pod struct { - // TODO populate + id string + name string + + containers map[string]*Container + + valid bool + lock sync.RWMutex +} + +// ID retrieves the pod's ID +func (p *Pod) ID() string { + return p.id +} + +// Name retrieves the pod's name +func (p *Pod) Name() string { + return p.name +} + +// Creates a new pod +func newPod() (*Pod, error) { + pod := new(Pod) + pod.id = stringid.GenerateNonCryptoID() + pod.name = pod.id // TODO generate human-readable name here + + pod.containers = make(map[string]*Container) + + return pod, nil +} + +// Adds a container to the pod +// Does not check that container's pod ID is set correctly, or attempt to set +// pod ID after adding +func (p *Pod) addContainer(ctr *Container) error { + p.lock.Lock() + defer p.lock.Unlock() + + if !p.valid { + return fmt.Errorf("pod has already been removed") + } + + if _, ok := p.containers[ctr.id]; ok { + return fmt.Errorf("container with id %s already exists in pod %s", ctr.id, p.id) + } + + p.containers[ctr.id] = ctr + + return nil +} + +// Removes a container from the pod +// Does not perform any checks on the container +func (p *Pod) removeContainer(ctr *Container) error { + p.lock.Lock() + defer p.lock.Unlock() + + if !p.valid { + return fmt.Errorf("pod has already been removed") + } + + if _, ok := p.containers[ctr.id]; !ok { + return fmt.Errorf("container with id %s does not exist in pod %s", ctr.id, p.id) + } + + delete(p.containers, ctr.id) + + return nil } // Start starts all containers within a pod that are not already running @@ -26,7 +95,19 @@ func (p *Pod) Kill(signal uint) error { // GetContainers retrieves the containers in the pod func (p *Pod) GetContainers() ([]*Container, error) { - return nil, errNotImplemented + p.lock.RLock() + defer p.lock.RUnlock() + + if !p.valid { + return nil, fmt.Errorf("pod has already been removed") + } + + ctrs := make([]*Container, 0, len(p.containers)) + for _, ctr := range p.containers { + ctrs = append(ctrs, ctr) + } + + return ctrs, nil } // Status gets the status of all containers in the pod diff --git a/libpod/runtime.go b/libpod/runtime.go index 85b98e96..5826e697 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -20,6 +20,7 @@ type RuntimeOption func(*Runtime) error // Runtime is the core libpod runtime type Runtime struct { config *RuntimeConfig + state State store storage.Store imageContext *types.SystemContext apparmorEnabled bool @@ -145,45 +146,158 @@ type ContainerFilter func(*Container) bool // NewContainer creates a new container from a given OCI config func (r *Runtime) NewContainer(spec *spec.Spec, options ...CtrCreateOption) (*Container, error) { - return nil, ctr.ErrNotImplemented + r.lock.Lock() + defer r.lock.Unlock() + + if !r.valid { + return nil, fmt.Errorf("runtime has already been shut down") + } + + ctr, err := newContainer(spec) + if err != nil { + return nil, err + } + + for _, option := range options { + if err := option(ctr); err != nil { + return nil, errors.Wrapf(err, "error running container create option") + } + } + + ctr.valid = true + + if err := r.state.AddContainer(ctr); err != nil { + // If we joined a pod, remove ourself from it + if ctr.pod != nil { + if err2 := ctr.pod.removeContainer(ctr); err2 != nil { + return nil, errors.Wrapf(err, "error adding new container to state, container could not be removed from pod %s", ctr.pod.ID()) + } + } + + // TODO: Might be worth making an effort to detect duplicate IDs + // We can recover from that by generating a new ID for the + // container + return nil, errors.Wrapf(err, "error adding new container to state") + } + + return ctr, nil } // RemoveContainer removes the given container // 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 ctr.ErrNotImplemented + return errNotImplemented } // GetContainer retrieves a container by its ID -func (r *Runtime) GetContainer(id string) (*ctr.Container, error) { - return nil, ctr.ErrNotImplemented +func (r *Runtime) GetContainer(id string) (*Container, error) { + r.lock.RLock() + defer r.lock.RUnlock() + + if !r.valid { + return nil, fmt.Errorf("runtime has already been shut down") + } + + return r.state.GetContainer(id) +} + +// HasContainer checks if a container with the given ID is present +func (r *Runtime) HasContainer(id string) (bool, error) { + r.lock.RLock() + defer r.lock.RUnlock() + + if !r.valid { + return false, fmt.Errorf("runtime has already been shut down") + } + + return r.state.HasContainer(id) } // LookupContainer looks up a container by its name or a partial ID // If a partial ID is not unique, an error will be returned -func (r *Runtime) LookupContainer(idOrName string) (*ctr.Container, error) { - return nil, ctr.ErrNotImplemented +func (r *Runtime) LookupContainer(idOrName string) (*Container, error) { + r.lock.RLock() + defer r.lock.RUnlock() + + if !r.valid { + return nil, fmt.Errorf("runtime has already been shut down") + } + + return r.state.LookupContainer(idOrName) } // GetContainers retrieves all containers from the state // Filters can be provided which will determine what containers are included in // the output. Multiple filters are handled by ANDing their output, so only // containers matching all filters are returned -func (r *Runtime) GetContainers(filters ...ContainerFilter) ([]*ctr.Container, error) { - return nil, ctr.ErrNotImplemented +func (r *Runtime) GetContainers(filters ...ContainerFilter) ([]*Container, error) { + r.lock.RLock() + defer r.lock.RUnlock() + + if !r.valid { + return nil, fmt.Errorf("runtime has already been shut down") + } + + ctrs, err := r.state.GetAllContainers() + if err != nil { + return nil, err + } + + ctrsFiltered := make([]*Container, 0, len(ctrs)) + + for _, ctr := range ctrs { + include := true + for _, filter := range filters { + include = include && filter(ctr) + } + + if include { + ctrsFiltered = append(ctrsFiltered, ctr) + } + } + + return ctrsFiltered, nil } // Pod API +// A PodCreateOption is a functional option which alters the Pod created by +// NewPod +type PodCreateOption func(*Pod) error + // PodFilter is a function to determine whether a pod is included in command // output. Pods to be outputted are tested using the function. A true return // will include the pod, a false return will exclude it. type PodFilter func(*Pod) bool // NewPod makes a new, empty pod -func (r *Runtime) NewPod() (*pod.Pod, error) { - return nil, ctr.ErrNotImplemented +func (r *Runtime) NewPod(options ...PodCreateOption) (*Pod, error) { + r.lock.Lock() + defer r.lock.Unlock() + + if !r.valid { + return nil, fmt.Errorf("runtime has already been shut down") + } + + pod, err := newPod() + if err != nil { + return nil, errors.Wrapf(err, "error creating pod") + } + + for _, option := range options { + if err := option(pod); err != nil { + return nil, errors.Wrapf(err, "error running pod create option") + } + } + + pod.valid = true + + if err := r.state.AddPod(pod); err != nil { + return nil, errors.Wrapf(err, "error adding pod to state") + } + + return nil, errNotImplemented } // RemovePod removes a pod and all containers in it @@ -191,24 +305,74 @@ func (r *Runtime) NewPod() (*pod.Pod, error) { // Otherwise, RemovePod will return an error if any container in the pod is running // Remove acts atomically, removing all containers or no containers func (r *Runtime) RemovePod(p *Pod, force bool) error { - return ctr.ErrNotImplemented + return errNotImplemented } // GetPod retrieves a pod by its ID -func (r *Runtime) GetPod(id string) (*pod.Pod, error) { - return nil, ctr.ErrNotImplemented +func (r *Runtime) GetPod(id string) (*Pod, error) { + r.lock.RLock() + defer r.lock.RUnlock() + + if !r.valid { + return nil, fmt.Errorf("runtime has already been shut down") + } + + return r.state.GetPod(id) +} + +// HasPod checks to see if a pod with the given ID exists +func (r *Runtime) HasPod(id string) (bool, error) { + r.lock.RLock() + defer r.lock.RUnlock() + + if !r.valid { + return false, fmt.Errorf("runtime has already been shut down") + } + + return r.state.HasPod(id) } // LookupPod retrieves a pod by its name or a partial ID // If a partial ID is not unique, an error will be returned -func (r *Runtime) LookupPod(idOrName string) (*pod.Pod, error) { - return nil, ctr.ErrNotImplemented +func (r *Runtime) LookupPod(idOrName string) (*Pod, error) { + r.lock.RLock() + defer r.lock.RUnlock() + + if !r.valid { + return nil, fmt.Errorf("runtime has already been shut down") + } + + return r.state.LookupPod(idOrName) } // GetPods retrieves all pods // Filters can be provided which will determine which pods are included in the // output. Multiple filters are handled by ANDing their output, so only pods // matching all filters are returned -func (r *Runtime) GetPods(filters ...PodFilter) ([]*pod.Pod, error) { - return nil, ctr.ErrNotImplemented +func (r *Runtime) GetPods(filters ...PodFilter) ([]*Pod, error) { + r.lock.RLock() + defer r.lock.RUnlock() + + if !r.valid { + return nil, fmt.Errorf("runtime has already been shut down") + } + + pods, err := r.state.GetAllPods() + if err != nil { + return nil, err + } + + podsFiltered := make([]*Pod, 0, len(pods)) + for _, pod := range pods { + include := true + for _, filter := range filters { + include = include && filter(pod) + } + + if include { + podsFiltered = append(podsFiltered, pod) + } + } + + return podsFiltered, nil }