summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xcontrib/build_rpm.sh2
-rwxr-xr-xhack/get_ci_vm.sh2
-rw-r--r--libpod/image/filters.go9
-rw-r--r--libpod/logs/log.go88
-rw-r--r--libpod/logs/reversereader/reversereader.go66
-rw-r--r--pkg/adapter/containers.go6
-rw-r--r--pkg/api/handlers/generic/containers_stats.go16
-rw-r--r--pkg/api/handlers/generic/types.go55
-rw-r--r--pkg/api/handlers/types.go8
-rw-r--r--pkg/api/server/register_containers.go42
-rw-r--r--pkg/api/server/register_images.go1
11 files changed, 233 insertions, 62 deletions
diff --git a/contrib/build_rpm.sh b/contrib/build_rpm.sh
index b162a9c88..e6acbdb15 100755
--- a/contrib/build_rpm.sh
+++ b/contrib/build_rpm.sh
@@ -48,7 +48,7 @@ fi
# btrfs-progs-devel is not available in CentOS/RHEL-8
if ! (grep -i 'Red Hat\|CentOS' /etc/redhat-release | grep " 8" ); then
- PKGS+=(golang-github-cpuguy83-go-md2man \
+ PKGS+=(golang-github-cpuguy83-md2man \
btrfs-progs-devel \
)
fi
diff --git a/hack/get_ci_vm.sh b/hack/get_ci_vm.sh
index 22f902e2d..768137213 100755
--- a/hack/get_ci_vm.sh
+++ b/hack/get_ci_vm.sh
@@ -97,7 +97,7 @@ keys=[k for k in env if "ENCRYPTED" not in str(env[k])]
for k,v in env.items():
v=str(v)
if "ENCRYPTED" not in v:
- print "{0}=\"{1}\"".format(k, v),
+ print("{0}=\"{1}\"".format(k, v)),
'
}
diff --git a/libpod/image/filters.go b/libpod/image/filters.go
index 7c7394930..c54ca6333 100644
--- a/libpod/image/filters.go
+++ b/libpod/image/filters.go
@@ -102,6 +102,13 @@ func ReferenceFilter(ctx context.Context, referenceFilter string) ResultFilter {
}
}
+// IdFilter allows you to filter by image Id
+func IdFilter(idFilter string) ResultFilter {
+ return func(i *Image) bool {
+ return i.ID() == idFilter
+ }
+}
+
// OutputImageFilter allows you to filter by an a specific image name
func OutputImageFilter(userImage *Image) ResultFilter {
return func(i *Image) bool {
@@ -165,6 +172,8 @@ func (ir *Runtime) createFilterFuncs(filters []string, img *Image) ([]ResultFilt
case "reference":
referenceFilter := strings.Join(splitFilter[1:], "=")
filterFuncs = append(filterFuncs, ReferenceFilter(ctx, referenceFilter))
+ case "id":
+ filterFuncs = append(filterFuncs, IdFilter(splitFilter[1]))
default:
return nil, errors.Errorf("invalid filter %s ", splitFilter[0])
}
diff --git a/libpod/logs/log.go b/libpod/logs/log.go
index 9a7bcb5be..bd918abae 100644
--- a/libpod/logs/log.go
+++ b/libpod/logs/log.go
@@ -2,13 +2,16 @@ package logs
import (
"fmt"
- "io/ioutil"
+ "io"
+ "os"
"strings"
"sync"
"time"
+ "github.com/containers/libpod/libpod/logs/reversereader"
"github.com/hpcloud/tail"
"github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
)
const (
@@ -74,43 +77,84 @@ func GetLogFile(path string, options *LogOptions) (*tail.Tail, []*LogLine, error
func getTailLog(path string, tail int) ([]*LogLine, error) {
var (
- tailLog []*LogLine
- nlls []*LogLine
- tailCounter int
- partial string
+ nlls []*LogLine
+ nllCounter int
+ leftover string
+ partial string
+ tailLog []*LogLine
)
- content, err := ioutil.ReadFile(path)
+ f, err := os.Open(path)
if err != nil {
return nil, err
}
- splitContent := strings.Split(string(content), "\n")
- // We read the content in reverse and add each nll until we have the same
- // number of F type messages as the desired tail
- for i := len(splitContent) - 1; i >= 0; i-- {
- if len(splitContent[i]) == 0 {
- continue
- }
- nll, err := NewLogLine(splitContent[i])
- if err != nil {
- return nil, err
+ rr, err := reversereader.NewReverseReader(f)
+ if err != nil {
+ return nil, err
+ }
+
+ inputs := make(chan []string)
+ go func() {
+ for {
+ s, err := rr.Read()
+ if err != nil {
+ if errors.Cause(err) == io.EOF {
+ inputs <- []string{leftover}
+ close(inputs)
+ break
+ }
+ logrus.Error(err)
+ close(inputs)
+ }
+ line := strings.Split(s+leftover, "\n")
+ if len(line) > 1 {
+ inputs <- line[1:]
+ }
+ leftover = line[0]
}
- nlls = append(nlls, nll)
- if !nll.Partial() {
- tailCounter++
+ }()
+
+ for i := range inputs {
+ // the incoming array is FIFO; we want FIFO so
+ // reverse the slice read order
+ for j := len(i) - 1; j >= 0; j-- {
+ // lines that are "" are junk
+ if len(i[j]) < 1 {
+ continue
+ }
+ // read the content in reverse and add each nll until we have the same
+ // number of F type messages as the desired tail
+ nll, err := NewLogLine(i[j])
+ if err != nil {
+ return nil, err
+ }
+ nlls = append(nlls, nll)
+ if !nll.Partial() {
+ nllCounter++
+ }
}
- if tailCounter == tail {
+ // if we have enough loglines, we can hangup
+ if nllCounter >= tail {
+ if err := f.Close(); err != nil {
+ logrus.Error(err)
+ }
break
}
}
- // Now we iterate the results and assemble partial messages to become full messages
+
+ // re-assemble the log lines and trim (if needed) to the
+ // tail length
for _, nll := range nlls {
if nll.Partial() {
partial += nll.Msg
} else {
nll.Msg += partial
- tailLog = append(tailLog, nll)
+ // prepend because we need to reverse the order again to FIFO
+ tailLog = append([]*LogLine{nll}, tailLog...)
partial = ""
}
+ if len(tailLog) == tail {
+ break
+ }
}
return tailLog, nil
}
diff --git a/libpod/logs/reversereader/reversereader.go b/libpod/logs/reversereader/reversereader.go
new file mode 100644
index 000000000..72d9ad975
--- /dev/null
+++ b/libpod/logs/reversereader/reversereader.go
@@ -0,0 +1,66 @@
+package reversereader
+
+import (
+ "io"
+ "os"
+
+ "github.com/pkg/errors"
+)
+
+// ReverseReader structure for reading a file backwards
+type ReverseReader struct {
+ reader *os.File
+ offset int64
+ readSize int64
+}
+
+// NewReverseReader returns a reader that reads from the end of a file
+// rather than the beginning. It sets the readsize to pagesize and determines
+// the first offset using using modulus.
+func NewReverseReader(reader *os.File) (*ReverseReader, error) {
+ // pagesize should be safe for memory use and file reads should be on page
+ // boundaries as well
+ pageSize := int64(os.Getpagesize())
+ stat, err := reader.Stat()
+ if err != nil {
+ return nil, err
+ }
+ // figure out the last page boundary
+ remainder := stat.Size() % pageSize
+ end, err := reader.Seek(0, 2)
+ if err != nil {
+ return nil, err
+ }
+ // set offset (starting position) to the last page boundary or
+ // zero if fits in one page
+ startOffset := end - remainder
+ if startOffset < 0 {
+ startOffset = 0
+ }
+ rr := ReverseReader{
+ reader: reader,
+ offset: startOffset,
+ readSize: pageSize,
+ }
+ return &rr, nil
+}
+
+// ReverseReader reads from a given offset to the previous offset and
+// then sets the newoff set one pagesize less than the previous read.
+func (r *ReverseReader) Read() (string, error) {
+ if r.offset < 0 {
+ return "", errors.Wrap(io.EOF, "at beginning of file")
+ }
+ // Read from given offset
+ b := make([]byte, r.readSize)
+ n, err := r.reader.ReadAt(b, r.offset)
+ if err != nil && errors.Cause(err) != io.EOF {
+ return "", err
+ }
+ if int64(n) < r.readSize {
+ b = b[0:n]
+ }
+ // Set to the next page boundary
+ r.offset = -r.readSize
+ return string(b), nil
+}
diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go
index 170b2e24e..ab4255f89 100644
--- a/pkg/adapter/containers.go
+++ b/pkg/adapter/containers.go
@@ -338,7 +338,11 @@ func (r *LocalRuntime) Log(c *cliconfig.LogsValues, options *logs.LogOptions) er
if tailLen < 0 {
tailLen = 0
}
- logChannel := make(chan *logs.LogLine, tailLen*len(c.InputArgs)+1)
+ numContainers := len(c.InputArgs)
+ if numContainers == 0 {
+ numContainers = 1
+ }
+ logChannel := make(chan *logs.LogLine, tailLen*numContainers+1)
containers, err := shortcuts.GetContainersByContext(false, c.Latest, c.InputArgs, r.Runtime)
if err != nil {
return err
diff --git a/pkg/api/handlers/generic/containers_stats.go b/pkg/api/handlers/generic/containers_stats.go
index 19e2cc882..977979741 100644
--- a/pkg/api/handlers/generic/containers_stats.go
+++ b/pkg/api/handlers/generic/containers_stats.go
@@ -7,7 +7,6 @@ import (
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
- "github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/libpod/pkg/cgroups"
docker "github.com/docker/docker/api/types"
@@ -58,17 +57,18 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
}
var preRead time.Time
- var preCPUStats docker.CPUStats
+ var preCPUStats CPUStats
if query.Stream {
preRead = time.Now()
systemUsage, _ := cgroups.GetSystemCPUUsage()
- preCPUStats = docker.CPUStats{
+ preCPUStats = CPUStats{
CPUUsage: docker.CPUUsage{
TotalUsage: stats.CPUNano,
PercpuUsage: stats.PerCPU,
UsageInKernelmode: stats.CPUSystemNano,
UsageInUsermode: stats.CPUNano - stats.CPUSystemNano,
},
+ CPU: stats.CPU,
SystemUsage: systemUsage,
OnlineCPUs: 0,
ThrottlingData: docker.ThrottlingData{},
@@ -124,9 +124,8 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
}
systemUsage, _ := cgroups.GetSystemCPUUsage()
-
- s := handlers.Stats{StatsJSON: docker.StatsJSON{
- Stats: docker.Stats{
+ s := StatsJSON{
+ Stats: Stats{
Read: time.Now(),
PreRead: preRead,
PidsStats: docker.PidsStats{
@@ -143,13 +142,14 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
IoTimeRecursive: nil,
SectorsRecursive: nil,
},
- CPUStats: docker.CPUStats{
+ CPUStats: CPUStats{
CPUUsage: docker.CPUUsage{
TotalUsage: cgroupStat.CPU.Usage.Total,
PercpuUsage: cgroupStat.CPU.Usage.PerCPU,
UsageInKernelmode: cgroupStat.CPU.Usage.Kernel,
UsageInUsermode: cgroupStat.CPU.Usage.Total - cgroupStat.CPU.Usage.Kernel,
},
+ CPU: stats.CPU,
SystemUsage: systemUsage,
OnlineCPUs: uint32(len(cgroupStat.CPU.Usage.PerCPU)),
ThrottlingData: docker.ThrottlingData{
@@ -173,7 +173,7 @@ func StatsContainer(w http.ResponseWriter, r *http.Request) {
Name: stats.Name,
ID: stats.ContainerID,
Networks: net,
- }}
+ }
utils.WriteJSON(w, http.StatusOK, s)
if flusher, ok := w.(http.Flusher); ok {
diff --git a/pkg/api/handlers/generic/types.go b/pkg/api/handlers/generic/types.go
new file mode 100644
index 000000000..f068ac011
--- /dev/null
+++ b/pkg/api/handlers/generic/types.go
@@ -0,0 +1,55 @@
+package generic
+
+import (
+ "time"
+
+ docker "github.com/docker/docker/api/types"
+)
+
+// CPUStats aggregates and wraps all CPU related info of container
+type CPUStats struct {
+ // CPU Usage. Linux and Windows.
+ CPUUsage docker.CPUUsage `json:"cpu_usage"`
+
+ // System Usage. Linux only.
+ SystemUsage uint64 `json:"system_cpu_usage,omitempty"`
+
+ // Online CPUs. Linux only.
+ OnlineCPUs uint32 `json:"online_cpus,omitempty"`
+
+ // Usage of CPU in %. Linux only.
+ CPU float64 `json:"cpu"`
+
+ // Throttling Data. Linux only.
+ ThrottlingData docker.ThrottlingData `json:"throttling_data,omitempty"`
+}
+
+// Stats is Ultimate struct aggregating all types of stats of one container
+type Stats struct {
+ // Common stats
+ Read time.Time `json:"read"`
+ PreRead time.Time `json:"preread"`
+
+ // Linux specific stats, not populated on Windows.
+ PidsStats docker.PidsStats `json:"pids_stats,omitempty"`
+ BlkioStats docker.BlkioStats `json:"blkio_stats,omitempty"`
+
+ // Windows specific stats, not populated on Linux.
+ NumProcs uint32 `json:"num_procs"`
+ StorageStats docker.StorageStats `json:"storage_stats,omitempty"`
+
+ // Shared stats
+ CPUStats CPUStats `json:"cpu_stats,omitempty"`
+ PreCPUStats CPUStats `json:"precpu_stats,omitempty"` // "Pre"="Previous"
+ MemoryStats docker.MemoryStats `json:"memory_stats,omitempty"`
+}
+
+type StatsJSON struct {
+ Stats
+
+ Name string `json:"name,omitempty"`
+ ID string `json:"id,omitempty"`
+
+ // Networks request version >=1.21
+ Networks map[string]docker.NetworkStats `json:"networks,omitempty"`
+}
diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go
index 6268028f5..c72b0f817 100644
--- a/pkg/api/handlers/types.go
+++ b/pkg/api/handlers/types.go
@@ -78,10 +78,6 @@ type Container struct {
docker.ContainerCreateConfig
}
-type ContainerStats struct {
- docker.ContainerStats
-}
-
type Version struct {
docker.Version
}
@@ -143,10 +139,6 @@ type IDResponse struct {
ID string `json:"id"`
}
-type Stats struct {
- docker.StatsJSON
-}
-
type ContainerTopOKBody struct {
dockerContainer.ContainerTopOKBody
}
diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go
index 6007a2d00..6a2ba4b1e 100644
--- a/pkg/api/server/register_containers.go
+++ b/pkg/api/server/register_containers.go
@@ -550,7 +550,7 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
libpod endpoints
*/
- // swagger:operation POST /containers/create libpod libpodContainerCreate
+ // swagger:operation POST /libpod/containers/create libpod libpodCreateContainer
// ---
// summary: Create a container
// tags:
@@ -615,21 +615,21 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// name: filters
// type: string
// description: |
- // Returns a list of containers.
- // - ancestor=(<image-name>[:<tag>], <image id>, or <image@digest>)
- // - before=(<container id> or <container name>)
- // - expose=(<port>[/<proto>]|<startport-endport>/[<proto>])
- // - exited=<int> containers with exit code of <int>
- // - health=(starting|healthy|unhealthy|none)
- // - id=<ID> a container's ID
- // - is-task=(true|false)
- // - label=key or label="key=value" of a container label
- // - name=<name> a container's name
- // - network=(<network id> or <network name>)
- // - publish=(<port>[/<proto>]|<startport-endport>/[<proto>])
- // - since=(<container id> or <container name>)
- // - status=(created|restarting|running|removing|paused|exited|dead)
- // - volume=(<volume name> or <mount point destination>)
+ // A JSON encoded value of the filters (a `map[string][]string`) to process on the containers list. Available filters:
+ // - `ancestor`=(`<image-name>[:<tag>]`, `<image id>`, or `<image@digest>`)
+ // - `before`=(`<container id>` or `<container name>`)
+ // - `expose`=(`<port>[/<proto>]` or `<startport-endport>/[<proto>]`)
+ // - `exited=<int>` containers with exit code of `<int>`
+ // - `health`=(`starting`, `healthy`, `unhealthy` or `none`)
+ // - `id=<ID>` a container's ID
+ // - `is-task`=(`true` or `false`)
+ // - `label`=(`key` or `"key=value"`) of an container label
+ // - `name=<name>` a container's name
+ // - `network`=(`<network id>` or `<network name>`)
+ // - `publish`=(`<port>[/<proto>]` or `<startport-endport>/[<proto>]`)
+ // - `since`=(`<container id>` or `<container name>`)
+ // - `status`=(`created`, `restarting`, `running`, `removing`, `paused`, `exited` or `dead`)
+ // - `volume`=(`<volume name>` or `<mount point destination>`)
// produces:
// - application/json
// responses:
@@ -662,7 +662,7 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/libpod/containers/prune"), s.APIHandler(handlers.PruneContainers)).Methods(http.MethodPost)
- // swagger:operation GET /libpod/containers/showmounted libpod showMountedContainers
+ // swagger:operation GET /libpod/containers/showmounted libpod libpodShowMountedContainers
// ---
// tags:
// - containers
@@ -769,7 +769,7 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/libpod/containers/{name}/kill"), s.APIHandler(libpod.KillContainer)).Methods(http.MethodPost)
- // swagger:operation POST /libpod/containers/{name}/mount libpod mountContainer
+ // swagger:operation POST /libpod/containers/{name}/mount libpod libpodMountContainer
// ---
// tags:
// - containers
@@ -1047,7 +1047,7 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/libpod/containers/{name}/wait"), s.APIHandler(libpod.WaitContainer)).Methods(http.MethodPost)
- // swagger:operation GET /libpod/containers/{name}/exists libpod containerExists
+ // swagger:operation GET /libpod/containers/{name}/exists libpod libpodContainerExists
// ---
// tags:
// - containers
@@ -1096,7 +1096,7 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/libpod/containers/{name}/stop"), s.APIHandler(handlers.StopContainer)).Methods(http.MethodPost)
- // swagger:operation POST /libpod/containers/{name}/attach libpod libpodAttach
+ // swagger:operation POST /libpod/containers/{name}/attach libpod libpodAttachContainer
// ---
// tags:
// - containers
@@ -1151,7 +1151,7 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/libpod/containers/{name}/attach"), s.APIHandler(handlers.AttachContainer)).Methods(http.MethodPost)
- // swagger:operation POST /libpod/containers/{name}/resize libpod libpodResize
+ // swagger:operation POST /libpod/containers/{name}/resize libpod libpodResizeContainer
// ---
// tags:
// - containers
diff --git a/pkg/api/server/register_images.go b/pkg/api/server/register_images.go
index 4c8f05385..db04ecdc9 100644
--- a/pkg/api/server/register_images.go
+++ b/pkg/api/server/register_images.go
@@ -648,6 +648,7 @@ func (s *APIServer) registerImagesHandlers(r *mux.Router) error {
// - `dangling=true`
// - `label=key` or `label="key=value"` of an image label
// - `reference`=(`<image-name>[:<tag>]`)
+ // - `id`=(`<image-id>`)
// - `since`=(`<image-name>[:<tag>]`, `<image id>` or `<image@digest>`)
// type: string
// produces: