diff options
Diffstat (limited to 'libpod')
-rw-r--r-- | libpod/container.go | 3 | ||||
-rw-r--r-- | libpod/container_api.go | 9 | ||||
-rw-r--r-- | libpod/container_config.go | 5 | ||||
-rw-r--r-- | libpod/container_copy_linux.go | 13 | ||||
-rw-r--r-- | libpod/container_exec.go | 2 | ||||
-rw-r--r-- | libpod/container_internal_linux.go | 7 | ||||
-rw-r--r-- | libpod/define/info.go | 12 | ||||
-rw-r--r-- | libpod/define/pod_inspect.go | 1 | ||||
-rw-r--r-- | libpod/healthcheck_linux.go | 70 | ||||
-rw-r--r-- | libpod/info.go | 63 | ||||
-rw-r--r-- | libpod/networking_linux.go | 34 | ||||
-rw-r--r-- | libpod/plugin/volume_api.go | 7 | ||||
-rw-r--r-- | libpod/pod_api.go | 9 | ||||
-rw-r--r-- | libpod/runtime.go | 3 |
14 files changed, 130 insertions, 108 deletions
diff --git a/libpod/container.go b/libpod/container.go index 64b4453fb..04a4ae64a 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -1331,8 +1331,7 @@ func (c *Container) getNetworkStatus() map[string]types.StatusBlock { } c.state.NetworkStatus = result _ = c.save() - // TODO remove debug for final version - logrus.Debugf("converted old network result to new result %v", result) + return result } return nil diff --git a/libpod/container_api.go b/libpod/container_api.go index 930b3e17b..b064d3528 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -213,9 +213,8 @@ func (c *Container) Kill(signal uint) error { } } - // TODO: Is killing a paused container OK? switch c.state.State { - case define.ContainerStateRunning, define.ContainerStateStopping: + case define.ContainerStateRunning, define.ContainerStateStopping, define.ContainerStatePaused: // Note that killing containers in "stopping" state is okay. // In that state, the Podman is waiting for the runtime to // stop the container and if that is taking too long, a user @@ -468,7 +467,7 @@ func (c *Container) AddArtifact(name string, data []byte) error { return define.ErrCtrRemoved } - return ioutil.WriteFile(c.getArtifactPath(name), data, 0740) + return ioutil.WriteFile(c.getArtifactPath(name), data, 0o740) } // GetArtifact reads the specified artifact file from the container @@ -899,7 +898,7 @@ func (c *Container) ShouldRestart(ctx context.Context) bool { // CopyFromArchive copies the contents from the specified tarStream to path // *inside* the container. -func (c *Container) CopyFromArchive(ctx context.Context, containerPath string, chown bool, rename map[string]string, tarStream io.Reader) (func() error, error) { +func (c *Container) CopyFromArchive(_ context.Context, containerPath string, chown, noOverwriteDirNonDir bool, rename map[string]string, tarStream io.Reader) (func() error, error) { if !c.batched { c.lock.Lock() defer c.lock.Unlock() @@ -909,7 +908,7 @@ func (c *Container) CopyFromArchive(ctx context.Context, containerPath string, c } } - return c.copyFromArchive(containerPath, chown, rename, tarStream) + return c.copyFromArchive(containerPath, chown, noOverwriteDirNonDir, rename, tarStream) } // CopyToArchive copies the contents from the specified path *inside* the diff --git a/libpod/container_config.go b/libpod/container_config.go index 3e85ad4d5..30b84adcf 100644 --- a/libpod/container_config.go +++ b/libpod/container_config.go @@ -243,12 +243,12 @@ type ContainerNetworkConfig struct { // This cannot be set unless CreateNetNS is set. // If not set, the container will be dynamically assigned an IP by CNI. // Deprecated: Do no use this anymore, this is only for DB backwards compat. - StaticIP net.IP `json:"staticIP"` + StaticIP net.IP `json:"staticIP,omitempty"` // StaticMAC is a static MAC to request for the container. // This cannot be set unless CreateNetNS is set. // If not set, the container will be dynamically assigned a MAC by CNI. // Deprecated: Do no use this anymore, this is only for DB backwards compat. - StaticMAC types.HardwareAddr `json:"staticMAC"` + StaticMAC types.HardwareAddr `json:"staticMAC,omitempty"` // PortMappings are the ports forwarded to the container's network // namespace // These are not used unless CreateNetNS is true @@ -372,7 +372,6 @@ type ContainerMiscConfig struct { // restart the container. Used only if RestartPolicy is set to // "on-failure". RestartRetries uint `json:"restart_retries,omitempty"` - // TODO log options for log drivers // PostConfigureNetNS needed when a user namespace is created by an OCI runtime // if the network namespace is created before the user namespace it will be // owned by the wrong user namespace. diff --git a/libpod/container_copy_linux.go b/libpod/container_copy_linux.go index 7566fbb12..9528cd06b 100644 --- a/libpod/container_copy_linux.go +++ b/libpod/container_copy_linux.go @@ -23,7 +23,7 @@ import ( "golang.org/x/sys/unix" ) -func (c *Container) copyFromArchive(path string, chown bool, rename map[string]string, reader io.Reader) (func() error, error) { +func (c *Container) copyFromArchive(path string, chown, noOverwriteDirNonDir bool, rename map[string]string, reader io.Reader) (func() error, error) { var ( mountPoint string resolvedRoot string @@ -89,11 +89,12 @@ func (c *Container) copyFromArchive(path string, chown bool, rename map[string]s defer unmount() defer decompressed.Close() putOptions := buildahCopiah.PutOptions{ - UIDMap: c.config.IDMappings.UIDMap, - GIDMap: c.config.IDMappings.GIDMap, - ChownDirs: idPair, - ChownFiles: idPair, - Rename: rename, + UIDMap: c.config.IDMappings.UIDMap, + GIDMap: c.config.IDMappings.GIDMap, + ChownDirs: idPair, + ChownFiles: idPair, + NoOverwriteDirNonDir: noOverwriteDirNonDir, + Rename: rename, } return c.joinMountAndExec( diff --git a/libpod/container_exec.go b/libpod/container_exec.go index c05e7fd94..1e8fce4da 100644 --- a/libpod/container_exec.go +++ b/libpod/container_exec.go @@ -279,8 +279,6 @@ func (c *Container) ExecStart(sessionID string) error { // ExecStartAndAttach starts and attaches to an exec session in a container. // newSize resizes the tty to this size before the process is started, must be nil if the exec session has no tty -// TODO: Should we include detach keys in the signature to allow override? -// TODO: How do we handle AttachStdin/AttachStdout/AttachStderr? func (c *Container) ExecStartAndAttach(sessionID string, streams *define.AttachStreams, newSize *define.TerminalSize) error { if !c.batched { c.lock.Lock() diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 298eb1947..e19d75deb 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -1091,7 +1091,6 @@ func (c *Container) addNamespaceContainer(g *generate.Generator, ns LinuxNS, ctr g.AddProcessEnv("HOSTNAME", hostname) } - // TODO need unlocked version of this for use in pods nsPath, err := nsCtr.NamespacePath(ns) if err != nil { return err @@ -3230,10 +3229,8 @@ func (c *Container) fixVolumePermissions(v *ContainerNamedVolume) error { return err } - // TODO: For now, I've disabled chowning volumes owned by non-Podman - // drivers. This may be safe, but it's really going to be a case-by-case - // thing, I think - safest to leave disabled now and re-enable later if - // there is a demand. + // Volumes owned by a volume driver are not chowned - we don't want to + // mess with a mount not managed by us. if vol.state.NeedsChown && !vol.UsesVolumeDriver() { vol.state.NeedsChown = false diff --git a/libpod/define/info.go b/libpod/define/info.go index 911fa5c03..c716bec7b 100644 --- a/libpod/define/info.go +++ b/libpod/define/info.go @@ -14,7 +14,7 @@ type Info struct { Version Version `json:"version"` } -// HostInfo describes the libpod host +// SecurityInfo describes the libpod host type SecurityInfo struct { AppArmorEnabled bool `json:"apparmorEnabled"` DefaultCapabilities string `json:"capabilities"` @@ -64,8 +64,7 @@ type RemoteSocket struct { Exists bool `json:"exists,omitempty"` } -// SlirpInfo describes the slirp executable that -// is being being used. +// SlirpInfo describes the slirp executable that is being used type SlirpInfo struct { Executable string `json:"executable"` Package string `json:"package"` @@ -78,8 +77,7 @@ type IDMappings struct { UIDMap []idtools.IDMap `json:"uidmap"` } -// DistributionInfo describes the host distribution -// for libpod +// DistributionInfo describes the host distribution for libpod type DistributionInfo struct { Distribution string `json:"distribution"` Variant string `json:"variant,omitempty"` @@ -141,8 +139,8 @@ type Plugins struct { Volume []string `json:"volume"` Network []string `json:"network"` Log []string `json:"log"` - // FIXME what should we do with Authorization, docker seems to return nothing by default - // Authorization []string `json:"authorization"` + // Authorization is provided for compatibility, will always be nil as Podman has no daemon + Authorization []string `json:"authorization"` } type CPUUsage struct { diff --git a/libpod/define/pod_inspect.go b/libpod/define/pod_inspect.go index 219ffade2..c387856e5 100644 --- a/libpod/define/pod_inspect.go +++ b/libpod/define/pod_inspect.go @@ -82,6 +82,7 @@ type InspectPodInfraConfig struct { HostNetwork bool // StaticIP is a static IPv4 that will be assigned to the infra // container and then used by the pod. + // swagger:strfmt ipv4 StaticIP net.IP // StaticMAC is a static MAC address that will be assigned to the infra // container and then used by the pod. diff --git a/libpod/healthcheck_linux.go b/libpod/healthcheck_linux.go index 45b3a0e41..1e03db542 100644 --- a/libpod/healthcheck_linux.go +++ b/libpod/healthcheck_linux.go @@ -7,6 +7,7 @@ import ( "os/exec" "strings" + "github.com/containers/podman/v4/pkg/errorhandling" "github.com/containers/podman/v4/pkg/rootless" "github.com/containers/podman/v4/pkg/systemd" "github.com/pkg/errors" @@ -46,6 +47,17 @@ func (c *Container) createTimer() error { return nil } +// Wait for a message on the channel. Throw an error if the message is not "done". +func systemdOpSuccessful(c chan string) error { + msg := <-c + switch msg { + case "done": + return nil + default: + return fmt.Errorf("expected %q but received %q", "done", msg) + } +} + // startTimer starts a systemd timer for the healthchecks func (c *Container) startTimer() error { if c.disableHealthCheckSystemd() { @@ -56,8 +68,17 @@ func (c *Container) startTimer() error { return errors.Wrapf(err, "unable to get systemd connection to start healthchecks") } defer conn.Close() - _, err = conn.StartUnitContext(context.Background(), fmt.Sprintf("%s.service", c.ID()), "fail", nil) - return err + + startFile := fmt.Sprintf("%s.service", c.ID()) + startChan := make(chan string) + if _, err := conn.StartUnitContext(context.Background(), startFile, "fail", startChan); err != nil { + return err + } + if err := systemdOpSuccessful(startChan); err != nil { + return fmt.Errorf("starting systemd health-check timer %q: %w", startFile, err) + } + + return nil } // removeTransientFiles removes the systemd timer and unit files @@ -71,30 +92,37 @@ func (c *Container) removeTransientFiles(ctx context.Context) error { return errors.Wrapf(err, "unable to get systemd connection to remove healthchecks") } defer conn.Close() + + // Errors are returned at the very end. Let's make sure to stop and + // clean up as much as possible. + stopErrors := []error{} + + // Stop the timer before the service to make sure the timer does not + // fire after the service is stopped. + timerChan := make(chan string) timerFile := fmt.Sprintf("%s.timer", c.ID()) - serviceFile := fmt.Sprintf("%s.service", c.ID()) + if _, err := conn.StopUnitContext(ctx, timerFile, "fail", timerChan); err != nil { + if !strings.HasSuffix(err.Error(), ".timer not loaded.") { + stopErrors = append(stopErrors, fmt.Errorf("removing health-check timer %q: %w", timerFile, err)) + } + } else if err := systemdOpSuccessful(timerChan); err != nil { + stopErrors = append(stopErrors, fmt.Errorf("stopping systemd health-check timer %q: %w", timerFile, err)) + } - // If the service has failed (the healthcheck has failed), then - // the .service file is not removed on stopping the unit file. If - // we check the properties of the service, it will automatically - // reset the state. But checking the state takes msecs vs usecs to - // blindly call reset. + // Reset the service before stopping it to make sure it's being removed + // on stop. + serviceChan := make(chan string) + serviceFile := fmt.Sprintf("%s.service", c.ID()) if err := conn.ResetFailedUnitContext(ctx, serviceFile); err != nil { - logrus.Debugf("failed to reset unit file: %q", err) + logrus.Debugf("Failed to reset unit file: %q", err) } - - // We want to ignore errors where the timer unit and/or service unit has already - // been removed. The error return is generic so we have to check against the - // string in the error - if _, err = conn.StopUnitContext(ctx, serviceFile, "fail", nil); err != nil { + if _, err := conn.StopUnitContext(ctx, serviceFile, "fail", serviceChan); err != nil { if !strings.HasSuffix(err.Error(), ".service not loaded.") { - return errors.Wrapf(err, "unable to remove service file") - } - } - if _, err = conn.StopUnitContext(ctx, timerFile, "fail", nil); err != nil { - if strings.HasSuffix(err.Error(), ".timer not loaded.") { - return nil + stopErrors = append(stopErrors, fmt.Errorf("removing health-check service %q: %w", serviceFile, err)) } + } else if err := systemdOpSuccessful(serviceChan); err != nil { + stopErrors = append(stopErrors, fmt.Errorf("stopping systemd health-check service %q: %w", serviceFile, err)) } - return err + + return errorhandling.JoinErrors(stopErrors) } diff --git a/libpod/info.go b/libpod/info.go index bc49a6cc9..561d11524 100644 --- a/libpod/info.go +++ b/libpod/info.go @@ -199,50 +199,38 @@ func (r *Runtime) hostInfo() (*define.HostInfo, error) { info.OCIRuntime = ociruntimeInfo } - up, err := readUptime() + duration, err := procUptime() if err != nil { return nil, errors.Wrapf(err, "error reading up time") } - // Convert uptime in seconds to a human-readable format - upSeconds := up + "s" - upDuration, err := time.ParseDuration(upSeconds) - if err != nil { - return nil, errors.Wrapf(err, "error parsing system uptime") - } - - // TODO Isn't there a simple lib for this, something like humantime? - hoursFound := false - var timeBuffer bytes.Buffer - var hoursBuffer bytes.Buffer - for _, elem := range upDuration.String() { - timeBuffer.WriteRune(elem) - if elem == 'h' || elem == 'm' { - timeBuffer.WriteRune(' ') - if elem == 'h' { - hoursFound = true - } - } - if !hoursFound { - hoursBuffer.WriteRune(elem) - } + + uptime := struct { + hours float64 + minutes float64 + seconds float64 + }{ + hours: duration.Truncate(time.Hour).Hours(), + minutes: duration.Truncate(time.Minute).Minutes(), + seconds: duration.Truncate(time.Second).Seconds(), } - info.Uptime = timeBuffer.String() - if hoursFound { - hours, err := strconv.ParseFloat(hoursBuffer.String(), 64) - if err == nil { - days := hours / 24 - info.Uptime = fmt.Sprintf("%s (Approximately %.2f days)", info.Uptime, days) - } + // Could not find a humanize-formatter for time.Duration + var buffer bytes.Buffer + buffer.WriteString(fmt.Sprintf("%.0fh %.0fm %.2fs", + uptime.hours, + math.Mod(uptime.seconds, 3600)/60, + math.Mod(uptime.seconds, 60), + )) + if int64(uptime.hours) > 0 { + buffer.WriteString(fmt.Sprintf(" (Approximately %.2f days)", uptime.hours/24)) } + info.Uptime = buffer.String() return &info, nil } func (r *Runtime) getContainerStoreInfo() (define.ContainerStore, error) { - var ( - paused, running, stopped int - ) + var paused, running, stopped int cs := define.ContainerStore{} cons, err := r.GetAllContainers() if err != nil { @@ -353,16 +341,17 @@ func readKernelVersion() (string, error) { return string(f[2]), nil } -func readUptime() (string, error) { +func procUptime() (time.Duration, error) { + var zero time.Duration buf, err := ioutil.ReadFile("/proc/uptime") if err != nil { - return "", err + return zero, err } f := bytes.Fields(buf) if len(f) < 1 { - return "", fmt.Errorf("invalid uptime") + return zero, errors.New("unable to parse uptime from /proc/uptime") } - return string(f[0]), nil + return time.ParseDuration(string(f[0]) + "s") } // GetHostDistributionInfo returns a map containing the host's distribution and version diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 73e64530e..37fa9b5f5 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -930,6 +930,8 @@ func (r *Runtime) reloadContainerNetwork(ctr *Container) (map[string]types.Statu return r.configureNetNS(ctr, ctr.state.NetNS) } +// TODO (5.0): return the statistics per network interface +// This would allow better compat with docker. func getContainerNetIO(ctr *Container) (*netlink.LinkStatistics, error) { var netStats *netlink.LinkStatistics @@ -943,21 +945,39 @@ func getContainerNetIO(ctr *Container) (*netlink.LinkStatistics, error) { return nil, nil } - // FIXME get the interface from the container netstatus - dev := "eth0" netMode := ctr.config.NetMode + netStatus := ctr.getNetworkStatus() if otherCtr != nil { netMode = otherCtr.config.NetMode + netStatus = otherCtr.getNetworkStatus() } if netMode.IsSlirp4netns() { - dev = "tap0" + // create a fake status with correct interface name for the logic below + netStatus = map[string]types.StatusBlock{ + "slirp4netns": { + Interfaces: map[string]types.NetInterface{"tap0": {}}, + }, + } } err := ns.WithNetNSPath(netNSPath, func(_ ns.NetNS) error { - link, err := netlink.LinkByName(dev) - if err != nil { - return err + for _, status := range netStatus { + for dev := range status.Interfaces { + link, err := netlink.LinkByName(dev) + if err != nil { + return err + } + if netStats == nil { + netStats = link.Attrs().Statistics + continue + } + // Currently only Tx/RxBytes are used. + // In the future we should return all stats per interface so that + // api users have a better options. + stats := link.Attrs().Statistics + netStats.TxBytes += stats.TxBytes + netStats.RxBytes += stats.RxBytes + } } - netStats = link.Attrs().Statistics return nil }) return netStats, err diff --git a/libpod/plugin/volume_api.go b/libpod/plugin/volume_api.go index a6d66a034..2818e70c1 100644 --- a/libpod/plugin/volume_api.go +++ b/libpod/plugin/volume_api.go @@ -22,9 +22,6 @@ import ( var json = jsoniter.ConfigCompatibleWithStandardLibrary -// TODO: We should add syntax for specifying plugins to containers.conf, and -// support for loading based on that. - // Copied from docker/go-plugins-helpers/volume/api.go - not exported, so we // need to do this to get at them. // These are well-established paths that should not change unless the plugin API @@ -185,8 +182,7 @@ func (p *VolumePlugin) getURI() string { } // Verify the plugin is still available. -// TODO: Do we want to ping with an HTTP request? There's no ping endpoint so -// we'd need to hit Activate or Capabilities? +// Does not actually ping the API, just verifies that the socket still exists. func (p *VolumePlugin) verifyReachable() error { if _, err := os.Stat(p.SocketPath); err != nil { if os.IsNotExist(err) { @@ -307,7 +303,6 @@ func (p *VolumePlugin) ListVolumes() ([]*volume.Volume, error) { return nil, err } - // TODO: Can probably unify response reading under a helper volumeRespBytes, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, errors.Wrapf(err, "error reading response body from volume plugin %s", p.Name) diff --git a/libpod/pod_api.go b/libpod/pod_api.go index eede896a9..1c1e15984 100644 --- a/libpod/pod_api.go +++ b/libpod/pod_api.go @@ -152,8 +152,8 @@ func (p *Pod) stopWithTimeout(ctx context.Context, cleanup bool, timeout int) (m return nil, err } - // TODO: There may be cases where it makes sense to order stops based on - // dependencies. Should we bother with this? + // Stopping pods is not ordered by dependency. We haven't seen any case + // where this would actually matter. ctrErrChan := make(map[string]<-chan error) @@ -162,8 +162,9 @@ func (p *Pod) stopWithTimeout(ctx context.Context, cleanup bool, timeout int) (m c := ctr logrus.Debugf("Adding parallel job to stop container %s", c.ID()) retChan := parallel.Enqueue(ctx, func() error { - // TODO: Might be better to batch stop and cleanup - // together? + // Can't batch these without forcing Stop() to hold the + // lock for the full duration of the timeout. + // We probably don't want to do that. if timeout > -1 { if err := c.StopWithTimeout(uint(timeout)); err != nil { return err diff --git a/libpod/runtime.go b/libpod/runtime.go index 00fa2fe88..e268c2d17 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -412,7 +412,6 @@ func makeRuntime(runtime *Runtime) (retErr error) { return err } runtime.eventer = eventer - // TODO: events for libimage // Set up containers/image if runtime.imageContext == nil { @@ -517,8 +516,6 @@ func makeRuntime(runtime *Runtime) (retErr error) { } // Acquire the lock and hold it until we return // This ensures that no two processes will be in runtime.refresh at once - // TODO: we can't close the FD in this lock, so we should keep it around - // and use it to lock important operations aliveLock.Lock() doRefresh := false defer func() { |