summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/api/handlers/compat/containers.go45
-rw-r--r--pkg/api/handlers/libpod/containers_stats.go22
-rw-r--r--pkg/api/server/register_containers.go9
-rw-r--r--pkg/bindings/containers/containers.go3
-rw-r--r--pkg/bindings/containers/types.go3
-rw-r--r--pkg/bindings/containers/types_stats_options.go16
-rw-r--r--pkg/domain/entities/containers.go2
-rw-r--r--pkg/domain/infra/abi/containers.go5
-rw-r--r--pkg/domain/infra/abi/parse/parse.go21
-rw-r--r--pkg/domain/infra/tunnel/containers.go2
-rw-r--r--pkg/rootlessport/rootlessport_linux.go79
11 files changed, 176 insertions, 31 deletions
diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go
index 2a0a0b725..95c09ff0e 100644
--- a/pkg/api/handlers/compat/containers.go
+++ b/pkg/api/handlers/compat/containers.go
@@ -403,22 +403,24 @@ func LibpodToContainerJSON(l *libpod.Container, sz bool) (*types.ContainerJSON,
state.Status = define.ContainerStateCreated.String()
}
- state.Health = &types.Health{
- Status: inspect.State.Healthcheck.Status,
- FailingStreak: inspect.State.Healthcheck.FailingStreak,
- }
-
- log := inspect.State.Healthcheck.Log
+ if l.HasHealthCheck() && state.Status != "created" {
+ state.Health = &types.Health{
+ Status: inspect.State.Healthcheck.Status,
+ FailingStreak: inspect.State.Healthcheck.FailingStreak,
+ }
- for _, item := range log {
- res := &types.HealthcheckResult{}
- s, _ := time.Parse(time.RFC3339Nano, item.Start)
- e, _ := time.Parse(time.RFC3339Nano, item.End)
- res.Start = s
- res.End = e
- res.ExitCode = item.ExitCode
- res.Output = item.Output
- state.Health.Log = append(state.Health.Log, res)
+ log := inspect.State.Healthcheck.Log
+
+ for _, item := range log {
+ res := &types.HealthcheckResult{}
+ s, _ := time.Parse(time.RFC3339Nano, item.Start)
+ e, _ := time.Parse(time.RFC3339Nano, item.End)
+ res.Start = s
+ res.End = e
+ res.ExitCode = item.ExitCode
+ res.Output = item.Output
+ state.Health.Log = append(state.Health.Log, res)
+ }
}
formatCapabilities(inspect.HostConfig.CapDrop)
@@ -495,6 +497,17 @@ func LibpodToContainerJSON(l *libpod.Container, sz bool) (*types.ContainerJSON,
exposedPorts[exposedPort] = struct{}{}
}
+ var healthcheck *container.HealthConfig
+ if inspect.Config.Healthcheck != nil {
+ healthcheck = &container.HealthConfig{
+ Test: inspect.Config.Healthcheck.Test,
+ Interval: inspect.Config.Healthcheck.Interval,
+ Timeout: inspect.Config.Healthcheck.Timeout,
+ StartPeriod: inspect.Config.Healthcheck.StartPeriod,
+ Retries: inspect.Config.Healthcheck.Retries,
+ }
+ }
+
config := container.Config{
Hostname: l.Hostname(),
Domainname: inspect.Config.DomainName,
@@ -508,7 +521,7 @@ func LibpodToContainerJSON(l *libpod.Container, sz bool) (*types.ContainerJSON,
StdinOnce: inspect.Config.StdinOnce,
Env: inspect.Config.Env,
Cmd: l.Command(),
- Healthcheck: nil,
+ Healthcheck: healthcheck,
ArgsEscaped: false,
Image: imageName,
Volumes: nil,
diff --git a/pkg/api/handlers/libpod/containers_stats.go b/pkg/api/handlers/libpod/containers_stats.go
index 75c404d4f..8a04884b0 100644
--- a/pkg/api/handlers/libpod/containers_stats.go
+++ b/pkg/api/handlers/libpod/containers_stats.go
@@ -3,28 +3,39 @@ package libpod
import (
"encoding/json"
"net/http"
- "time"
"github.com/containers/podman/v3/libpod"
"github.com/containers/podman/v3/pkg/api/handlers/utils"
+ "github.com/containers/podman/v3/pkg/cgroups"
"github.com/containers/podman/v3/pkg/domain/entities"
"github.com/containers/podman/v3/pkg/domain/infra/abi"
+ "github.com/containers/podman/v3/pkg/rootless"
"github.com/gorilla/schema"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
-const DefaultStatsPeriod = 5 * time.Second
-
func StatsContainer(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
+ // Check if service is running rootless (cheap check)
+ if rootless.IsRootless() {
+ // if so, then verify cgroup v2 available (more expensive check)
+ if isV2, _ := cgroups.IsCgroup2UnifiedMode(); !isV2 {
+ msg := "Container stats resource only available for cgroup v2"
+ utils.Error(w, msg, http.StatusConflict, errors.New(msg))
+ return
+ }
+ }
+
query := struct {
Containers []string `schema:"containers"`
Stream bool `schema:"stream"`
+ Interval int `schema:"interval"`
}{
- Stream: true,
+ Stream: true,
+ Interval: 5,
}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "failed to parse parameters for %s", r.URL.String()))
@@ -36,7 +47,8 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
containerEngine := abi.ContainerEngine{Libpod: runtime}
statsOptions := entities.ContainerStatsOptions{
- Stream: query.Stream,
+ Stream: query.Stream,
+ Interval: query.Interval,
}
// Stats will stop if the connection is closed.
diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go
index 50e059ecc..0ec4f95d9 100644
--- a/pkg/api/server/register_containers.go
+++ b/pkg/api/server/register_containers.go
@@ -1085,6 +1085,8 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// description: no error
// 404:
// $ref: "#/responses/NoSuchContainer"
+ // 409:
+ // $ref: "#/responses/ConflictError"
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/libpod/containers/{name}/stats"), s.APIHandler(compat.StatsContainer)).Methods(http.MethodGet)
@@ -1106,6 +1108,11 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// type: boolean
// default: true
// description: Stream the output
+ // - in: query
+ // name: interval
+ // type: integer
+ // default: 5
+ // description: Time in seconds between stats reports
// produces:
// - application/json
// responses:
@@ -1113,6 +1120,8 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// description: no error
// 404:
// $ref: "#/responses/NoSuchContainer"
+ // 409:
+ // $ref: "#/responses/ConflictError"
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/libpod/containers/stats"), s.APIHandler(libpod.StatsContainer)).Methods(http.MethodGet)
diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go
index 86304f392..bc7b0c8c9 100644
--- a/pkg/bindings/containers/containers.go
+++ b/pkg/bindings/containers/containers.go
@@ -223,6 +223,9 @@ func Stats(ctx context.Context, containers []string, options *StatsOptions) (cha
if err != nil {
return nil, err
}
+ if !response.IsSuccess() {
+ return nil, response.Process(nil)
+ }
statsChan := make(chan entities.ContainerStatsReport)
diff --git a/pkg/bindings/containers/types.go b/pkg/bindings/containers/types.go
index cf088441f..3e9a384de 100644
--- a/pkg/bindings/containers/types.go
+++ b/pkg/bindings/containers/types.go
@@ -166,7 +166,8 @@ type StartOptions struct {
//go:generate go run ../generator/generator.go StatsOptions
// StatsOptions are optional options for getting stats on containers
type StatsOptions struct {
- Stream *bool
+ Stream *bool
+ Interval *int
}
//go:generate go run ../generator/generator.go TopOptions
diff --git a/pkg/bindings/containers/types_stats_options.go b/pkg/bindings/containers/types_stats_options.go
index 8f6a03301..604004eb6 100644
--- a/pkg/bindings/containers/types_stats_options.go
+++ b/pkg/bindings/containers/types_stats_options.go
@@ -35,3 +35,19 @@ func (o *StatsOptions) GetStream() bool {
}
return *o.Stream
}
+
+// WithInterval
+func (o *StatsOptions) WithInterval(value int) *StatsOptions {
+ v := &value
+ o.Interval = v
+ return o
+}
+
+// GetInterval
+func (o *StatsOptions) GetInterval() int {
+ var interval int
+ if o.Interval == nil {
+ return interval
+ }
+ return *o.Interval
+}
diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go
index 564921c52..d2a7505a8 100644
--- a/pkg/domain/entities/containers.go
+++ b/pkg/domain/entities/containers.go
@@ -440,6 +440,8 @@ type ContainerStatsOptions struct {
Latest bool
// Stream stats.
Stream bool
+ // Interval in seconds
+ Interval int
}
// ContainerStatsReport is used for streaming container stats.
diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go
index 2003879b8..ddd768328 100644
--- a/pkg/domain/infra/abi/containers.go
+++ b/pkg/domain/infra/abi/containers.go
@@ -1283,6 +1283,9 @@ func (ic *ContainerEngine) Shutdown(_ context.Context) {
}
func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []string, options entities.ContainerStatsOptions) (statsChan chan entities.ContainerStatsReport, err error) {
+ if options.Interval < 1 {
+ return nil, errors.New("Invalid interval, must be a positive number greater zero")
+ }
statsChan = make(chan entities.ContainerStatsReport, 1)
containerFunc := ic.Libpod.GetRunningContainers
@@ -1363,7 +1366,7 @@ func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []stri
return
}
- time.Sleep(time.Second)
+ time.Sleep(time.Second * time.Duration(options.Interval))
goto stream
}()
diff --git a/pkg/domain/infra/abi/parse/parse.go b/pkg/domain/infra/abi/parse/parse.go
index 56c747711..5a75e1216 100644
--- a/pkg/domain/infra/abi/parse/parse.go
+++ b/pkg/domain/infra/abi/parse/parse.go
@@ -6,12 +6,13 @@ import (
"github.com/containers/podman/v3/libpod"
"github.com/containers/podman/v3/libpod/define"
+ units "github.com/docker/go-units"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// Handle volume options from CLI.
-// Parse "o" option to find UID, GID.
+// Parse "o" option to find UID, GID, Size.
func VolumeOptions(opts map[string]string) ([]libpod.VolumeCreateOption, error) {
libpodOptions := []libpod.VolumeCreateOption{}
volumeOptions := make(map[string]string)
@@ -28,6 +29,24 @@ func VolumeOptions(opts map[string]string) ([]libpod.VolumeCreateOption, error)
// "opt=value"
splitO := strings.SplitN(o, "=", 2)
switch strings.ToLower(splitO[0]) {
+ case "size":
+ size, err := units.FromHumanSize(splitO[1])
+ if err != nil {
+ return nil, errors.Wrapf(err, "cannot convert size %s to integer", splitO[1])
+ }
+ libpodOptions = append(libpodOptions, libpod.WithVolumeSize(uint64(size)))
+ finalVal = append(finalVal, o)
+ // set option "SIZE": "$size"
+ volumeOptions["SIZE"] = splitO[1]
+ case "inodes":
+ inodes, err := strconv.ParseUint(splitO[1], 10, 64)
+ if err != nil {
+ return nil, errors.Wrapf(err, "cannot convert inodes %s to integer", splitO[1])
+ }
+ libpodOptions = append(libpodOptions, libpod.WithVolumeInodes(uint64(inodes)))
+ finalVal = append(finalVal, o)
+ // set option "INODES": "$size"
+ volumeOptions["INODES"] = splitO[1]
case "uid":
if len(splitO) != 2 {
return nil, errors.Wrapf(define.ErrInvalidArg, "uid option must provide a UID")
diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go
index 58f9c5fb0..3c2802165 100644
--- a/pkg/domain/infra/tunnel/containers.go
+++ b/pkg/domain/infra/tunnel/containers.go
@@ -873,7 +873,7 @@ func (ic *ContainerEngine) ContainerStats(ctx context.Context, namesOrIds []stri
if options.Latest {
return nil, errors.New("latest is not supported for the remote client")
}
- return containers.Stats(ic.ClientCtx, namesOrIds, new(containers.StatsOptions).WithStream(options.Stream))
+ return containers.Stats(ic.ClientCtx, namesOrIds, new(containers.StatsOptions).WithStream(options.Stream).WithInterval(options.Interval))
}
// ShouldRestart reports back whether the container will restart
diff --git a/pkg/rootlessport/rootlessport_linux.go b/pkg/rootlessport/rootlessport_linux.go
index 7cb54a7c3..ede216bfe 100644
--- a/pkg/rootlessport/rootlessport_linux.go
+++ b/pkg/rootlessport/rootlessport_linux.go
@@ -17,9 +17,11 @@ import (
"fmt"
"io"
"io/ioutil"
+ "net"
"os"
"os/exec"
"os/signal"
+ "path/filepath"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containers/storage/pkg/reexec"
@@ -43,12 +45,14 @@ const (
// Config needs to be provided to the process via stdin as a JSON string.
// stdin needs to be closed after the message has been written.
type Config struct {
- Mappings []ocicni.PortMapping
- NetNSPath string
- ExitFD int
- ReadyFD int
- TmpDir string
- ChildIP string
+ Mappings []ocicni.PortMapping
+ NetNSPath string
+ ExitFD int
+ ReadyFD int
+ TmpDir string
+ ChildIP string
+ ContainerID string
+ RootlessCNI bool
}
func init() {
@@ -126,6 +130,12 @@ func parent() error {
}
}()
+ socketDir := filepath.Join(cfg.TmpDir, "rp")
+ err = os.MkdirAll(socketDir, 0700)
+ if err != nil {
+ return err
+ }
+
// create the parent driver
stateDir, err := ioutil.TempDir(cfg.TmpDir, "rootlessport")
if err != nil {
@@ -231,6 +241,16 @@ outer:
return err
}
+ // we only need to have a socket to reload ports when we run under rootless cni
+ if cfg.RootlessCNI {
+ socket, err := net.Listen("unix", filepath.Join(socketDir, cfg.ContainerID))
+ if err != nil {
+ return err
+ }
+ defer socket.Close()
+ go serve(socket, driver)
+ }
+
// write and close ReadyFD (convention is same as slirp4netns --ready-fd)
logrus.Info("ready")
if _, err := readyW.Write([]byte("1")); err != nil {
@@ -248,6 +268,53 @@ outer:
return nil
}
+func serve(listener net.Listener, pm rkport.Manager) {
+ for {
+ conn, err := listener.Accept()
+ if err != nil {
+ // we cannot log this error, stderr is already closed
+ continue
+ }
+ ctx := context.TODO()
+ err = handler(ctx, conn, pm)
+ if err != nil {
+ conn.Write([]byte(err.Error()))
+ } else {
+ conn.Write([]byte("OK"))
+ }
+ conn.Close()
+ }
+}
+
+func handler(ctx context.Context, conn io.Reader, pm rkport.Manager) error {
+ var childIP string
+ dec := json.NewDecoder(conn)
+ err := dec.Decode(&childIP)
+ if err != nil {
+ return errors.Wrap(err, "rootless port failed to decode ports")
+ }
+ portStatus, err := pm.ListPorts(ctx)
+ if err != nil {
+ return errors.Wrap(err, "rootless port failed to list ports")
+ }
+ for _, status := range portStatus {
+ err = pm.RemovePort(ctx, status.ID)
+ if err != nil {
+ return errors.Wrap(err, "rootless port failed to remove port")
+ }
+ }
+ // add the ports with the new child IP
+ for _, status := range portStatus {
+ // set the new child IP
+ status.Spec.ChildIP = childIP
+ _, err = pm.AddPort(ctx, status.Spec)
+ if err != nil {
+ return errors.Wrap(err, "rootless port failed to add port")
+ }
+ }
+ return nil
+}
+
func exposePorts(pm rkport.Manager, portMappings []ocicni.PortMapping, childIP string) error {
ctx := context.TODO()
for _, i := range portMappings {