From b96be3af1b9d00662758211420c955becbaf2f9e Mon Sep 17 00:00:00 2001 From: baude Date: Wed, 20 Jun 2018 13:23:24 -0500 Subject: changes to allow for darwin compilation Signed-off-by: baude Closes: #1015 Approved by: baude --- libpod/container.go | 12 +- libpod/container_darwin.go | 14 ++ libpod/container_internal.go | 43 +----- libpod/container_internal_linux.go | 52 +++++++ libpod/container_internal_unsupported.go | 7 + libpod/container_linux.go | 23 +++ libpod/container_windows.go | 14 ++ libpod/errors.go | 4 + libpod/networking.go | 214 -------------------------- libpod/networking_linux.go | 230 ++++++++++++++++++++++++++++ libpod/oci.go | 27 +--- libpod/oci_linux.go | 41 +++++ libpod/oci_unsupported.go | 7 + libpod/runtime_pod.go | 246 ------------------------------ libpod/runtime_pod_linux.go | 249 +++++++++++++++++++++++++++++++ libpod/runtime_pod_unsupported.go | 16 ++ libpod/stats.go | 35 +---- libpod/stats_config.go | 18 +++ libpod/stats_unsupported.go | 8 + 19 files changed, 696 insertions(+), 564 deletions(-) create mode 100644 libpod/container_darwin.go create mode 100644 libpod/container_internal_linux.go create mode 100644 libpod/container_internal_unsupported.go create mode 100644 libpod/container_linux.go create mode 100644 libpod/container_windows.go delete mode 100644 libpod/networking.go create mode 100644 libpod/networking_linux.go create mode 100644 libpod/oci_linux.go create mode 100644 libpod/oci_unsupported.go create mode 100644 libpod/runtime_pod_linux.go create mode 100644 libpod/runtime_pod_unsupported.go create mode 100644 libpod/stats_config.go create mode 100644 libpod/stats_unsupported.go (limited to 'libpod') diff --git a/libpod/container.go b/libpod/container.go index b7189ae0b..2931aee30 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -8,7 +8,6 @@ import ( "github.com/containernetworking/cni/pkg/types" cnitypes "github.com/containernetworking/cni/pkg/types/current" - "github.com/containernetworking/plugins/pkg/ns" "github.com/containers/storage" "github.com/cri-o/ocicni/pkg/ocicni" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -141,10 +140,6 @@ type containerState struct { OOMKilled bool `json:"oomKilled,omitempty"` // PID is the PID of a running container PID int `json:"pid,omitempty"` - // NetNSPath is the path of the container's network namespace - // Will only be set if config.CreateNetNS is true, or the container was - // told to join another container's network namespace - NetNS ns.NetNS `json:"-"` // ExecSessions contains active exec sessions for container // Exec session ID is mapped to PID of exec process ExecSessions map[string]*ExecSession `json:"execSessions,omitempty"` @@ -177,6 +172,13 @@ type containerState struct { // ExtensionStageHooks holds hooks which will be executed by libpod // and not delegated to the OCI runtime. ExtensionStageHooks map[string][]spec.Hook `json:"extensionStageHooks,omitempty"` + + // Special container state attributes for Linux + containerStateLinux + // Special container state attributes for Windows + containerStateWindows + // Special container state attributes for Darwin + containerStateDarwin } // ExecSession contains information on an active exec session diff --git a/libpod/container_darwin.go b/libpod/container_darwin.go new file mode 100644 index 000000000..948ed9f2a --- /dev/null +++ b/libpod/container_darwin.go @@ -0,0 +1,14 @@ +// +build darwin + +package libpod + +type containerStateDarwin struct { +} + +// containerStateLinux is intentionally left as a blank stub +type containerStateLinux struct { +} + +// containerStateWindows is intentionally left as a blank stub +type containerStateWindows struct { +} diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 07bb5c6d1..22b398709 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -14,7 +14,6 @@ import ( "syscall" "time" - "github.com/containerd/cgroups" "github.com/containers/storage" "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/chrootarchive" @@ -810,47 +809,6 @@ func (c *Container) prepare() (err error) { return nil } -// cleanupCgroup cleans up residual CGroups after container execution -// This is a no-op for the systemd cgroup driver -func (c *Container) cleanupCgroups() error { - if !c.state.CgroupCreated { - logrus.Debugf("Cgroups are not present, ignoring...") - return nil - } - - if c.runtime.config.CgroupManager == SystemdCgroupsManager { - return nil - } - - // Remove the base path of the container's cgroups - path := filepath.Join(c.config.CgroupParent, fmt.Sprintf("libpod-%s", c.ID())) - - logrus.Debugf("Removing CGroup %s", path) - - cgroup, err := cgroups.Load(cgroups.V1, cgroups.StaticPath(path)) - if err != nil { - // It's fine for the cgroup to not exist - // We want it gone, it's gone - if err == cgroups.ErrCgroupDeleted { - return nil - } - - return err - } - - if err := cgroup.Delete(); err != nil { - return err - } - - c.state.CgroupCreated = false - - if c.valid { - return c.save() - } - - return nil -} - // cleanupNetwork unmounts and cleans up the container's network func (c *Container) cleanupNetwork() error { if c.state.NetNS == nil { @@ -1258,6 +1216,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { return nil, errors.Wrapf(err, "error setting up OCI Hooks") } } + // Bind builtin image volumes if c.config.Rootfs == "" && c.config.ImageVolumes { if err := c.addImageVolumes(ctx, &g); err != nil { diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go new file mode 100644 index 000000000..41b8a86fc --- /dev/null +++ b/libpod/container_internal_linux.go @@ -0,0 +1,52 @@ +// +build linux + +package libpod + +import ( + "fmt" + "path/filepath" + + "github.com/containerd/cgroups" + "github.com/sirupsen/logrus" +) + +// cleanupCgroup cleans up residual CGroups after container execution +// This is a no-op for the systemd cgroup driver +func (c *Container) cleanupCgroups() error { + if !c.state.CgroupCreated { + logrus.Debugf("Cgroups are not present, ignoring...") + return nil + } + + if c.runtime.config.CgroupManager == SystemdCgroupsManager { + return nil + } + + // Remove the base path of the container's cgroups + path := filepath.Join(c.config.CgroupParent, fmt.Sprintf("libpod-%s", c.ID())) + + logrus.Debugf("Removing CGroup %s", path) + + cgroup, err := cgroups.Load(cgroups.V1, cgroups.StaticPath(path)) + if err != nil { + // It's fine for the cgroup to not exist + // We want it gone, it's gone + if err == cgroups.ErrCgroupDeleted { + return nil + } + + return err + } + + if err := cgroup.Delete(); err != nil { + return err + } + + c.state.CgroupCreated = false + + if c.valid { + return c.save() + } + + return nil +} diff --git a/libpod/container_internal_unsupported.go b/libpod/container_internal_unsupported.go new file mode 100644 index 000000000..015536850 --- /dev/null +++ b/libpod/container_internal_unsupported.go @@ -0,0 +1,7 @@ +// +build !linux + +package libpod + +func (c *Container) cleanupCgroups() error { + return ErrOSNotSupported +} diff --git a/libpod/container_linux.go b/libpod/container_linux.go new file mode 100644 index 000000000..b271a3089 --- /dev/null +++ b/libpod/container_linux.go @@ -0,0 +1,23 @@ +// +build linux + +package libpod + +import ( + "github.com/containernetworking/plugins/pkg/ns" +) + +type containerStateLinux struct { + + // NetNSPath is the path of the container's network namespace + // Will only be set if config.CreateNetNS is true, or the container was + // told to join another container's network namespace + NetNS ns.NetNS `json:"-"` +} + +// containerStateWindows is intentionally left as a blank stub +type containerStateWindows struct { +} + +// containerStateDarwin is intentionally left as a blank stub +type containerStateDarwin struct { +} diff --git a/libpod/container_windows.go b/libpod/container_windows.go new file mode 100644 index 000000000..db2bf24d7 --- /dev/null +++ b/libpod/container_windows.go @@ -0,0 +1,14 @@ +// +build windows + +package libpod + +type containerStateWindows struct { +} + +// containerStateLinux is intentionally left as a blank stub +type containerStateLinux struct { +} + +// containerStateDarwin is intentionally left as a blank stub +type containerStateDarwin struct { +} diff --git a/libpod/errors.go b/libpod/errors.go index 782104cf0..ddd586e29 100644 --- a/libpod/errors.go +++ b/libpod/errors.go @@ -66,4 +66,8 @@ var ( // ErrNotImplemented indicates that the requested functionality is not // yet present ErrNotImplemented = errors.New("not yet implemented") + + // ErrOSNotSupported indicates the function is not available on the particular + // OS. + ErrOSNotSupported = errors.New("No support for this OS yet") ) diff --git a/libpod/networking.go b/libpod/networking.go deleted file mode 100644 index afa18b534..000000000 --- a/libpod/networking.go +++ /dev/null @@ -1,214 +0,0 @@ -package libpod - -import ( - "crypto/rand" - "fmt" - "net" - "os" - "path/filepath" - "strings" - "syscall" - - cnitypes "github.com/containernetworking/cni/pkg/types/current" - "github.com/containernetworking/plugins/pkg/ns" - "github.com/cri-o/ocicni/pkg/ocicni" - "github.com/pkg/errors" - "github.com/projectatomic/libpod/utils" - "github.com/sirupsen/logrus" - "golang.org/x/sys/unix" -) - -// Get an OCICNI network config -func getPodNetwork(id, name, nsPath string, ports []ocicni.PortMapping) ocicni.PodNetwork { - return ocicni.PodNetwork{ - Name: name, - Namespace: name, // TODO is there something else we should put here? We don't know about Kube namespaces - ID: id, - NetNS: nsPath, - PortMappings: ports, - } -} - -// Create and configure a new network namespace for a container -func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) (err error) { - podNetwork := getPodNetwork(ctr.ID(), ctr.Name(), ctrNS.Path(), ctr.config.PortMappings) - - result, err := r.netPlugin.SetUpPod(podNetwork) - if err != nil { - return errors.Wrapf(err, "error configuring network namespace for container %s", ctr.ID()) - } - defer func() { - if err != nil { - if err2 := r.netPlugin.TearDownPod(podNetwork); err2 != nil { - logrus.Errorf("Error tearing down partially created network namespace for container %s: %v", ctr.ID(), err2) - } - } - }() - - logrus.Debugf("Response from CNI plugins: %v", result.String()) - - resultStruct, err := cnitypes.GetResult(result) - if err != nil { - return errors.Wrapf(err, "error parsing result from CNI plugins") - } - - ctr.state.NetNS = ctrNS - ctr.state.IPs = resultStruct.IPs - ctr.state.Routes = resultStruct.Routes - ctr.state.Interfaces = resultStruct.Interfaces - - // We need to temporarily use iptables to allow the container - // to resolve DNS until this issue is fixed upstream. - // https://github.com/containernetworking/plugins/pull/75 - if resultStruct.IPs != nil { - for _, ip := range resultStruct.IPs { - if ip.Address.IP.To4() != nil { - iptablesDNS("-I", ip.Address.IP.String()) - } - } - } - return nil -} - -// Create and configure a new network namespace for a container -func (r *Runtime) createNetNS(ctr *Container) (err error) { - ctrNS, err := ns.NewNS() - if err != nil { - return errors.Wrapf(err, "error creating network namespace for container %s", ctr.ID()) - } - defer func() { - if err != nil { - if err2 := ctrNS.Close(); err2 != nil { - logrus.Errorf("Error closing partially created network namespace for container %s: %v", ctr.ID(), err2) - } - } - }() - - logrus.Debugf("Made network namespace at %s for container %s", ctrNS.Path(), ctr.ID()) - return r.configureNetNS(ctr, ctrNS) -} - -// Configure the network namespace using the container process -func (r *Runtime) setupNetNS(ctr *Container) (err error) { - nsProcess := fmt.Sprintf("/proc/%d/ns/net", ctr.state.PID) - - b := make([]byte, 16) - - if _, err := rand.Reader.Read(b); err != nil { - return errors.Wrapf(err, "failed to generate random netns name") - } - - nsPath := fmt.Sprintf("/var/run/netns/cni-%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]) - - if err := os.MkdirAll(filepath.Dir(nsPath), 0711); err != nil { - return errors.Wrapf(err, "cannot create %s", filepath.Dir(nsPath)) - } - - mountPointFd, err := os.Create(nsPath) - if err != nil { - return errors.Wrapf(err, "cannot open %s", nsPath) - } - mountPointFd.Close() - - if err := unix.Mount(nsProcess, nsPath, "none", unix.MS_BIND, ""); err != nil { - return errors.Wrapf(err, "cannot mount %s", nsPath) - } - - netNS, err := ns.GetNS(nsPath) - if err != nil { - return err - } - return r.configureNetNS(ctr, netNS) -} - -// iptablesDNS accepts an arg (-I|-D) and IP address of the container and then -// generates an iptables command to either add or subtract the needed rule -func iptablesDNS(arg, ip string) error { - iptablesCmd := []string{"-t", "filter", arg, "FORWARD", "-s", ip, "!", "-o", ip, "-j", "ACCEPT"} - logrus.Debug("Running iptables command: ", strings.Join(iptablesCmd, " ")) - _, err := utils.ExecCmd("iptables", iptablesCmd...) - if err != nil { - logrus.Error(err) - } - return err -} - -// Join an existing network namespace -func joinNetNS(path string) (ns.NetNS, error) { - ns, err := ns.GetNS(path) - if err != nil { - return nil, errors.Wrapf(err, "error retrieving network namespace at %s", path) - } - - return ns, nil -} - -// Get a container's IP address -func (r *Runtime) getContainerIP(ctr *Container) (net.IP, error) { - if ctr.state.NetNS == nil { - return nil, errors.Wrapf(ErrInvalidArg, "container %s has no network namespace, cannot get IP", ctr.ID()) - } - - podNetwork := getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), ctr.config.PortMappings) - - ipStr, err := r.netPlugin.GetPodNetworkStatus(podNetwork) - if err != nil { - return nil, errors.Wrapf(err, "error retrieving network status of container %s", ctr.ID()) - } - - ip := net.ParseIP(ipStr) - if ip == nil { - return nil, errors.Wrapf(ErrInternal, "error parsing IP address %s for container %s", ipStr, ctr.ID()) - } - - return ip, nil -} - -// Tear down a network namespace -func (r *Runtime) teardownNetNS(ctr *Container) error { - if ctr.state.NetNS == nil { - // The container has no network namespace, we're set - return nil - } - - // Because we are using iptables to allow the container to resolve DNS - // on per IP address, we also need to try to remove the iptables rule - // on cleanup. Remove when https://github.com/containernetworking/plugins/pull/75 - // is merged. - for _, ip := range ctr.state.IPs { - if ip.Address.IP.To4() != nil { - iptablesDNS("-D", ip.Address.IP.String()) - } - } - - logrus.Debugf("Tearing down network namespace at %s for container %s", ctr.state.NetNS.Path(), ctr.ID()) - - podNetwork := getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), ctr.config.PortMappings) - - // The network may have already been torn down, so don't fail here, just log - if err := r.netPlugin.TearDownPod(podNetwork); err != nil { - logrus.Errorf("Failed to tear down network namespace for container %s: %v", ctr.ID(), err) - } - - nsPath := ctr.state.NetNS.Path() - - if err := ctr.state.NetNS.Close(); err != nil { - return errors.Wrapf(err, "error closing network namespace for container %s", ctr.ID()) - } - - // We need to unconditionally try to unmount/remove the namespace - // because we may be in a separate process from the one that created the - // namespace, and Close() will only do that if it is the same process. - if err := unix.Unmount(nsPath, unix.MNT_DETACH); err != nil { - if err != syscall.EINVAL && err != syscall.ENOENT { - return errors.Wrapf(err, "error unmounting network namespace %s for container %s", nsPath, ctr.ID()) - } - } - if err := os.RemoveAll(nsPath); err != nil && !os.IsNotExist(err) { - return errors.Wrapf(err, "error removing network namespace %s for container %s", nsPath, ctr.ID()) - } - - ctr.state.NetNS = nil - - return nil -} diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go new file mode 100644 index 000000000..ee90b765c --- /dev/null +++ b/libpod/networking_linux.go @@ -0,0 +1,230 @@ +// +build linux + +package libpod + +import ( + "crypto/rand" + "fmt" + "net" + "os" + "path/filepath" + "strings" + "syscall" + + cnitypes "github.com/containernetworking/cni/pkg/types/current" + "github.com/containernetworking/plugins/pkg/ns" + "github.com/cri-o/ocicni/pkg/ocicni" + "github.com/pkg/errors" + "github.com/projectatomic/libpod/utils" + "github.com/sirupsen/logrus" + "github.com/vishvananda/netlink" + "golang.org/x/sys/unix" +) + +// Get an OCICNI network config +func getPodNetwork(id, name, nsPath string, ports []ocicni.PortMapping) ocicni.PodNetwork { + return ocicni.PodNetwork{ + Name: name, + Namespace: name, // TODO is there something else we should put here? We don't know about Kube namespaces + ID: id, + NetNS: nsPath, + PortMappings: ports, + } +} + +// Create and configure a new network namespace for a container +func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) (err error) { + podNetwork := getPodNetwork(ctr.ID(), ctr.Name(), ctrNS.Path(), ctr.config.PortMappings) + + result, err := r.netPlugin.SetUpPod(podNetwork) + if err != nil { + return errors.Wrapf(err, "error configuring network namespace for container %s", ctr.ID()) + } + defer func() { + if err != nil { + if err2 := r.netPlugin.TearDownPod(podNetwork); err2 != nil { + logrus.Errorf("Error tearing down partially created network namespace for container %s: %v", ctr.ID(), err2) + } + } + }() + + logrus.Debugf("Response from CNI plugins: %v", result.String()) + + resultStruct, err := cnitypes.GetResult(result) + if err != nil { + return errors.Wrapf(err, "error parsing result from CNI plugins") + } + + ctr.state.NetNS = ctrNS + ctr.state.IPs = resultStruct.IPs + ctr.state.Routes = resultStruct.Routes + ctr.state.Interfaces = resultStruct.Interfaces + + // We need to temporarily use iptables to allow the container + // to resolve DNS until this issue is fixed upstream. + // https://github.com/containernetworking/plugins/pull/75 + if resultStruct.IPs != nil { + for _, ip := range resultStruct.IPs { + if ip.Address.IP.To4() != nil { + iptablesDNS("-I", ip.Address.IP.String()) + } + } + } + return nil +} + +// Create and configure a new network namespace for a container +func (r *Runtime) createNetNS(ctr *Container) (err error) { + ctrNS, err := ns.NewNS() + if err != nil { + return errors.Wrapf(err, "error creating network namespace for container %s", ctr.ID()) + } + defer func() { + if err != nil { + if err2 := ctrNS.Close(); err2 != nil { + logrus.Errorf("Error closing partially created network namespace for container %s: %v", ctr.ID(), err2) + } + } + }() + + logrus.Debugf("Made network namespace at %s for container %s", ctrNS.Path(), ctr.ID()) + return r.configureNetNS(ctr, ctrNS) +} + +// Configure the network namespace using the container process +func (r *Runtime) setupNetNS(ctr *Container) (err error) { + nsProcess := fmt.Sprintf("/proc/%d/ns/net", ctr.state.PID) + + b := make([]byte, 16) + + if _, err := rand.Reader.Read(b); err != nil { + return errors.Wrapf(err, "failed to generate random netns name") + } + + nsPath := fmt.Sprintf("/var/run/netns/cni-%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]) + + if err := os.MkdirAll(filepath.Dir(nsPath), 0711); err != nil { + return errors.Wrapf(err, "cannot create %s", filepath.Dir(nsPath)) + } + + mountPointFd, err := os.Create(nsPath) + if err != nil { + return errors.Wrapf(err, "cannot open %s", nsPath) + } + mountPointFd.Close() + + if err := unix.Mount(nsProcess, nsPath, "none", unix.MS_BIND, ""); err != nil { + return errors.Wrapf(err, "cannot mount %s", nsPath) + } + + netNS, err := ns.GetNS(nsPath) + if err != nil { + return err + } + return r.configureNetNS(ctr, netNS) +} + +// iptablesDNS accepts an arg (-I|-D) and IP address of the container and then +// generates an iptables command to either add or subtract the needed rule +func iptablesDNS(arg, ip string) error { + iptablesCmd := []string{"-t", "filter", arg, "FORWARD", "-s", ip, "!", "-o", ip, "-j", "ACCEPT"} + logrus.Debug("Running iptables command: ", strings.Join(iptablesCmd, " ")) + _, err := utils.ExecCmd("iptables", iptablesCmd...) + if err != nil { + logrus.Error(err) + } + return err +} + +// Join an existing network namespace +func joinNetNS(path string) (ns.NetNS, error) { + ns, err := ns.GetNS(path) + if err != nil { + return nil, errors.Wrapf(err, "error retrieving network namespace at %s", path) + } + + return ns, nil +} + +// Get a container's IP address +func (r *Runtime) getContainerIP(ctr *Container) (net.IP, error) { + if ctr.state.NetNS == nil { + return nil, errors.Wrapf(ErrInvalidArg, "container %s has no network namespace, cannot get IP", ctr.ID()) + } + + podNetwork := getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), ctr.config.PortMappings) + + ipStr, err := r.netPlugin.GetPodNetworkStatus(podNetwork) + if err != nil { + return nil, errors.Wrapf(err, "error retrieving network status of container %s", ctr.ID()) + } + + ip := net.ParseIP(ipStr) + if ip == nil { + return nil, errors.Wrapf(ErrInternal, "error parsing IP address %s for container %s", ipStr, ctr.ID()) + } + + return ip, nil +} + +// Tear down a network namespace +func (r *Runtime) teardownNetNS(ctr *Container) error { + if ctr.state.NetNS == nil { + // The container has no network namespace, we're set + return nil + } + + // Because we are using iptables to allow the container to resolve DNS + // on per IP address, we also need to try to remove the iptables rule + // on cleanup. Remove when https://github.com/containernetworking/plugins/pull/75 + // is merged. + for _, ip := range ctr.state.IPs { + if ip.Address.IP.To4() != nil { + iptablesDNS("-D", ip.Address.IP.String()) + } + } + + logrus.Debugf("Tearing down network namespace at %s for container %s", ctr.state.NetNS.Path(), ctr.ID()) + + podNetwork := getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), ctr.config.PortMappings) + + // The network may have already been torn down, so don't fail here, just log + if err := r.netPlugin.TearDownPod(podNetwork); err != nil { + logrus.Errorf("Failed to tear down network namespace for container %s: %v", ctr.ID(), err) + } + + nsPath := ctr.state.NetNS.Path() + + if err := ctr.state.NetNS.Close(); err != nil { + return errors.Wrapf(err, "error closing network namespace for container %s", ctr.ID()) + } + + // We need to unconditionally try to unmount/remove the namespace + // because we may be in a separate process from the one that created the + // namespace, and Close() will only do that if it is the same process. + if err := unix.Unmount(nsPath, unix.MNT_DETACH); err != nil { + if err != syscall.EINVAL && err != syscall.ENOENT { + return errors.Wrapf(err, "error unmounting network namespace %s for container %s", nsPath, ctr.ID()) + } + } + if err := os.RemoveAll(nsPath); err != nil && !os.IsNotExist(err) { + return errors.Wrapf(err, "error removing network namespace %s for container %s", nsPath, ctr.ID()) + } + + ctr.state.NetNS = nil + + return nil +} + +func getContainerNetIO(ctr *Container) (*netlink.LinkStatistics, error) { + var netStats *netlink.LinkStatistics + err := ns.WithNetNSPath(ctr.state.NetNS.Path(), func(_ ns.NetNS) error { + link, err := netlink.LinkByName(ocicni.DefaultInterfaceName) + if err != nil { + return err + } + netStats = link.Attrs().Statistics + return nil + }) + return netStats, err +} diff --git a/libpod/oci.go b/libpod/oci.go index dfed3d6b8..3a7d6be3a 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -15,11 +15,10 @@ import ( "syscall" "time" - "github.com/containerd/cgroups" "github.com/containers/storage/pkg/idtools" "github.com/coreos/go-systemd/activation" spec "github.com/opencontainers/runtime-spec/specs-go" - selinux "github.com/opencontainers/selinux/go-selinux" + "github.com/opencontainers/selinux/go-selinux" "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -323,7 +322,6 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string) (er fds := activation.Files(false) cmd.ExtraFiles = append(cmd.ExtraFiles, fds...) } - if selinux.GetEnabled() { // Set the label of the conmon process to be level :s0 // This will allow the container processes to talk to fifo-files @@ -361,27 +359,8 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string) (er childStartPipe.Close() // Move conmon to specified cgroup - if os.Getuid() == 0 { - if r.cgroupManager == SystemdCgroupsManager { - unitName := createUnitName("libpod-conmon", ctr.ID()) - - logrus.Infof("Running conmon under slice %s and unitName %s", cgroupParent, unitName) - if err = utils.RunUnderSystemdScope(cmd.Process.Pid, cgroupParent, unitName); err != nil { - logrus.Warnf("Failed to add conmon to systemd sandbox cgroup: %v", err) - } - } else { - cgroupPath := filepath.Join(ctr.config.CgroupParent, fmt.Sprintf("libpod-%s", ctr.ID()), "conmon") - control, err := cgroups.New(cgroups.V1, cgroups.StaticPath(cgroupPath), &spec.LinuxResources{}) - if err != nil { - logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err) - } else { - // we need to remove this defer and delete the cgroup once conmon exits - // maybe need a conmon monitor? - if err := control.Add(cgroups.Process{Pid: cmd.Process.Pid}); err != nil { - logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err) - } - } - } + if err := r.moveConmonToCgroup(ctr, cgroupParent, cmd); err != nil { + return err } /* We set the cgroup, now the child can start creating children */ diff --git a/libpod/oci_linux.go b/libpod/oci_linux.go new file mode 100644 index 000000000..14373cbb2 --- /dev/null +++ b/libpod/oci_linux.go @@ -0,0 +1,41 @@ +// +build linux + +package libpod + +import ( + "fmt" + "os" + "os/exec" + "path/filepath" + + "github.com/containerd/cgroups" + spec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/projectatomic/libpod/utils" + "github.com/sirupsen/logrus" +) + +func (r *OCIRuntime) moveConmonToCgroup(ctr *Container, cgroupParent string, cmd *exec.Cmd) error { + if os.Getuid() == 0 { + if r.cgroupManager == SystemdCgroupsManager { + unitName := createUnitName("libpod-conmon", ctr.ID()) + + logrus.Infof("Running conmon under slice %s and unitName %s", cgroupParent, unitName) + if err := utils.RunUnderSystemdScope(cmd.Process.Pid, cgroupParent, unitName); err != nil { + logrus.Warnf("Failed to add conmon to systemd sandbox cgroup: %v", err) + } + } else { + cgroupPath := filepath.Join(ctr.config.CgroupParent, fmt.Sprintf("libpod-%s", ctr.ID()), "conmon") + control, err := cgroups.New(cgroups.V1, cgroups.StaticPath(cgroupPath), &spec.LinuxResources{}) + if err != nil { + logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err) + } else { + // we need to remove this defer and delete the cgroup once conmon exits + // maybe need a conmon monitor? + if err := control.Add(cgroups.Process{Pid: cmd.Process.Pid}); err != nil { + logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err) + } + } + } + } + return nil +} diff --git a/libpod/oci_unsupported.go b/libpod/oci_unsupported.go new file mode 100644 index 000000000..6e5601eac --- /dev/null +++ b/libpod/oci_unsupported.go @@ -0,0 +1,7 @@ +// +build !linux + +package libpod + +func moveConmonToCgroup() error { + return ErrOSNotSupported +} diff --git a/libpod/runtime_pod.go b/libpod/runtime_pod.go index 067a110fa..af277c5cc 100644 --- a/libpod/runtime_pod.go +++ b/libpod/runtime_pod.go @@ -1,16 +1,5 @@ package libpod -import ( - "context" - "path" - "path/filepath" - "strings" - - "github.com/containerd/cgroups" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - // Contains the public Runtime API for pods // A PodCreateOption is a functional option which alters the Pod created by @@ -22,241 +11,6 @@ type PodCreateOption func(*Pod) error // 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(options ...PodCreateOption) (*Pod, error) { - r.lock.Lock() - defer r.lock.Unlock() - - if !r.valid { - return nil, ErrRuntimeStopped - } - - pod, err := newPod(r.lockDir, r) - 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") - } - } - - if pod.config.Name == "" { - name, err := r.generateName() - if err != nil { - return nil, err - } - pod.config.Name = name - } - - pod.valid = true - - // Check CGroup parent sanity, and set it if it was not set - switch r.config.CgroupManager { - case CgroupfsCgroupsManager: - if pod.config.CgroupParent == "" { - pod.config.CgroupParent = CgroupfsDefaultCgroupParent - } else if strings.HasSuffix(path.Base(pod.config.CgroupParent), ".slice") { - return nil, errors.Wrapf(ErrInvalidArg, "systemd slice received as cgroup parent when using cgroupfs") - } - // Creating CGroup path is currently a NOOP until proper systemd - // cgroup management is merged - case SystemdCgroupsManager: - if pod.config.CgroupParent == "" { - pod.config.CgroupParent = SystemdDefaultCgroupParent - } else if len(pod.config.CgroupParent) < 6 || !strings.HasSuffix(path.Base(pod.config.CgroupParent), ".slice") { - return nil, errors.Wrapf(ErrInvalidArg, "did not receive systemd slice as cgroup parent when using systemd to manage cgroups") - } - // If we are set to use pod cgroups, set the cgroup parent that - // all containers in the pod will share - // No need to create it with cgroupfs - the first container to - // launch should do it for us - if pod.config.UsePodCgroup { - pod.state.CgroupPath = filepath.Join(pod.config.CgroupParent, pod.ID()) - } - default: - return nil, errors.Wrapf(ErrInvalidArg, "unsupported CGroup manager: %s - cannot validate cgroup parent", r.config.CgroupManager) - } - - 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 -// If removeCtrs is specified, containers will be removed -// Otherwise, a pod that is not empty will return an error and not be removed -// If force is specified with removeCtrs, all containers will be stopped before -// being removed -// Otherwise, the pod will not be removed if any containers are running -func (r *Runtime) RemovePod(ctx context.Context, p *Pod, removeCtrs, force bool) error { - r.lock.Lock() - defer r.lock.Unlock() - - if !r.valid { - return ErrRuntimeStopped - } - - p.lock.Lock() - defer p.lock.Unlock() - - if !p.valid { - return ErrPodRemoved - } - - ctrs, err := r.state.PodContainers(p) - if err != nil { - return err - } - - numCtrs := len(ctrs) - - if !removeCtrs && numCtrs > 0 { - return errors.Wrapf(ErrCtrExists, "pod %s contains containers and cannot be removed", p.ID()) - } - - // Go through and lock all containers so we can operate on them all at once - dependencies := make(map[string][]string) - for _, ctr := range ctrs { - ctr.lock.Lock() - defer ctr.lock.Unlock() - - // Sync all containers - if err := ctr.syncContainer(); err != nil { - return err - } - - // Check if the container is in a good state to be removed - if ctr.state.State == ContainerStatePaused { - return errors.Wrapf(ErrCtrStateInvalid, "pod %s contains paused container %s, cannot remove", p.ID(), ctr.ID()) - } - - if ctr.state.State == ContainerStateUnknown { - return errors.Wrapf(ErrCtrStateInvalid, "pod %s contains container %s with invalid state", p.ID(), ctr.ID()) - } - - // If the container is running and force is not set we can't do anything - if ctr.state.State == ContainerStateRunning && !force { - return errors.Wrapf(ErrCtrStateInvalid, "pod %s contains container %s which is running", p.ID(), ctr.ID()) - } - - // If the container has active exec sessions and force is not set we can't do anything - if len(ctr.state.ExecSessions) != 0 && !force { - return errors.Wrapf(ErrCtrStateInvalid, "pod %s contains container %s which has active exec sessions", p.ID(), ctr.ID()) - } - - deps, err := r.state.ContainerInUse(ctr) - if err != nil { - return err - } - dependencies[ctr.ID()] = deps - } - - // Check if containers have dependencies - // If they do, and the dependencies are not in the pod, error - for ctr, deps := range dependencies { - for _, dep := range deps { - if _, ok := dependencies[dep]; !ok { - return errors.Wrapf(ErrCtrExists, "container %s depends on container %s not in pod %s", ctr, dep, p.ID()) - } - } - } - - // First loop through all containers and stop them - // Do not remove in this loop to ensure that we don't remove unless all - // containers are in a good state - if force { - for _, ctr := range ctrs { - // If force is set and the container is running, stop it now - if ctr.state.State == ContainerStateRunning { - if err := r.ociRuntime.stopContainer(ctr, ctr.StopTimeout()); err != nil { - return errors.Wrapf(err, "error stopping container %s to remove pod %s", ctr.ID(), p.ID()) - } - - // Sync again to pick up stopped state - if err := ctr.syncContainer(); err != nil { - return err - } - } - // If the container has active exec sessions, stop them now - if len(ctr.state.ExecSessions) != 0 { - if err := r.ociRuntime.execStopContainer(ctr, ctr.StopTimeout()); err != nil { - return err - } - } - } - } - - // Start removing containers - // We can remove containers even if they have dependencies now - // As we have guaranteed their dependencies are in the pod - for _, ctr := range ctrs { - // Clean up network namespace, cgroups, mounts - if err := ctr.cleanup(); err != nil { - return err - } - - // Stop container's storage - if err := ctr.teardownStorage(); err != nil { - return err - } - - // Delete the container from runtime (only if we are not - // ContainerStateConfigured) - if ctr.state.State != ContainerStateConfigured { - if err := ctr.delete(ctx); err != nil { - return err - } - } - } - - // Remove containers from the state - if err := r.state.RemovePodContainers(p); err != nil { - return err - } - - // Mark containers invalid - for _, ctr := range ctrs { - ctr.valid = false - } - - // Remove pod cgroup, if present - if p.state.CgroupPath != "" { - switch p.runtime.config.CgroupManager { - case SystemdCgroupsManager: - // NOOP for now, until proper systemd cgroup management - // is implemented - case CgroupfsCgroupsManager: - // Delete the cgroupfs cgroup - logrus.Debugf("Removing pod cgroup %s", p.state.CgroupPath) - - cgroup, err := cgroups.Load(cgroups.V1, cgroups.StaticPath(p.state.CgroupPath)) - if err != nil && err != cgroups.ErrCgroupDeleted { - return err - } else if err == nil { - if err := cgroup.Delete(); err != nil { - return err - } - } - default: - return errors.Wrapf(ErrInvalidArg, "unknown cgroups manager %s specified", p.runtime.config.CgroupManager) - } - } - - // Remove pod from state - if err := r.state.RemovePod(p); err != nil { - return err - } - - // Mark pod invalid - p.valid = false - - return nil -} - // GetPod retrieves a pod by its ID func (r *Runtime) GetPod(id string) (*Pod, error) { r.lock.RLock() diff --git a/libpod/runtime_pod_linux.go b/libpod/runtime_pod_linux.go new file mode 100644 index 000000000..0edac9dbe --- /dev/null +++ b/libpod/runtime_pod_linux.go @@ -0,0 +1,249 @@ +// +build linux + +package libpod + +import ( + "context" + "path" + "path/filepath" + "strings" + + "github.com/containerd/cgroups" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +// NewPod makes a new, empty pod +func (r *Runtime) NewPod(options ...PodCreateOption) (*Pod, error) { + r.lock.Lock() + defer r.lock.Unlock() + + if !r.valid { + return nil, ErrRuntimeStopped + } + + pod, err := newPod(r.lockDir, r) + 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") + } + } + + if pod.config.Name == "" { + name, err := r.generateName() + if err != nil { + return nil, err + } + pod.config.Name = name + } + + pod.valid = true + + // Check CGroup parent sanity, and set it if it was not set + switch r.config.CgroupManager { + case CgroupfsCgroupsManager: + if pod.config.CgroupParent == "" { + pod.config.CgroupParent = CgroupfsDefaultCgroupParent + } else if strings.HasSuffix(path.Base(pod.config.CgroupParent), ".slice") { + return nil, errors.Wrapf(ErrInvalidArg, "systemd slice received as cgroup parent when using cgroupfs") + } + // Creating CGroup path is currently a NOOP until proper systemd + // cgroup management is merged + case SystemdCgroupsManager: + if pod.config.CgroupParent == "" { + pod.config.CgroupParent = SystemdDefaultCgroupParent + } else if len(pod.config.CgroupParent) < 6 || !strings.HasSuffix(path.Base(pod.config.CgroupParent), ".slice") { + return nil, errors.Wrapf(ErrInvalidArg, "did not receive systemd slice as cgroup parent when using systemd to manage cgroups") + } + // If we are set to use pod cgroups, set the cgroup parent that + // all containers in the pod will share + // No need to create it with cgroupfs - the first container to + // launch should do it for us + if pod.config.UsePodCgroup { + pod.state.CgroupPath = filepath.Join(pod.config.CgroupParent, pod.ID()) + } + default: + return nil, errors.Wrapf(ErrInvalidArg, "unsupported CGroup manager: %s - cannot validate cgroup parent", r.config.CgroupManager) + } + + 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 +// If removeCtrs is specified, containers will be removed +// Otherwise, a pod that is not empty will return an error and not be removed +// If force is specified with removeCtrs, all containers will be stopped before +// being removed +// Otherwise, the pod will not be removed if any containers are running +func (r *Runtime) RemovePod(ctx context.Context, p *Pod, removeCtrs, force bool) error { + r.lock.Lock() + defer r.lock.Unlock() + + if !r.valid { + return ErrRuntimeStopped + } + + p.lock.Lock() + defer p.lock.Unlock() + + if !p.valid { + return ErrPodRemoved + } + + ctrs, err := r.state.PodContainers(p) + if err != nil { + return err + } + + numCtrs := len(ctrs) + + if !removeCtrs && numCtrs > 0 { + return errors.Wrapf(ErrCtrExists, "pod %s contains containers and cannot be removed", p.ID()) + } + + // Go through and lock all containers so we can operate on them all at once + dependencies := make(map[string][]string) + for _, ctr := range ctrs { + ctr.lock.Lock() + defer ctr.lock.Unlock() + + // Sync all containers + if err := ctr.syncContainer(); err != nil { + return err + } + + // Check if the container is in a good state to be removed + if ctr.state.State == ContainerStatePaused { + return errors.Wrapf(ErrCtrStateInvalid, "pod %s contains paused container %s, cannot remove", p.ID(), ctr.ID()) + } + + if ctr.state.State == ContainerStateUnknown { + return errors.Wrapf(ErrCtrStateInvalid, "pod %s contains container %s with invalid state", p.ID(), ctr.ID()) + } + + // If the container is running and force is not set we can't do anything + if ctr.state.State == ContainerStateRunning && !force { + return errors.Wrapf(ErrCtrStateInvalid, "pod %s contains container %s which is running", p.ID(), ctr.ID()) + } + + // If the container has active exec sessions and force is not set we can't do anything + if len(ctr.state.ExecSessions) != 0 && !force { + return errors.Wrapf(ErrCtrStateInvalid, "pod %s contains container %s which has active exec sessions", p.ID(), ctr.ID()) + } + + deps, err := r.state.ContainerInUse(ctr) + if err != nil { + return err + } + dependencies[ctr.ID()] = deps + } + + // Check if containers have dependencies + // If they do, and the dependencies are not in the pod, error + for ctr, deps := range dependencies { + for _, dep := range deps { + if _, ok := dependencies[dep]; !ok { + return errors.Wrapf(ErrCtrExists, "container %s depends on container %s not in pod %s", ctr, dep, p.ID()) + } + } + } + + // First loop through all containers and stop them + // Do not remove in this loop to ensure that we don't remove unless all + // containers are in a good state + if force { + for _, ctr := range ctrs { + // If force is set and the container is running, stop it now + if ctr.state.State == ContainerStateRunning { + if err := r.ociRuntime.stopContainer(ctr, ctr.StopTimeout()); err != nil { + return errors.Wrapf(err, "error stopping container %s to remove pod %s", ctr.ID(), p.ID()) + } + + // Sync again to pick up stopped state + if err := ctr.syncContainer(); err != nil { + return err + } + } + // If the container has active exec sessions, stop them now + if len(ctr.state.ExecSessions) != 0 { + if err := r.ociRuntime.execStopContainer(ctr, ctr.StopTimeout()); err != nil { + return err + } + } + } + } + + // Start removing containers + // We can remove containers even if they have dependencies now + // As we have guaranteed their dependencies are in the pod + for _, ctr := range ctrs { + // Clean up network namespace, cgroups, mounts + if err := ctr.cleanup(); err != nil { + return err + } + + // Stop container's storage + if err := ctr.teardownStorage(); err != nil { + return err + } + + // Delete the container from runtime (only if we are not + // ContainerStateConfigured) + if ctr.state.State != ContainerStateConfigured { + if err := ctr.delete(ctx); err != nil { + return err + } + } + } + + // Remove containers from the state + if err := r.state.RemovePodContainers(p); err != nil { + return err + } + + // Mark containers invalid + for _, ctr := range ctrs { + ctr.valid = false + } + + // Remove pod cgroup, if present + if p.state.CgroupPath != "" { + switch p.runtime.config.CgroupManager { + case SystemdCgroupsManager: + // NOOP for now, until proper systemd cgroup management + // is implemented + case CgroupfsCgroupsManager: + // Delete the cgroupfs cgroup + logrus.Debugf("Removing pod cgroup %s", p.state.CgroupPath) + + cgroup, err := cgroups.Load(cgroups.V1, cgroups.StaticPath(p.state.CgroupPath)) + if err != nil && err != cgroups.ErrCgroupDeleted { + return err + } else if err == nil { + if err := cgroup.Delete(); err != nil { + return err + } + } + default: + return errors.Wrapf(ErrInvalidArg, "unknown cgroups manager %s specified", p.runtime.config.CgroupManager) + } + } + + // Remove pod from state + if err := r.state.RemovePod(p); err != nil { + return err + } + + // Mark pod invalid + p.valid = false + + return nil +} diff --git a/libpod/runtime_pod_unsupported.go b/libpod/runtime_pod_unsupported.go new file mode 100644 index 000000000..b22f151d8 --- /dev/null +++ b/libpod/runtime_pod_unsupported.go @@ -0,0 +1,16 @@ +// +build !linux + +package libpod + +import ( + "context" +) + +// NewPod makes a new, empty pod +func (r *Runtime) NewPod(options ...PodCreateOption) (*Pod, error) { + return nil, ErrOSNotSupported +} + +func (r *Runtime) RemovePod(ctx context.Context, p *Pod, removeCtrs, force bool) error { + return ErrOSNotSupported +} diff --git a/libpod/stats.go b/libpod/stats.go index 52bcc901d..262c1ac00 100644 --- a/libpod/stats.go +++ b/libpod/stats.go @@ -1,3 +1,5 @@ +// +build linux + package libpod import ( @@ -6,29 +8,9 @@ import ( "time" "github.com/containerd/cgroups" - "github.com/containernetworking/plugins/pkg/ns" - "github.com/cri-o/ocicni/pkg/ocicni" "github.com/pkg/errors" - "github.com/vishvananda/netlink" ) -// ContainerStats contains the statistics information for a running container -type ContainerStats struct { - ContainerID string - Name string - CPU float64 - CPUNano uint64 - SystemNano uint64 - MemUsage uint64 - MemLimit uint64 - MemPerc float64 - NetInput uint64 - NetOutput uint64 - BlockInput uint64 - BlockOutput uint64 - PIDs uint64 -} - // GetContainerStats gets the running stats for a given container func (c *Container) GetContainerStats(previousStats *ContainerStats) (*ContainerStats, error) { stats := new(ContainerStats) @@ -103,19 +85,6 @@ func getMemLimit(cgroupLimit uint64) uint64 { return cgroupLimit } -func getContainerNetIO(ctr *Container) (*netlink.LinkStatistics, error) { - var netStats *netlink.LinkStatistics - err := ns.WithNetNSPath(ctr.state.NetNS.Path(), func(_ ns.NetNS) error { - link, err := netlink.LinkByName(ocicni.DefaultInterfaceName) - if err != nil { - return err - } - netStats = link.Attrs().Statistics - return nil - }) - return netStats, err -} - func calculateCPUPercent(stats *cgroups.Metrics, previousCPU, previousSystem uint64) float64 { var ( cpuPercent = 0.0 diff --git a/libpod/stats_config.go b/libpod/stats_config.go new file mode 100644 index 000000000..9c7d97298 --- /dev/null +++ b/libpod/stats_config.go @@ -0,0 +1,18 @@ +package libpod + +// ContainerStats contains the statistics information for a running container +type ContainerStats struct { + ContainerID string + Name string + CPU float64 + CPUNano uint64 + SystemNano uint64 + MemUsage uint64 + MemLimit uint64 + MemPerc float64 + NetInput uint64 + NetOutput uint64 + BlockInput uint64 + BlockOutput uint64 + PIDs uint64 +} diff --git a/libpod/stats_unsupported.go b/libpod/stats_unsupported.go new file mode 100644 index 000000000..7117413eb --- /dev/null +++ b/libpod/stats_unsupported.go @@ -0,0 +1,8 @@ +// +build !linux + +package libpod + +// GetContainerStats gets the running stats for a given container +func (c *Container) GetContainerStats(previousStats *ContainerStats) (*ContainerStats, error) { + return nil, ErrOSNotSupported +} -- cgit v1.2.3-54-g00ecf