diff options
Diffstat (limited to 'libpod')
-rw-r--r-- | libpod/container_api.go | 6 | ||||
-rw-r--r-- | libpod/container_config.go | 4 | ||||
-rw-r--r-- | libpod/container_copy_linux.go | 13 | ||||
-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 |
8 files changed, 120 insertions, 83 deletions
diff --git a/libpod/container_api.go b/libpod/container_api.go index 0fab36bdc..d87deb71a 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -446,7 +446,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 @@ -877,7 +877,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() @@ -887,7 +887,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 ae3bc5865..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 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/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 |