Unverified Commit 184f6973 authored by Darren Shepherd's avatar Darren Shepherd Committed by GitHub

Merge pull request #314 from ibuildthecloud/k8s-14

k8s 1.14
parents 9e801774 a11ac8cc

Too many changes to show.

To preserve performance only 1000 of 1000+ files are displayed.

...@@ -20,6 +20,7 @@ import ( ...@@ -20,6 +20,7 @@ import (
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"github.com/urfave/cli" "github.com/urfave/cli"
"k8s.io/apimachinery/pkg/util/net" "k8s.io/apimachinery/pkg/util/net"
"k8s.io/kubernetes/pkg/volume"
_ "github.com/mattn/go-sqlite3" // ensure we have sqlite _ "github.com/mattn/go-sqlite3" // ensure we have sqlite
) )
...@@ -70,6 +71,9 @@ func run(app *cli.Context, cfg *cmds.Server) error { ...@@ -70,6 +71,9 @@ func run(app *cli.Context, cfg *cmds.Server) error {
return fmt.Errorf("must run as root unless --disable-agent is specified") return fmt.Errorf("must run as root unless --disable-agent is specified")
} }
// If running agent in server, set this so that CSI initializes properly
volume.WaitForValidHost = !cfg.DisableAgent
serverConfig := server.Config{} serverConfig := server.Config{}
serverConfig.ControlConfig.ClusterSecret = cfg.ClusterSecret serverConfig.ControlConfig.ClusterSecret = cfg.ClusterSecret
serverConfig.ControlConfig.DataDir = cfg.DataDir serverConfig.ControlConfig.DataDir = cfg.DataDir
......
...@@ -12,7 +12,7 @@ import ( ...@@ -12,7 +12,7 @@ import (
"github.com/rancher/k3s/pkg/daemons/config" "github.com/rancher/k3s/pkg/daemons/config"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/util/net" "k8s.io/apimachinery/pkg/util/net"
"k8s.io/apiserver/pkg/util/logs" "k8s.io/component-base/logs"
app2 "k8s.io/kubernetes/cmd/kube-proxy/app" app2 "k8s.io/kubernetes/cmd/kube-proxy/app"
"k8s.io/kubernetes/cmd/kubelet/app" "k8s.io/kubernetes/cmd/kubelet/app"
_ "k8s.io/kubernetes/pkg/client/metrics/prometheus" // for client metric registration _ "k8s.io/kubernetes/pkg/client/metrics/prometheus" // for client metric registration
...@@ -82,6 +82,7 @@ func kubelet(cfg *config.Agent) { ...@@ -82,6 +82,7 @@ func kubelet(cfg *config.Agent) {
if cfg.RuntimeSocket != "" { if cfg.RuntimeSocket != "" {
args = append(args, "--container-runtime", "remote") args = append(args, "--container-runtime", "remote")
args = append(args, "--container-runtime-endpoint", cfg.RuntimeSocket) args = append(args, "--container-runtime-endpoint", cfg.RuntimeSocket)
args = append(args, "--serialize-image-pulls=false")
} }
if cfg.ListenAddress != "" { if cfg.ListenAddress != "" {
args = append(args, "--address", cfg.ListenAddress) args = append(args, "--address", cfg.ListenAddress)
......
...@@ -24,9 +24,9 @@ import ( ...@@ -24,9 +24,9 @@ import (
"time" "time"
"github.com/rancher/k3s/pkg/daemons/config" "github.com/rancher/k3s/pkg/daemons/config"
certutil "github.com/rancher/norman/pkg/cert"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
"k8s.io/apiserver/pkg/authentication/authenticator" "k8s.io/apiserver/pkg/authentication/authenticator"
certutil "k8s.io/client-go/util/cert"
"k8s.io/kubernetes/cmd/kube-apiserver/app" "k8s.io/kubernetes/cmd/kube-apiserver/app"
cmapp "k8s.io/kubernetes/cmd/kube-controller-manager/app" cmapp "k8s.io/kubernetes/cmd/kube-controller-manager/app"
sapp "k8s.io/kubernetes/cmd/kube-scheduler/app" sapp "k8s.io/kubernetes/cmd/kube-scheduler/app"
......
...@@ -9,8 +9,8 @@ import ( ...@@ -9,8 +9,8 @@ import (
"github.com/rancher/k3s/pkg/server" "github.com/rancher/k3s/pkg/server"
"github.com/spf13/pflag" "github.com/spf13/pflag"
utilflag "k8s.io/apiserver/pkg/util/flag" utilflag "k8s.io/component-base/cli/flag"
"k8s.io/apiserver/pkg/util/logs" "k8s.io/component-base/logs"
"k8s.io/kubernetes/pkg/kubectl/cmd" "k8s.io/kubernetes/pkg/kubectl/cmd"
) )
......
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -9,9 +9,9 @@ package=github.com/opencontainers/runc/libcontainer/nsenter ...@@ -9,9 +9,9 @@ package=github.com/opencontainers/runc/libcontainer/nsenter
package=github.com/opencontainers/runc/libcontainer/specconv package=github.com/opencontainers/runc/libcontainer/specconv
package=github.com/opencontainers/runc/contrib/cmd/recvtty package=github.com/opencontainers/runc/contrib/cmd/recvtty
k8s.io/kubernetes v1.13.5-k3s.1 https://github.com/rancher/k3s.git transitive=true,staging=true k8s.io/kubernetes v1.14.1-k3s.1 https://github.com/rancher/k3s.git transitive=true,staging=true
github.com/rancher/norman efb72b594a2a34f2573b9565c6cd9926a1f6ae08 https://github.com/ibuildthecloud/norman.git github.com/rancher/norman 50017efee23caa79542ef685b65a7b783e0a73ca https://github.com/ibuildthecloud/norman.git
github.com/coreos/flannel 823afe66b2266bf71f5bec24e6e28b26d70cfc7c https://github.com/ibuildthecloud/flannel.git github.com/coreos/flannel 823afe66b2266bf71f5bec24e6e28b26d70cfc7c https://github.com/ibuildthecloud/flannel.git
github.com/natefinch/lumberjack aee4629129445bbdfb69aa565537dcfa16544311 github.com/natefinch/lumberjack aee4629129445bbdfb69aa565537dcfa16544311
github.com/gorilla/mux v1.6.2 github.com/gorilla/mux v1.6.2
...@@ -36,10 +36,10 @@ github.com/alexflint/go-filemutex 72bdc8eae2aef913234599b837f5dda445ca9bd9 ...@@ -36,10 +36,10 @@ github.com/alexflint/go-filemutex 72bdc8eae2aef913234599b837f5dda445ca9bd9
github.com/opencontainers/runtime-spec 5684b8af48c1ac3b1451fa499724e30e3c20a294 github.com/opencontainers/runtime-spec 5684b8af48c1ac3b1451fa499724e30e3c20a294
# containerd # containerd
github.com/containerd/containerd v1.2.4-k3s1 https://github.com/ibuildthecloud/containerd github.com/containerd/containerd v1.2.6-k3s1 https://github.com/ibuildthecloud/containerd
github.com/containerd/go-runc 5a6d9f37cfa36b15efba46dc7ea349fa9b7143c3 github.com/containerd/go-runc 5a6d9f37cfa36b15efba46dc7ea349fa9b7143c3
github.com/containerd/console c12b1e7919c14469339a5d38f2f8ed9b64a9de23 github.com/containerd/console c12b1e7919c14469339a5d38f2f8ed9b64a9de23
github.com/containerd/cgroups 5e610833b72089b37d0e615de9a92dfc043757c2 github.com/containerd/cgroups 4994991857f9b0ae8dc439551e8bebdbb4bf66c1
github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40 github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40
github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c
github.com/containerd/btrfs 2e1aa0ddf94f91fa282b6ed87c23bf0d64911244 github.com/containerd/btrfs 2e1aa0ddf94f91fa282b6ed87c23bf0d64911244
...@@ -59,7 +59,7 @@ github.com/gogo/protobuf v1.0.0 ...@@ -59,7 +59,7 @@ github.com/gogo/protobuf v1.0.0
github.com/gogo/googleapis 08a7655d27152912db7aaf4f983275eaf8d128ef github.com/gogo/googleapis 08a7655d27152912db7aaf4f983275eaf8d128ef
github.com/golang/protobuf v1.1.0 github.com/golang/protobuf v1.1.0
#github.com/opencontainers/runtime-spec eba862dc2470385a233c7507392675cbeadf7353 # v1.0.1-45-geba862d #github.com/opencontainers/runtime-spec eba862dc2470385a233c7507392675cbeadf7353 # v1.0.1-45-geba862d
github.com/opencontainers/runc 6635b4f0c6af3810594d2770f662f34ddc15b40d github.com/opencontainers/runc 69ae5da6afdcaaf38285a10b36f362e41cb298d6
github.com/sirupsen/logrus v1.0.3 github.com/sirupsen/logrus v1.0.3
github.com/urfave/cli 7bc6a0acffa589f415f88aca16cc1de5ffd66f9c github.com/urfave/cli 7bc6a0acffa589f415f88aca16cc1de5ffd66f9c
golang.org/x/net b3756b4b77d7b13260a0a2ec658753cf48922eac golang.org/x/net b3756b4b77d7b13260a0a2ec658753cf48922eac
...@@ -85,11 +85,11 @@ go.etcd.io/bbolt v1.3.1-etcd.8 ...@@ -85,11 +85,11 @@ go.etcd.io/bbolt v1.3.1-etcd.8
github.com/kubernetes-sigs/cri-tools c465773e3ad8c941d1756c0a467d550b04a8f65b https://github.com/ibuildthecloud/cri-tools.git github.com/kubernetes-sigs/cri-tools c465773e3ad8c941d1756c0a467d550b04a8f65b https://github.com/ibuildthecloud/cri-tools.git
# cri dependencies # cri dependencies
github.com/containerd/cri c3cf754321fc38c6af5dfd2552fdde0ad192b31d # release/1.2 branch github.com/containerd/cri eb926cd79d3bac188dcc4ed7694fc9298f8831be # release/1.2 branch
github.com/containerd/go-cni 40bcf8ec8acd7372be1d77031d585d5d8e561c90 github.com/containerd/go-cni 40bcf8ec8acd7372be1d77031d585d5d8e561c90
github.com/blang/semver v3.1.0 github.com/blang/semver v3.1.0
github.com/containernetworking/cni v0.6.0 github.com/containernetworking/cni v0.6.0
#github.com/containernetworking/plugins v0.7.0 #github.com/containernetworking/plugins v0.7.5
github.com/davecgh/go-spew v1.1.0 github.com/davecgh/go-spew v1.1.0
github.com/docker/distribution 0d3efadf0154c2b8a4e7b6621fff9809655cc580 github.com/docker/distribution 0d3efadf0154c2b8a4e7b6621fff9809655cc580
#github.com/docker/docker 86f080cff0914e9694068ed78d503701667c4c00 #github.com/docker/docker 86f080cff0914e9694068ed78d503701667c4c00
...@@ -104,7 +104,7 @@ github.com/json-iterator/go 1.1.5 ...@@ -104,7 +104,7 @@ github.com/json-iterator/go 1.1.5
github.com/modern-go/reflect2 1.0.1 github.com/modern-go/reflect2 1.0.1
github.com/modern-go/concurrent 1.0.3 github.com/modern-go/concurrent 1.0.3
github.com/opencontainers/runtime-tools v0.6.0 github.com/opencontainers/runtime-tools v0.6.0
github.com/opencontainers/selinux b6fa367ed7f534f9ba25391cc2d467085dbb445a github.com/opencontainers/selinux v1.2.1
github.com/seccomp/libseccomp-golang 32f571b70023028bd57d9288c20efbcb237f3ce0 github.com/seccomp/libseccomp-golang 32f571b70023028bd57d9288c20efbcb237f3ce0
github.com/tchap/go-patricia v2.2.6 github.com/tchap/go-patricia v2.2.6
github.com/xeipuuv/gojsonpointer 4e3ac2762d5f479393488629ee9370b50873b3a6 github.com/xeipuuv/gojsonpointer 4e3ac2762d5f479393488629ee9370b50873b3a6
......
test/test
test/piggie
test/phaul
image
rpc/rpc.proto
language: go
sudo: required
os:
- linux
go:
- "1.8"
- "1.9"
- "1.10"
env:
# Run the tests with CRIU master and criu-dev
- CRIU_BRANCH="master"
- CRIU_BRANCH="criu-dev"
install:
- sudo apt-get update
- sudo apt-get install -y libprotobuf-dev libprotobuf-c0-dev protobuf-c-compiler protobuf-compiler python-protobuf libnl-3-dev libnet-dev libcap-dev
- go get github.com/checkpoint-restore/go-criu
- git clone --single-branch -b ${CRIU_BRANCH} https://github.com/checkpoint-restore/criu.git
- cd criu; make
- sudo install -D -m 755 criu/criu /usr/sbin/
- cd ..
script:
# This builds the code without running the tests.
- make build phaul test/test test/phaul test/piggie
# Run actual test as root as it uses CRIU.
- sudo make test phaul-test
GO ?= go
CC ?= gcc
ifeq ($(GOPATH),)
export GOPATH := $(shell $(GO) env GOPATH)
endif
FIRST_GOPATH := $(firstword $(subst :, ,$(GOPATH)))
GOBIN := $(shell $(GO) env GOBIN)
ifeq ($(GOBIN),)
GOBIN := $(FIRST_GOPATH)/bin
endif
all: build test phaul phaul-test
lint:
@golint . test phaul
build:
@$(GO) build -v
test/piggie: test/piggie.c
@$(CC) $^ -o $@
test/test: test/main.go
@$(GO) build -v -o test/test test/main.go
test: test/test test/piggie
mkdir -p image
test/piggie
test/test dump `pidof piggie` image
test/test restore image
pkill -9 piggie || :
phaul:
@cd phaul; go build -v
test/phaul: test/phaul-main.go
@$(GO) build -v -o test/phaul test/phaul-main.go
phaul-test: test/phaul test/piggie
rm -rf image
test/piggie
test/phaul `pidof piggie`
pkill -9 piggie || :
clean:
@rm -f test/test test/piggie test/phaul
@rm -rf image
@rm -f rpc/rpc.proto
install.tools:
if [ ! -x "$(GOBIN)/golint" ]; then \
$(GO) get -u golang.org/x/lint/golint; \
fi
rpc/rpc.proto:
curl -s https://raw.githubusercontent.com/checkpoint-restore/criu/master/images/rpc.proto -o $@
rpc/rpc.pb.go: rpc/rpc.proto
protoc --go_out=. $^
.PHONY: build test clean lint phaul
[![master](https://travis-ci.org/checkpoint-restore/go-criu.svg?branch=master)](https://travis-ci.org/checkpoint-restore/go-criu)
## go-criu -- Go bindings for [CRIU](https://criu.org/)
This repository provides Go bindings for CRIU. The code is based on the Go based PHaul
implementation from the CRIU repository. For easier inclusion into other Go projects the
CRIU Go bindings have been moved to this repository.
The Go bindings provide an easy way to use the CRIU RPC calls from Go without the need
to set up all the infrastructure to make the actual RPC connection to CRIU.
The following example would print the version of CRIU:
```
c := criu.MakeCriu()
version, err := c.GetCriuVersion()
fmt.Println(version)
```
or to just check if at least a certain CRIU version is installed:
```
c := criu.MakeCriu()
result, err := c.IsCriuAtLeast(31100)
```
## How to contribute
While bug fixes can first be identified via an "issue", that is not required.
It's ok to just open up a PR with the fix, but make sure you include the same
information you would have included in an issue - like how to reproduce it.
PRs for new features should include some background on what use cases the
new code is trying to address. When possible and when it makes sense, try to
break-up larger PRs into smaller ones - it's easier to review smaller
code changes. But only if those smaller ones make sense as stand-alone PRs.
Regardless of the type of PR, all PRs should include:
* well documented code changes
* additional testcases. Ideally, they should fail w/o your code change applied
* documentation changes
Squash your commits into logical pieces of work that might want to be reviewed
separate from the rest of the PRs. Ideally, each commit should implement a
single idea, and the PR branch should pass the tests at every commit. GitHub
makes it easy to review the cumulative effect of many commits; so, when in
doubt, use smaller commits.
PRs that fix issues should include a reference like `Closes #XXXX` in the
commit message so that github will automatically close the referenced issue
when the PR is merged.
Contributors must assert that they are in compliance with the [Developer
Certificate of Origin 1.1](http://developercertificate.org/). This is achieved
by adding a "Signed-off-by" line containing the contributor's name and e-mail
to every commit message. Your signature certifies that you wrote the patch or
otherwise have the right to pass it on as an open-source patch.
### License
The license of go-criu is the Apache 2.0 license.
# How to Contribute # How to Contribute
CSI is under [Apache 2.0](LICENSE) and accepts contributions via GitHub pull requests. This document outlines some of the requirements and conventions for contributing to the Container Storage Interface, including development workflow, commit message formatting, contact points, and other resources to make it easier to get your contribution accepted.
## Licence and CLA
Contributions require signing an individual or Corporate CLA available [here](https://github.com/container-storage-interface/spec/blob/master/CCLA.pdf) which should be signed and mailed to the [mailing list]( https://groups.google.com/forum/#!topic/container-storage-interface-community/). CSI is under [Apache 2.0](LICENSE) and accepts contributions via GitHub pull requests.
This document outlines some of the conventions on development workflow, commit message formatting, contact points and other resources to make it easier to get your contribution accepted. Before contributing to the Container Storage Interface, contributors MUST sign the CLA available [here](https://github.com/container-storage-interface/spec/blob/master/CCLA.pdf).
The CLA MAY be signed on behalf of a company, possibly covering multiple contributors, or as an individual (put "Individual" for "Corporation name").
The completed CLA MUST be mailed to the CSI Approvers [mailing list](container-storage-interface-approvers@googlegroups.com).
## Markdown style ## Markdown style
......
...@@ -57,6 +57,9 @@ service Controller { ...@@ -57,6 +57,9 @@ service Controller {
rpc ListSnapshots (ListSnapshotsRequest) rpc ListSnapshots (ListSnapshotsRequest)
returns (ListSnapshotsResponse) {} returns (ListSnapshotsResponse) {}
rpc ControllerExpandVolume (ControllerExpandVolumeRequest)
returns (ControllerExpandVolumeResponse) {}
} }
service Node { service Node {
...@@ -75,6 +78,11 @@ service Node { ...@@ -75,6 +78,11 @@ service Node {
rpc NodeGetVolumeStats (NodeGetVolumeStatsRequest) rpc NodeGetVolumeStats (NodeGetVolumeStatsRequest)
returns (NodeGetVolumeStatsResponse) {} returns (NodeGetVolumeStatsResponse) {}
rpc NodeExpandVolume(NodeExpandVolumeRequest)
returns (NodeExpandVolumeResponse) {}
rpc NodeGetCapabilities (NodeGetCapabilitiesRequest) rpc NodeGetCapabilities (NodeGetCapabilitiesRequest)
returns (NodeGetCapabilitiesResponse) {} returns (NodeGetCapabilitiesResponse) {}
...@@ -116,7 +124,6 @@ message PluginCapability { ...@@ -116,7 +124,6 @@ message PluginCapability {
message Service { message Service {
enum Type { enum Type {
UNKNOWN = 0; UNKNOWN = 0;
// CONTROLLER_SERVICE indicates that the Plugin provides RPCs for // CONTROLLER_SERVICE indicates that the Plugin provides RPCs for
// the ControllerService. Plugins SHOULD provide this capability. // the ControllerService. Plugins SHOULD provide this capability.
// In rare cases certain plugins MAY wish to omit the // In rare cases certain plugins MAY wish to omit the
...@@ -138,9 +145,56 @@ message PluginCapability { ...@@ -138,9 +145,56 @@ message PluginCapability {
Type type = 1; Type type = 1;
} }
message VolumeExpansion {
enum Type {
UNKNOWN = 0;
// ONLINE indicates that volumes may be expanded when published to
// a node. When a Plugin implements this capability it MUST
// implement either the EXPAND_VOLUME controller capability or the
// EXPAND_VOLUME node capability or both. When a plugin supports
// ONLINE volume expansion and also has the EXPAND_VOLUME
// controller capability then the plugin MUST support expansion of
// volumes currently published and available on a node. When a
// plugin supports ONLINE volume expansion and also has the
// EXPAND_VOLUME node capability then the plugin MAY support
// expansion of node-published volume via NodeExpandVolume.
//
// Example 1: Given a shared filesystem volume (e.g. GlusterFs),
// the Plugin may set the ONLINE volume expansion capability and
// implement ControllerExpandVolume but not NodeExpandVolume.
//
// Example 2: Given a block storage volume type (e.g. EBS), the
// Plugin may set the ONLINE volume expansion capability and
// implement both ControllerExpandVolume and NodeExpandVolume.
//
// Example 3: Given a Plugin that supports volume expansion only
// upon a node, the Plugin may set the ONLINE volume
// expansion capability and implement NodeExpandVolume but not
// ControllerExpandVolume.
ONLINE = 1;
// OFFLINE indicates that volumes currently published and
// available on a node SHALL NOT be expanded via
// ControllerExpandVolume. When a plugin supports OFFLINE volume
// expansion it MUST implement either the EXPAND_VOLUME controller
// capability or both the EXPAND_VOLUME controller capability and
// the EXPAND_VOLUME node capability.
//
// Example 1: Given a block storage volume type (e.g. Azure Disk)
// that does not support expansion of "node-attached" (i.e.
// controller-published) volumes, the Plugin may indicate
// OFFLINE volume expansion support and implement both
// ControllerExpandVolume and NodeExpandVolume.
OFFLINE = 2;
}
Type type = 1;
}
oneof type { oneof type {
// Service that the plugin supports. // Service that the plugin supports.
Service service = 1; Service service = 1;
VolumeExpansion volume_expansion = 2;
} }
} }
message ProbeRequest { message ProbeRequest {
...@@ -236,7 +290,7 @@ message CreateVolumeRequest { ...@@ -236,7 +290,7 @@ message CreateVolumeRequest {
// If specified, the new volume will be pre-populated with data from // If specified, the new volume will be pre-populated with data from
// this source. This field is OPTIONAL. // this source. This field is OPTIONAL.
VolumeContentSource volume_content_source = 6; VolumeContentSource volume_content_source = 6;
// Specifies where (regions, zones, racks, etc.) the provisioned // Specifies where (regions, zones, racks, etc.) the provisioned
// volume MUST be accessible from. // volume MUST be accessible from.
// An SP SHALL advertise the requirements for topological // An SP SHALL advertise the requirements for topological
...@@ -420,7 +474,7 @@ message Volume { ...@@ -420,7 +474,7 @@ message Volume {
// Example 2: // Example 2:
// accessible_topology = // accessible_topology =
// {"region": "R1", "zone": "Z2"}, // {"region": "R1", "zone": "Z2"},
// {"region": "R1", "zone": "Z3"} // {"region": "R1", "zone": "Z3"}
// Indicates a volume accessible from both "zone" "Z2" and "zone" "Z3" // Indicates a volume accessible from both "zone" "Z2" and "zone" "Z3"
// in the "region" "R1". // in the "region" "R1".
repeated Topology accessible_topology = 5; repeated Topology accessible_topology = 5;
...@@ -431,15 +485,15 @@ message TopologyRequirement { ...@@ -431,15 +485,15 @@ message TopologyRequirement {
// accessible from. // accessible from.
// This field is OPTIONAL. If TopologyRequirement is specified either // This field is OPTIONAL. If TopologyRequirement is specified either
// requisite or preferred or both MUST be specified. // requisite or preferred or both MUST be specified.
// //
// If requisite is specified, the provisioned volume MUST be // If requisite is specified, the provisioned volume MUST be
// accessible from at least one of the requisite topologies. // accessible from at least one of the requisite topologies.
// //
// Given // Given
// x = number of topologies provisioned volume is accessible from // x = number of topologies provisioned volume is accessible from
// n = number of requisite topologies // n = number of requisite topologies
// The CO MUST ensure n >= 1. The SP MUST ensure x >= 1 // The CO MUST ensure n >= 1. The SP MUST ensure x >= 1
// If x==n, than the SP MUST make the provisioned volume available to // If x==n, then the SP MUST make the provisioned volume available to
// all topologies from the list of requisite topologies. If it is // all topologies from the list of requisite topologies. If it is
// unable to do so, the SP MUST fail the CreateVolume call. // unable to do so, the SP MUST fail the CreateVolume call.
// For example, if a volume should be accessible from a single zone, // For example, if a volume should be accessible from a single zone,
...@@ -454,7 +508,7 @@ message TopologyRequirement { ...@@ -454,7 +508,7 @@ message TopologyRequirement {
// then the provisioned volume MUST be accessible from the "region" // then the provisioned volume MUST be accessible from the "region"
// "R1" and both "zone" "Z2" and "zone" "Z3". // "R1" and both "zone" "Z2" and "zone" "Z3".
// //
// If x<n, than the SP SHALL choose x unique topologies from the list // If x<n, then the SP SHALL choose x unique topologies from the list
// of requisite topologies. If it is unable to do so, the SP MUST fail // of requisite topologies. If it is unable to do so, the SP MUST fail
// the CreateVolume call. // the CreateVolume call.
// For example, if a volume should be accessible from a single zone, // For example, if a volume should be accessible from a single zone,
...@@ -472,7 +526,7 @@ message TopologyRequirement { ...@@ -472,7 +526,7 @@ message TopologyRequirement {
// of two unique topologies: e.g. "R1/Z2" and "R1/Z3", or "R1/Z2" and // of two unique topologies: e.g. "R1/Z2" and "R1/Z3", or "R1/Z2" and
// "R1/Z4", or "R1/Z3" and "R1/Z4". // "R1/Z4", or "R1/Z3" and "R1/Z4".
// //
// If x>n, than the SP MUST make the provisioned volume available from // If x>n, then the SP MUST make the provisioned volume available from
// all topologies from the list of requisite topologies and MAY choose // all topologies from the list of requisite topologies and MAY choose
// the remaining x-n unique topologies from the list of all possible // the remaining x-n unique topologies from the list of all possible
// topologies. If it is unable to do so, the SP MUST fail the // topologies. If it is unable to do so, the SP MUST fail the
...@@ -490,7 +544,7 @@ message TopologyRequirement { ...@@ -490,7 +544,7 @@ message TopologyRequirement {
// //
// This field is OPTIONAL. If TopologyRequirement is specified either // This field is OPTIONAL. If TopologyRequirement is specified either
// requisite or preferred or both MUST be specified. // requisite or preferred or both MUST be specified.
// //
// An SP MUST attempt to make the provisioned volume available using // An SP MUST attempt to make the provisioned volume available using
// the preferred topologies in order from first to last. // the preferred topologies in order from first to last.
// //
...@@ -505,7 +559,7 @@ message TopologyRequirement { ...@@ -505,7 +559,7 @@ message TopologyRequirement {
// If the list of requisite topologies is specified and the SP is // If the list of requisite topologies is specified and the SP is
// unable to to make the provisioned volume available from any of the // unable to to make the provisioned volume available from any of the
// requisite topologies it MUST fail the CreateVolume call. // requisite topologies it MUST fail the CreateVolume call.
// //
// Example 1: // Example 1:
// Given a volume should be accessible from a single zone, and // Given a volume should be accessible from a single zone, and
// requisite = // requisite =
...@@ -817,14 +871,19 @@ message ControllerServiceCapability { ...@@ -817,14 +871,19 @@ message ControllerServiceCapability {
// snapshot. // snapshot.
CREATE_DELETE_SNAPSHOT = 5; CREATE_DELETE_SNAPSHOT = 5;
LIST_SNAPSHOTS = 6; LIST_SNAPSHOTS = 6;
// Plugins supporting volume cloning at the storage level MAY // Plugins supporting volume cloning at the storage level MAY
// report this capability. The source volume MUST be managed by // report this capability. The source volume MUST be managed by
// the same plugin. Not all volume sources and parameters // the same plugin. Not all volume sources and parameters
// combinations MAY work. // combinations MAY work.
CLONE_VOLUME = 7; CLONE_VOLUME = 7;
// Indicates the SP supports ControllerPublishVolume.readonly // Indicates the SP supports ControllerPublishVolume.readonly
// field. // field.
PUBLISH_READONLY = 8; PUBLISH_READONLY = 8;
// See VolumeExpansion for details.
EXPAND_VOLUME = 9;
} }
Type type = 1; Type type = 1;
...@@ -967,6 +1026,28 @@ message ListSnapshotsResponse { ...@@ -967,6 +1026,28 @@ message ListSnapshotsResponse {
// An empty string is equal to an unspecified field value. // An empty string is equal to an unspecified field value.
string next_token = 2; string next_token = 2;
} }
message ControllerExpandVolumeRequest {
// The ID of the volume to expand. This field is REQUIRED.
string volume_id = 1;
// This allows CO to specify the capacity requirements of the volume
// after expansion. This field is REQUIRED.
CapacityRange capacity_range = 2;
// Secrets required by the plugin for expanding the volume.
// This field is OPTIONAL.
map<string, string> secrets = 3 [(csi_secret) = true];
}
message ControllerExpandVolumeResponse {
// Capacity of volume after expansion. This field is REQUIRED.
int64 capacity_bytes = 1;
// Whether node expansion is required for the volume. When true
// the CO MUST make NodeExpandVolume RPC call on the node. This field
// is REQUIRED.
bool node_expansion_required = 2;
}
message NodeStageVolumeRequest { message NodeStageVolumeRequest {
// The ID of the volume to publish. This field is REQUIRED. // The ID of the volume to publish. This field is REQUIRED.
string volume_id = 1; string volume_id = 1;
...@@ -1151,6 +1232,8 @@ message NodeServiceCapability { ...@@ -1151,6 +1232,8 @@ message NodeServiceCapability {
// then it MUST implement NodeGetVolumeStats RPC // then it MUST implement NodeGetVolumeStats RPC
// call for fetching volume statistics. // call for fetching volume statistics.
GET_VOLUME_STATS = 2; GET_VOLUME_STATS = 2;
// See VolumeExpansion for details.
EXPAND_VOLUME = 3;
} }
Type type = 1; Type type = 1;
...@@ -1196,8 +1279,28 @@ message NodeGetInfoResponse { ...@@ -1196,8 +1279,28 @@ message NodeGetInfoResponse {
// //
// Example 1: // Example 1:
// accessible_topology = // accessible_topology =
// {"region": "R1", "zone": "R2"} // {"region": "R1", "zone": "Z2"}
// Indicates the node exists within the "region" "R1" and the "zone" // Indicates the node exists within the "region" "R1" and the "zone"
// "Z2". // "Z2".
Topology accessible_topology = 3; Topology accessible_topology = 3;
} }
message NodeExpandVolumeRequest {
// The ID of the volume. This field is REQUIRED.
string volume_id = 1;
// The path on which volume is available. This field is REQUIRED.
string volume_path = 2;
// This allows CO to specify the capacity requirements of the volume
// after expansion. If capacity_range is omitted then a plugin MAY
// inspect the file system of the volume to determine the maximum
// capacity to which the volume can be expanded. In such cases a
// plugin MAY expand the volume to its maximum capacity.
// This field is OPTIONAL.
CapacityRange capacity_range = 3;
}
message NodeExpandVolumeResponse {
// The capacity of the volume in bytes. This field is OPTIONAL.
int64 capacity_bytes = 1;
}
language: go language: go
go: go:
- 1.10.x - 1.10.x
- tip - 1.11.x
install: install:
- mkdir -p $GOPATH/src/github.com/prometheus $GOPATH/src/github.com/opencontainers - mkdir -p $GOPATH/src/github.com/prometheus $GOPATH/src/github.com/opencontainers $GOPATH/src/github.com/coreos $GOPATH/src/github.com/godbus
- cd $GOPATH/src/github.com/opencontainers && git clone https://github.com/opencontainers/runtime-spec && cd runtime-spec && git checkout fa4b36aa9c99e00c2ef7b5c0013c84100ede5f4e - cd $GOPATH/src/github.com/opencontainers && git clone https://github.com/opencontainers/runtime-spec && cd runtime-spec && git checkout fa4b36aa9c99e00c2ef7b5c0013c84100ede5f4e
- cd $GOPATH/src/github.com/coreos && git clone https://github.com/coreos/go-systemd && cd go-systemd && git checkout 48702e0da86bd25e76cfef347e2adeb434a0d0a6
- cd $GOPATH/src/github.com/godbus && git clone https://github.com/godbus/dbus && cd dbus && git checkout c7fdd8b5cd55e87b4e1f4e372cdb1db61dd6c66f
- cd $GOPATH/src/github.com/containerd/cgroups - cd $GOPATH/src/github.com/containerd/cgroups
- go get -t ./... - go get -d -t ./...
- go get -u github.com/vbatts/git-validation
- go get -u github.com/kunalkushwaha/ltag
before_script:
- pushd ..; git clone https://github.com/containerd/project; popd
script: script:
- DCO_VERBOSITY=-q ../project/script/validate/dco
- ../project/script/validate/fileheader ../project/
- go test -race -coverprofile=coverage.txt -covermode=atomic - go test -race -coverprofile=coverage.txt -covermode=atomic
after_success: after_success:
......
# cgroups # cgroups
[![Build Status](https://travis-ci.org/containerd/cgroups.svg?branch=master)](https://travis-ci.org/containerd/cgroups) [![Build Status](https://travis-ci.org/containerd/cgroups.svg?branch=master)](https://travis-ci.org/containerd/cgroups)
[![codecov](https://codecov.io/gh/containerd/cgroups/branch/master/graph/badge.svg)](https://codecov.io/gh/containerd/cgroups) [![codecov](https://codecov.io/gh/containerd/cgroups/branch/master/graph/badge.svg)](https://codecov.io/gh/containerd/cgroups)
[![GoDoc](https://godoc.org/github.com/containerd/cgroups?status.svg)](https://godoc.org/github.com/containerd/cgroups)
[![Go Report Card](https://goreportcard.com/badge/github.com/containerd/cgroups)](https://goreportcard.com/report/github.com/containerd/cgroups)
Go package for creating, managing, inspecting, and destroying cgroups. Go package for creating, managing, inspecting, and destroying cgroups.
The resources format for settings on the cgroup uses the OCI runtime-spec found The resources format for settings on the cgroup uses the OCI runtime-spec found
...@@ -110,3 +111,14 @@ err := control.MoveTo(destination) ...@@ -110,3 +111,14 @@ err := control.MoveTo(destination)
```go ```go
subCgroup, err := control.New("child", resources) subCgroup, err := control.New("child", resources)
``` ```
## Project details
Cgroups is a containerd sub-project, licensed under the [Apache 2.0 license](./LICENSE).
As a containerd sub-project, you will find the:
* [Project governance](https://github.com/containerd/project/blob/master/GOVERNANCE.md),
* [Maintainers](https://github.com/containerd/project/blob/master/MAINTAINERS),
* and [Contributing guidelines](https://github.com/containerd/project/blob/master/CONTRIBUTING.md)
information in our [`containerd/project`](https://github.com/containerd/project) repository.
...@@ -191,31 +191,42 @@ func (b *blkioController) readEntry(devices map[deviceKey]string, path, name str ...@@ -191,31 +191,42 @@ func (b *blkioController) readEntry(devices map[deviceKey]string, path, name str
} }
func createBlkioSettings(blkio *specs.LinuxBlockIO) []blkioSettings { func createBlkioSettings(blkio *specs.LinuxBlockIO) []blkioSettings {
settings := []blkioSettings{ settings := []blkioSettings{}
{
name: "weight", if blkio.Weight != nil {
value: blkio.Weight,
format: uintf,
},
{
name: "leaf_weight",
value: blkio.LeafWeight,
format: uintf,
},
}
for _, wd := range blkio.WeightDevice {
settings = append(settings, settings = append(settings,
blkioSettings{ blkioSettings{
name: "weight_device", name: "weight",
value: wd, value: blkio.Weight,
format: weightdev, format: uintf,
}, })
}
if blkio.LeafWeight != nil {
settings = append(settings,
blkioSettings{ blkioSettings{
name: "leaf_weight_device", name: "leaf_weight",
value: wd, value: blkio.LeafWeight,
format: weightleafdev, format: uintf,
}) })
} }
for _, wd := range blkio.WeightDevice {
if wd.Weight != nil {
settings = append(settings,
blkioSettings{
name: "weight_device",
value: wd,
format: weightdev,
})
}
if wd.LeafWeight != nil {
settings = append(settings,
blkioSettings{
name: "leaf_weight_device",
value: wd,
format: weightleafdev,
})
}
}
for _, t := range []struct { for _, t := range []struct {
name string name string
list []specs.LinuxThrottleDevice list []specs.LinuxThrottleDevice
...@@ -265,12 +276,12 @@ func uintf(v interface{}) []byte { ...@@ -265,12 +276,12 @@ func uintf(v interface{}) []byte {
func weightdev(v interface{}) []byte { func weightdev(v interface{}) []byte {
wd := v.(specs.LinuxWeightDevice) wd := v.(specs.LinuxWeightDevice)
return []byte(fmt.Sprintf("%d:%d %d", wd.Major, wd.Minor, wd.Weight)) return []byte(fmt.Sprintf("%d:%d %d", wd.Major, wd.Minor, *wd.Weight))
} }
func weightleafdev(v interface{}) []byte { func weightleafdev(v interface{}) []byte {
wd := v.(specs.LinuxWeightDevice) wd := v.(specs.LinuxWeightDevice)
return []byte(fmt.Sprintf("%d:%d %d", wd.Major, wd.Minor, wd.LeafWeight)) return []byte(fmt.Sprintf("%d:%d %d", wd.Major, wd.Minor, *wd.LeafWeight))
} }
func throttleddev(v interface{}) []byte { func throttleddev(v interface{}) []byte {
......
...@@ -30,47 +30,88 @@ import ( ...@@ -30,47 +30,88 @@ import (
) )
// New returns a new control via the cgroup cgroups interface // New returns a new control via the cgroup cgroups interface
func New(hierarchy Hierarchy, path Path, resources *specs.LinuxResources) (Cgroup, error) { func New(hierarchy Hierarchy, path Path, resources *specs.LinuxResources, opts ...InitOpts) (Cgroup, error) {
config := newInitConfig()
for _, o := range opts {
if err := o(config); err != nil {
return nil, err
}
}
subsystems, err := hierarchy() subsystems, err := hierarchy()
if err != nil { if err != nil {
return nil, err return nil, err
} }
var active []Subsystem
for _, s := range subsystems { for _, s := range subsystems {
// check if subsystem exists
if err := initializeSubsystem(s, path, resources); err != nil { if err := initializeSubsystem(s, path, resources); err != nil {
if err == ErrControllerNotActive {
if config.InitCheck != nil {
if skerr := config.InitCheck(s, path, err); skerr != nil {
if skerr != ErrIgnoreSubsystem {
return nil, skerr
}
}
}
continue
}
return nil, err return nil, err
} }
active = append(active, s)
} }
return &cgroup{ return &cgroup{
path: path, path: path,
subsystems: subsystems, subsystems: active,
}, nil }, nil
} }
// Load will load an existing cgroup and allow it to be controlled // Load will load an existing cgroup and allow it to be controlled
func Load(hierarchy Hierarchy, path Path) (Cgroup, error) { func Load(hierarchy Hierarchy, path Path, opts ...InitOpts) (Cgroup, error) {
config := newInitConfig()
for _, o := range opts {
if err := o(config); err != nil {
return nil, err
}
}
var activeSubsystems []Subsystem
subsystems, err := hierarchy() subsystems, err := hierarchy()
if err != nil { if err != nil {
return nil, err return nil, err
} }
// check the the subsystems still exist // check that the subsystems still exist, and keep only those that actually exist
for _, s := range pathers(subsystems) { for _, s := range pathers(subsystems) {
p, err := path(s.Name()) p, err := path(s.Name())
if err != nil { if err != nil {
if os.IsNotExist(errors.Cause(err)) { if os.IsNotExist(errors.Cause(err)) {
return nil, ErrCgroupDeleted return nil, ErrCgroupDeleted
} }
if err == ErrControllerNotActive {
if config.InitCheck != nil {
if skerr := config.InitCheck(s, path, err); skerr != nil {
if skerr != ErrIgnoreSubsystem {
return nil, skerr
}
}
}
continue
}
return nil, err return nil, err
} }
if _, err := os.Lstat(s.Path(p)); err != nil { if _, err := os.Lstat(s.Path(p)); err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
return nil, ErrCgroupDeleted continue
} }
return nil, err return nil, err
} }
activeSubsystems = append(activeSubsystems, s)
}
// if we do not have any active systems then the cgroup is deleted
if len(activeSubsystems) == 0 {
return nil, ErrCgroupDeleted
} }
return &cgroup{ return &cgroup{
path: path, path: path,
subsystems: subsystems, subsystems: activeSubsystems,
}, nil }, nil
} }
...@@ -319,6 +360,49 @@ func (c *cgroup) processes(subsystem Name, recursive bool) ([]Process, error) { ...@@ -319,6 +360,49 @@ func (c *cgroup) processes(subsystem Name, recursive bool) ([]Process, error) {
return processes, err return processes, err
} }
// Tasks returns the tasks running inside the cgroup along
// with the subsystem used, pid, and path
func (c *cgroup) Tasks(subsystem Name, recursive bool) ([]Task, error) {
c.mu.Lock()
defer c.mu.Unlock()
if c.err != nil {
return nil, c.err
}
return c.tasks(subsystem, recursive)
}
func (c *cgroup) tasks(subsystem Name, recursive bool) ([]Task, error) {
s := c.getSubsystem(subsystem)
sp, err := c.path(subsystem)
if err != nil {
return nil, err
}
path := s.(pather).Path(sp)
var tasks []Task
err = filepath.Walk(path, func(p string, info os.FileInfo, err error) error {
if err != nil {
return err
}
if !recursive && info.IsDir() {
if p == path {
return nil
}
return filepath.SkipDir
}
dir, name := filepath.Split(p)
if name != cgroupTasks {
return nil
}
procs, err := readTasksPids(dir, subsystem)
if err != nil {
return err
}
tasks = append(tasks, procs...)
return nil
})
return tasks, err
}
// Freeze freezes the entire cgroup and all the processes inside it // Freeze freezes the entire cgroup and all the processes inside it
func (c *cgroup) Freeze() error { func (c *cgroup) Freeze() error {
c.mu.Lock() c.mu.Lock()
......
...@@ -44,6 +44,15 @@ type Process struct { ...@@ -44,6 +44,15 @@ type Process struct {
Path string Path string
} }
type Task struct {
// Subsystem is the name of the subsystem that the task is in
Subsystem Name
// Pid is the process id of the task
Pid int
// Path is the full path of the subsystem and location that the task is in
Path string
}
// Cgroup handles interactions with the individual groups to perform // Cgroup handles interactions with the individual groups to perform
// actions on them as them main interface to this cgroup package // actions on them as them main interface to this cgroup package
type Cgroup interface { type Cgroup interface {
...@@ -64,6 +73,8 @@ type Cgroup interface { ...@@ -64,6 +73,8 @@ type Cgroup interface {
Update(resources *specs.LinuxResources) error Update(resources *specs.LinuxResources) error
// Processes returns all the processes in a select subsystem for the cgroup // Processes returns all the processes in a select subsystem for the cgroup
Processes(Name, bool) ([]Process, error) Processes(Name, bool) ([]Process, error)
// Tasks returns all the tasks in a select subsystem for the cgroup
Tasks(Name, bool) ([]Task, error)
// Freeze freezes or pauses all processes inside the cgroup // Freeze freezes or pauses all processes inside the cgroup
Freeze() error Freeze() error
// Thaw thaw or resumes all processes inside the cgroup // Thaw thaw or resumes all processes inside the cgroup
......
...@@ -57,21 +57,21 @@ func (c *cpusetController) Create(path string, resources *specs.LinuxResources) ...@@ -57,21 +57,21 @@ func (c *cpusetController) Create(path string, resources *specs.LinuxResources)
if resources.CPU != nil { if resources.CPU != nil {
for _, t := range []struct { for _, t := range []struct {
name string name string
value *string value string
}{ }{
{ {
name: "cpus", name: "cpus",
value: &resources.CPU.Cpus, value: resources.CPU.Cpus,
}, },
{ {
name: "mems", name: "mems",
value: &resources.CPU.Mems, value: resources.CPU.Mems,
}, },
} { } {
if t.value != nil { if t.value != "" {
if err := ioutil.WriteFile( if err := ioutil.WriteFile(
filepath.Join(c.Path(path), fmt.Sprintf("cpuset.%s", t.name)), filepath.Join(c.Path(path), fmt.Sprintf("cpuset.%s", t.name)),
[]byte(*t.value), []byte(t.value),
defaultFilePerm, defaultFilePerm,
); err != nil { ); err != nil {
return err return err
......
...@@ -58,6 +58,9 @@ func (d *devicesController) Create(path string, resources *specs.LinuxResources) ...@@ -58,6 +58,9 @@ func (d *devicesController) Create(path string, resources *specs.LinuxResources)
if device.Allow { if device.Allow {
file = allowDeviceFile file = allowDeviceFile
} }
if device.Type == "" {
device.Type = "a"
}
if err := ioutil.WriteFile( if err := ioutil.WriteFile(
filepath.Join(d.Path(path), file), filepath.Join(d.Path(path), file),
[]byte(deviceString(device)), []byte(deviceString(device)),
......
...@@ -50,7 +50,7 @@ func (n *netprioController) Create(path string, resources *specs.LinuxResources) ...@@ -50,7 +50,7 @@ func (n *netprioController) Create(path string, resources *specs.LinuxResources)
if resources.Network != nil { if resources.Network != nil {
for _, prio := range resources.Network.Priorities { for _, prio := range resources.Network.Priorities {
if err := ioutil.WriteFile( if err := ioutil.WriteFile(
filepath.Join(n.Path(path), "net_prio_ifpriomap"), filepath.Join(n.Path(path), "net_prio.ifpriomap"),
formatPrio(prio.Name, prio.Priority), formatPrio(prio.Name, prio.Priority),
defaultFilePerm, defaultFilePerm,
); err != nil { ); err != nil {
......
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cgroups
import (
"github.com/pkg/errors"
)
var (
// ErrIgnoreSubsystem allows the specific subsystem to be skipped
ErrIgnoreSubsystem = errors.New("skip subsystem")
// ErrDevicesRequired is returned when the devices subsystem is required but
// does not exist or is not active
ErrDevicesRequired = errors.New("devices subsystem is required")
)
// InitOpts allows configuration for the creation or loading of a cgroup
type InitOpts func(*InitConfig) error
// InitConfig provides configuration options for the creation
// or loading of a cgroup and its subsystems
type InitConfig struct {
// InitCheck can be used to check initialization errors from the subsystem
InitCheck InitCheck
}
func newInitConfig() *InitConfig {
return &InitConfig{
InitCheck: RequireDevices,
}
}
// InitCheck allows subsystems errors to be checked when initialized or loaded
type InitCheck func(Subsystem, Path, error) error
// AllowAny allows any subsystem errors to be skipped
func AllowAny(s Subsystem, p Path, err error) error {
return ErrIgnoreSubsystem
}
// RequireDevices requires the device subsystem but no others
func RequireDevices(s Subsystem, p Path, err error) error {
if s.Name() == Devices {
return ErrDevicesRequired
}
return ErrIgnoreSubsystem
}
...@@ -57,6 +57,9 @@ func PidPath(pid int) Path { ...@@ -57,6 +57,9 @@ func PidPath(pid int) Path {
return existingPath(paths, "") return existingPath(paths, "")
} }
// ErrControllerNotActive is returned when a controller is not supported or enabled
var ErrControllerNotActive = errors.New("controller is not supported")
func existingPath(paths map[string]string, suffix string) Path { func existingPath(paths map[string]string, suffix string) Path {
// localize the paths based on the root mount dest for nested cgroups // localize the paths based on the root mount dest for nested cgroups
for n, p := range paths { for n, p := range paths {
...@@ -77,7 +80,7 @@ func existingPath(paths map[string]string, suffix string) Path { ...@@ -77,7 +80,7 @@ func existingPath(paths map[string]string, suffix string) Path {
root, ok := paths[string(name)] root, ok := paths[string(name)]
if !ok { if !ok {
if root, ok = paths[fmt.Sprintf("name=%s", name)]; !ok { if root, ok = paths[fmt.Sprintf("name=%s", name)]; !ok {
return "", fmt.Errorf("unable to find %q in controller set", name) return "", ErrControllerNotActive
} }
} }
if suffix != "" { if suffix != "" {
......
...@@ -42,7 +42,7 @@ const ( ...@@ -42,7 +42,7 @@ const (
) )
// Subsystems returns a complete list of the default cgroups // Subsystems returns a complete list of the default cgroups
// avaliable on most linux systems // available on most linux systems
func Subsystems() []Name { func Subsystems() []Name {
n := []Name{ n := []Name{
Hugetlb, Hugetlb,
......
...@@ -32,6 +32,11 @@ const ( ...@@ -32,6 +32,11 @@ const (
defaultSlice = "system.slice" defaultSlice = "system.slice"
) )
var (
canDelegate bool
once sync.Once
)
func Systemd() ([]Subsystem, error) { func Systemd() ([]Subsystem, error) {
root, err := v1MountPoint() root, err := v1MountPoint()
if err != nil { if err != nil {
...@@ -54,7 +59,7 @@ func Slice(slice, name string) Path { ...@@ -54,7 +59,7 @@ func Slice(slice, name string) Path {
slice = defaultSlice slice = defaultSlice
} }
return func(subsystem Name) (string, error) { return func(subsystem Name) (string, error) {
return filepath.Join(slice, unitName(name)), nil return filepath.Join(slice, name), nil
} }
} }
...@@ -80,15 +85,39 @@ func (s *SystemdController) Create(path string, resources *specs.LinuxResources) ...@@ -80,15 +85,39 @@ func (s *SystemdController) Create(path string, resources *specs.LinuxResources)
} }
defer conn.Close() defer conn.Close()
slice, name := splitName(path) slice, name := splitName(path)
// We need to see if systemd can handle the delegate property
// Systemd will return an error if it cannot handle delegate regardless
// of its bool setting.
checkDelegate := func() {
canDelegate = true
dlSlice := newProperty("Delegate", true)
if _, err := conn.StartTransientUnit(slice, "testdelegate", []systemdDbus.Property{dlSlice}, nil); err != nil {
if dbusError, ok := err.(dbus.Error); ok {
// Starting with systemd v237, Delegate is not even a property of slices anymore,
// so the D-Bus call fails with "InvalidArgs" error.
if strings.Contains(dbusError.Name, "org.freedesktop.DBus.Error.PropertyReadOnly") || strings.Contains(dbusError.Name, "org.freedesktop.DBus.Error.InvalidArgs") {
canDelegate = false
}
}
}
conn.StopUnit(slice, "testDelegate", nil)
}
once.Do(checkDelegate)
properties := []systemdDbus.Property{ properties := []systemdDbus.Property{
systemdDbus.PropDescription(fmt.Sprintf("cgroup %s", name)), systemdDbus.PropDescription(fmt.Sprintf("cgroup %s", name)),
systemdDbus.PropWants(slice), systemdDbus.PropWants(slice),
newProperty("DefaultDependencies", false), newProperty("DefaultDependencies", false),
newProperty("Delegate", true),
newProperty("MemoryAccounting", true), newProperty("MemoryAccounting", true),
newProperty("CPUAccounting", true), newProperty("CPUAccounting", true),
newProperty("BlockIOAccounting", true), newProperty("BlockIOAccounting", true),
} }
// If we can delegate, we add the property back in
if canDelegate {
properties = append(properties, newProperty("Delegate", true))
}
ch := make(chan string) ch := make(chan string)
_, err = conn.StartTransientUnit(name, "replace", properties, ch) _, err = conn.StartTransientUnit(name, "replace", properties, ch)
if err != nil { if err != nil {
......
...@@ -111,7 +111,7 @@ func remove(path string) error { ...@@ -111,7 +111,7 @@ func remove(path string) error {
return fmt.Errorf("cgroups: unable to remove path %q", path) return fmt.Errorf("cgroups: unable to remove path %q", path)
} }
// readPids will read all the pids in a cgroup by the provided path // readPids will read all the pids of processes in a cgroup by the provided path
func readPids(path string, subsystem Name) ([]Process, error) { func readPids(path string, subsystem Name) ([]Process, error) {
f, err := os.Open(filepath.Join(path, cgroupProcs)) f, err := os.Open(filepath.Join(path, cgroupProcs))
if err != nil { if err != nil {
...@@ -138,6 +138,33 @@ func readPids(path string, subsystem Name) ([]Process, error) { ...@@ -138,6 +138,33 @@ func readPids(path string, subsystem Name) ([]Process, error) {
return out, nil return out, nil
} }
// readTasksPids will read all the pids of tasks in a cgroup by the provided path
func readTasksPids(path string, subsystem Name) ([]Task, error) {
f, err := os.Open(filepath.Join(path, cgroupTasks))
if err != nil {
return nil, err
}
defer f.Close()
var (
out []Task
s = bufio.NewScanner(f)
)
for s.Scan() {
if t := s.Text(); t != "" {
pid, err := strconv.Atoi(t)
if err != nil {
return nil, err
}
out = append(out, Task{
Pid: pid,
Subsystem: subsystem,
Path: path,
})
}
}
return out, nil
}
func hugePageSizes() ([]string, error) { func hugePageSizes() ([]string, error) {
var ( var (
pageSizes []string pageSizes []string
......
...@@ -13,7 +13,7 @@ environment: ...@@ -13,7 +13,7 @@ environment:
GOPATH: C:\gopath GOPATH: C:\gopath
CGO_ENABLED: 1 CGO_ENABLED: 1
matrix: matrix:
- GO_VERSION: 1.10 - GO_VERSION: 1.11
before_build: before_build:
- choco install -y mingw --version 5.3.0 - choco install -y mingw --version 5.3.0
......
...@@ -2,7 +2,9 @@ Abhinandan Prativadi <abhi@docker.com> Abhinandan Prativadi <aprativadi@gmail.co ...@@ -2,7 +2,9 @@ Abhinandan Prativadi <abhi@docker.com> Abhinandan Prativadi <aprativadi@gmail.co
Abhinandan Prativadi <abhi@docker.com> abhi <abhi@docker.com> Abhinandan Prativadi <abhi@docker.com> abhi <abhi@docker.com>
Akihiro Suda <suda.akihiro@lab.ntt.co.jp> Akihiro Suda <suda.kyoto@gmail.com> Akihiro Suda <suda.akihiro@lab.ntt.co.jp> Akihiro Suda <suda.kyoto@gmail.com>
Andrei Vagin <avagin@virtuozzo.com> Andrei Vagin <avagin@openvz.org> Andrei Vagin <avagin@virtuozzo.com> Andrei Vagin <avagin@openvz.org>
Brent Baude <bbaude@redhat.com> baude <bbaude@redhat.com>
Frank Yang <yyb196@gmail.com> frank yang <yyb196@gmail.com> Frank Yang <yyb196@gmail.com> frank yang <yyb196@gmail.com>
Georgia Panoutsakopoulou <gpanoutsak@gmail.com> gpanouts <gpanoutsak@gmail.com>
Jie Zhang <iamkadisi@163.com> kadisi <iamkadisi@163.com> Jie Zhang <iamkadisi@163.com> kadisi <iamkadisi@163.com>
John Howard <john.howard@microsoft.com> John Howard <jhoward@microsoft.com> John Howard <john.howard@microsoft.com> John Howard <jhoward@microsoft.com>
Justin Terry <juterry@microsoft.com> Justin Terry (VM) <juterry@microsoft.com> Justin Terry <juterry@microsoft.com> Justin Terry (VM) <juterry@microsoft.com>
......
...@@ -7,7 +7,7 @@ services: ...@@ -7,7 +7,7 @@ services:
language: go language: go
go: go:
- "1.10.x" - "1.11.x"
go_import_path: github.com/containerd/containerd go_import_path: github.com/containerd/containerd
......
...@@ -22,6 +22,7 @@ DESTDIR=/usr/local ...@@ -22,6 +22,7 @@ DESTDIR=/usr/local
# Used to populate variables in version package. # Used to populate variables in version package.
VERSION=$(shell git describe --match 'v[0-9]*' --dirty='.m' --always) VERSION=$(shell git describe --match 'v[0-9]*' --dirty='.m' --always)
REVISION=$(shell git rev-parse HEAD)$(shell if ! git diff --no-ext-diff --quiet --exit-code; then echo .m; fi) REVISION=$(shell git rev-parse HEAD)$(shell if ! git diff --no-ext-diff --quiet --exit-code; then echo .m; fi)
PACKAGE=github.com/containerd/containerd
ifneq "$(strip $(shell command -v go 2>/dev/null))" "" ifneq "$(strip $(shell command -v go 2>/dev/null))" ""
GOOS ?= $(shell go env GOOS) GOOS ?= $(shell go env GOOS)
...@@ -77,8 +78,8 @@ MANPAGES=ctr.1 containerd.1 containerd-config.1 containerd-config.toml.5 ...@@ -77,8 +78,8 @@ MANPAGES=ctr.1 containerd.1 containerd-config.1 containerd-config.toml.5
# Build tags seccomp and apparmor are needed by CRI plugin. # Build tags seccomp and apparmor are needed by CRI plugin.
BUILDTAGS ?= seccomp apparmor BUILDTAGS ?= seccomp apparmor
GO_TAGS=$(if $(BUILDTAGS),-tags "$(BUILDTAGS)",) GO_TAGS=$(if $(BUILDTAGS),-tags "$(BUILDTAGS)",)
GO_LDFLAGS=-ldflags '-s -w -X $(PKG)/version.Version=$(VERSION) -X $(PKG)/version.Revision=$(REVISION) -X $(PKG)/version.Package=$(PKG) $(EXTRA_LDFLAGS)' GO_LDFLAGS=-ldflags '-s -w -X $(PKG)/version.Version=$(VERSION) -X $(PKG)/version.Revision=$(REVISION) -X $(PKG)/version.Package=$(PACKAGE) $(EXTRA_LDFLAGS)'
SHIM_GO_LDFLAGS=-ldflags '-s -w -X $(PKG)/version.Version=$(VERSION) -X $(PKG)/version.Revision=$(REVISION) -X $(PKG)/version.Package=$(PKG) -extldflags "-static"' SHIM_GO_LDFLAGS=-ldflags '-s -w -X $(PKG)/version.Version=$(VERSION) -X $(PKG)/version.Revision=$(REVISION) -X $(PKG)/version.Package=$(PACKAGE) -extldflags "-static"'
#Replaces ":" (*nix), ";" (windows) with newline for easy parsing #Replaces ":" (*nix), ";" (windows) with newline for easy parsing
GOPATHS=$(shell echo ${GOPATH} | tr ":" "\n" | tr ";" "\n") GOPATHS=$(shell echo ${GOPATH} | tr ":" "\n" | tr ";" "\n")
......
...@@ -194,7 +194,7 @@ func applyNaive(ctx context.Context, root string, tr *tar.Reader, options ApplyO ...@@ -194,7 +194,7 @@ func applyNaive(ctx context.Context, root string, tr *tar.Reader, options ApplyO
parentPath = filepath.Dir(path) parentPath = filepath.Dir(path)
} }
if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) { if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
err = mkdirAll(parentPath, 0700) err = mkdirAll(parentPath, 0755)
if err != nil { if err != nil {
return 0, err return 0, err
} }
......
...@@ -7,19 +7,19 @@ ...@@ -7,19 +7,19 @@
# #
# Install proto3 # Install proto3
FROM golang:1.10 AS proto3 FROM golang:1.11 AS proto3
RUN apt-get update && apt-get install -y autoconf automake g++ libtool unzip RUN apt-get update && apt-get install -y autoconf automake g++ libtool unzip
COPY script/setup/install-protobuf install-protobuf COPY script/setup/install-protobuf install-protobuf
RUN ./install-protobuf RUN ./install-protobuf
# Install runc # Install runc
FROM golang:1.10 AS runc FROM golang:1.11 AS runc
RUN apt-get update && apt-get install -y curl libseccomp-dev RUN apt-get update && apt-get install -y curl libseccomp-dev
COPY vendor.conf /go/src/github.com/containerd/containerd/vendor.conf COPY vendor.conf /go/src/github.com/containerd/containerd/vendor.conf
COPY script/setup/install-runc install-runc COPY script/setup/install-runc install-runc
RUN ./install-runc RUN ./install-runc
FROM golang:1.10 FROM golang:1.11
RUN apt-get update && apt-get install -y btrfs-tools gcc git libseccomp-dev make xfsprogs RUN apt-get update && apt-get install -y btrfs-tools gcc git libseccomp-dev make xfsprogs
COPY --from=proto3 /usr/local/bin/protoc /usr/local/bin/protoc COPY --from=proto3 /usr/local/bin/protoc /usr/local/bin/protoc
......
...@@ -161,6 +161,7 @@ func DefaultProfile(sp *specs.Spec) *specs.LinuxSeccomp { ...@@ -161,6 +161,7 @@ func DefaultProfile(sp *specs.Spec) *specs.LinuxSeccomp {
"ioctl", "ioctl",
"io_destroy", "io_destroy",
"io_getevents", "io_getevents",
"io_pgetevents",
"ioprio_get", "ioprio_get",
"ioprio_set", "ioprio_set",
"io_setup", "io_setup",
...@@ -319,6 +320,7 @@ func DefaultProfile(sp *specs.Spec) *specs.LinuxSeccomp { ...@@ -319,6 +320,7 @@ func DefaultProfile(sp *specs.Spec) *specs.LinuxSeccomp {
"stat64", "stat64",
"statfs", "statfs",
"statfs64", "statfs64",
"statx",
"symlink", "symlink",
"symlinkat", "symlinkat",
"sync", "sync",
......
...@@ -170,26 +170,22 @@ func (i *image) Unpack(ctx context.Context, snapshotterName string) error { ...@@ -170,26 +170,22 @@ func (i *image) Unpack(ctx context.Context, snapshotterName string) error {
chain = append(chain, layer.Diff.Digest) chain = append(chain, layer.Diff.Digest)
} }
if unpacked { desc, err := i.i.Config(ctx, cs, i.platform)
desc, err := i.i.Config(ctx, cs, i.platform) if err != nil {
if err != nil { return err
return err }
}
rootfs := identity.ChainID(chain).String() rootfs := identity.ChainID(chain).String()
cinfo := content.Info{ cinfo := content.Info{
Digest: desc.Digest, Digest: desc.Digest,
Labels: map[string]string{ Labels: map[string]string{
fmt.Sprintf("containerd.io/gc.ref.snapshot.%s", snapshotterName): rootfs, fmt.Sprintf("containerd.io/gc.ref.snapshot.%s", snapshotterName): rootfs,
}, },
}
if _, err := cs.Update(ctx, cinfo, fmt.Sprintf("labels.containerd.io/gc.ref.snapshot.%s", snapshotterName)); err != nil {
return err
}
} }
return nil _, err = cs.Update(ctx, cinfo, fmt.Sprintf("labels.containerd.io/gc.ref.snapshot.%s", snapshotterName))
return err
} }
func (i *image) getLayers(ctx context.Context, platform platforms.MatchComparer) ([]rootfs.Layer, error) { func (i *image) getLayers(ctx context.Context, platform platforms.MatchComparer) ([]rootfs.Layer, error) {
......
...@@ -64,6 +64,18 @@ func scanRoots(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error { ...@@ -64,6 +64,18 @@ func scanRoots(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error {
// iterate through each namespace // iterate through each namespace
v1c := v1bkt.Cursor() v1c := v1bkt.Cursor()
// cerr indicates the scan did not successfully send all
// the roots. The scan does not need to be cancelled but
// must return error at the end.
var cerr error
fn := func(n gc.Node) {
select {
case nc <- n:
case <-ctx.Done():
cerr = ctx.Err()
}
}
for k, v := v1c.First(); k != nil; k, v = v1c.Next() { for k, v := v1c.First(); k != nil; k, v = v1c.Next() {
if v != nil { if v != nil {
continue continue
...@@ -92,11 +104,7 @@ func scanRoots(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error { ...@@ -92,11 +104,7 @@ func scanRoots(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error {
} }
} }
select { fn(gcnode(ResourceLease, ns, string(k)))
case nc <- gcnode(ResourceLease, ns, string(k)):
case <-ctx.Done():
return ctx.Err()
}
// Emit content and snapshots as roots instead of implementing // Emit content and snapshots as roots instead of implementing
// in references. Since leases cannot be referenced there is // in references. Since leases cannot be referenced there is
...@@ -106,11 +114,7 @@ func scanRoots(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error { ...@@ -106,11 +114,7 @@ func scanRoots(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error {
cbkt := libkt.Bucket(bucketKeyObjectContent) cbkt := libkt.Bucket(bucketKeyObjectContent)
if cbkt != nil { if cbkt != nil {
if err := cbkt.ForEach(func(k, v []byte) error { if err := cbkt.ForEach(func(k, v []byte) error {
select { fn(gcnode(ResourceContent, ns, string(k)))
case nc <- gcnode(ResourceContent, ns, string(k)):
case <-ctx.Done():
return ctx.Err()
}
return nil return nil
}); err != nil { }); err != nil {
return err return err
...@@ -126,11 +130,7 @@ func scanRoots(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error { ...@@ -126,11 +130,7 @@ func scanRoots(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error {
snbkt := sbkt.Bucket(sk) snbkt := sbkt.Bucket(sk)
return snbkt.ForEach(func(k, v []byte) error { return snbkt.ForEach(func(k, v []byte) error {
select { fn(gcnode(ResourceSnapshot, ns, fmt.Sprintf("%s/%s", sk, k)))
case nc <- gcnode(ResourceSnapshot, ns, fmt.Sprintf("%s/%s", sk, k)):
case <-ctx.Done():
return ctx.Err()
}
return nil return nil
}) })
}); err != nil { }); err != nil {
...@@ -141,11 +141,7 @@ func scanRoots(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error { ...@@ -141,11 +141,7 @@ func scanRoots(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error {
ibkt := libkt.Bucket(bucketKeyObjectIngests) ibkt := libkt.Bucket(bucketKeyObjectIngests)
if ibkt != nil { if ibkt != nil {
if err := ibkt.ForEach(func(k, v []byte) error { if err := ibkt.ForEach(func(k, v []byte) error {
select { fn(gcnode(ResourceIngest, ns, string(k)))
case nc <- gcnode(ResourceIngest, ns, string(k)):
case <-ctx.Done():
return ctx.Err()
}
return nil return nil
}); err != nil { }); err != nil {
return err return err
...@@ -168,18 +164,9 @@ func scanRoots(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error { ...@@ -168,18 +164,9 @@ func scanRoots(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error {
target := ibkt.Bucket(k).Bucket(bucketKeyTarget) target := ibkt.Bucket(k).Bucket(bucketKeyTarget)
if target != nil { if target != nil {
contentKey := string(target.Get(bucketKeyDigest)) contentKey := string(target.Get(bucketKeyDigest))
select { fn(gcnode(ResourceContent, ns, contentKey))
case nc <- gcnode(ResourceContent, ns, contentKey):
case <-ctx.Done():
return ctx.Err()
}
} }
return sendSnapshotRefs(ns, ibkt.Bucket(k), func(n gc.Node) { return sendLabelRefs(ns, ibkt.Bucket(k), fn)
select {
case nc <- n:
case <-ctx.Done():
}
})
}); err != nil { }); err != nil {
return err return err
} }
...@@ -200,11 +187,7 @@ func scanRoots(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error { ...@@ -200,11 +187,7 @@ func scanRoots(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error {
if ea == nil || expThreshold.After(*ea) { if ea == nil || expThreshold.After(*ea) {
return nil return nil
} }
select { fn(gcnode(ResourceIngest, ns, string(k)))
case nc <- gcnode(ResourceIngest, ns, string(k)):
case <-ctx.Done():
return ctx.Err()
}
return nil return nil
}); err != nil { }); err != nil {
return err return err
...@@ -216,7 +199,12 @@ func scanRoots(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error { ...@@ -216,7 +199,12 @@ func scanRoots(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error {
if v != nil { if v != nil {
return nil return nil
} }
return sendRootRef(ctx, nc, gcnode(ResourceContent, ns, string(k)), cbkt.Bucket(k))
if isRootRef(cbkt.Bucket(k)) {
fn(gcnode(ResourceContent, ns, string(k)))
}
return nil
}); err != nil { }); err != nil {
return err return err
} }
...@@ -229,23 +217,15 @@ func scanRoots(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error { ...@@ -229,23 +217,15 @@ func scanRoots(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error {
if v != nil { if v != nil {
return nil return nil
} }
snapshotter := string(cbkt.Bucket(k).Get(bucketKeySnapshotter))
cibkt := cbkt.Bucket(k)
snapshotter := string(cibkt.Get(bucketKeySnapshotter))
if snapshotter != "" { if snapshotter != "" {
ss := string(cbkt.Bucket(k).Get(bucketKeySnapshotKey)) ss := string(cibkt.Get(bucketKeySnapshotKey))
select { fn(gcnode(ResourceSnapshot, ns, fmt.Sprintf("%s/%s", snapshotter, ss)))
case nc <- gcnode(ResourceSnapshot, ns, fmt.Sprintf("%s/%s", snapshotter, ss)):
case <-ctx.Done():
return ctx.Err()
}
} }
// TODO: Send additional snapshot refs through labels return sendLabelRefs(ns, cibkt, fn)
return sendSnapshotRefs(ns, cbkt.Bucket(k), func(n gc.Node) {
select {
case nc <- n:
case <-ctx.Done():
}
})
}); err != nil { }); err != nil {
return err return err
} }
...@@ -263,15 +243,17 @@ func scanRoots(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error { ...@@ -263,15 +243,17 @@ func scanRoots(ctx context.Context, tx *bolt.Tx, nc chan<- gc.Node) error {
if v != nil { if v != nil {
return nil return nil
} }
if isRootRef(snbkt.Bucket(k)) {
return sendRootRef(ctx, nc, gcnode(ResourceSnapshot, ns, fmt.Sprintf("%s/%s", sk, k)), snbkt.Bucket(k)) fn(gcnode(ResourceSnapshot, ns, fmt.Sprintf("%s/%s", sk, k)))
}
return nil
}) })
}); err != nil { }); err != nil {
return err return err
} }
} }
} }
return nil return cerr
} }
func references(ctx context.Context, tx *bolt.Tx, node gc.Node, fn func(gc.Node)) error { func references(ctx context.Context, tx *bolt.Tx, node gc.Node, fn func(gc.Node)) error {
...@@ -282,10 +264,7 @@ func references(ctx context.Context, tx *bolt.Tx, node gc.Node, fn func(gc.Node) ...@@ -282,10 +264,7 @@ func references(ctx context.Context, tx *bolt.Tx, node gc.Node, fn func(gc.Node)
return nil return nil
} }
if err := sendSnapshotRefs(node.Namespace, bkt, fn); err != nil { return sendLabelRefs(node.Namespace, bkt, fn)
return err
}
return sendContentRefs(node.Namespace, bkt, fn)
} else if node.Type == ResourceSnapshot { } else if node.Type == ResourceSnapshot {
parts := strings.SplitN(node.Key, "/", 2) parts := strings.SplitN(node.Key, "/", 2)
if len(parts) != 2 { if len(parts) != 2 {
...@@ -304,7 +283,7 @@ func references(ctx context.Context, tx *bolt.Tx, node gc.Node, fn func(gc.Node) ...@@ -304,7 +283,7 @@ func references(ctx context.Context, tx *bolt.Tx, node gc.Node, fn func(gc.Node)
fn(gcnode(ResourceSnapshot, node.Namespace, fmt.Sprintf("%s/%s", ss, pv))) fn(gcnode(ResourceSnapshot, node.Namespace, fmt.Sprintf("%s/%s", ss, pv)))
} }
return sendSnapshotRefs(node.Namespace, bkt, fn) return sendLabelRefs(node.Namespace, bkt, fn)
} else if node.Type == ResourceIngest { } else if node.Type == ResourceIngest {
// Send expected value // Send expected value
bkt := getBucket(tx, bucketKeyVersion, []byte(node.Namespace), bucketKeyObjectContent, bucketKeyObjectIngests, []byte(node.Key)) bkt := getBucket(tx, bucketKeyVersion, []byte(node.Namespace), bucketKeyObjectContent, bucketKeyObjectIngests, []byte(node.Key))
...@@ -456,25 +435,8 @@ func remove(ctx context.Context, tx *bolt.Tx, node gc.Node) error { ...@@ -456,25 +435,8 @@ func remove(ctx context.Context, tx *bolt.Tx, node gc.Node) error {
return nil return nil
} }
// sendSnapshotRefs sends all snapshot references referred to by the labels in the bkt // sendLabelRefs sends all snapshot and content references referred to by the labels in the bkt
func sendSnapshotRefs(ns string, bkt *bolt.Bucket, fn func(gc.Node)) error { func sendLabelRefs(ns string, bkt *bolt.Bucket, fn func(gc.Node)) error {
lbkt := bkt.Bucket(bucketKeyObjectLabels)
if lbkt != nil {
lc := lbkt.Cursor()
for k, v := lc.Seek(labelGCSnapRef); k != nil && strings.HasPrefix(string(k), string(labelGCSnapRef)); k, v = lc.Next() {
snapshotter := k[len(labelGCSnapRef):]
if i := bytes.IndexByte(snapshotter, '/'); i >= 0 {
snapshotter = snapshotter[:i]
}
fn(gcnode(ResourceSnapshot, ns, fmt.Sprintf("%s/%s", snapshotter, v)))
}
}
return nil
}
// sendContentRefs sends all content references referred to by the labels in the bkt
func sendContentRefs(ns string, bkt *bolt.Bucket, fn func(gc.Node)) error {
lbkt := bkt.Bucket(bucketKeyObjectLabels) lbkt := bkt.Bucket(bucketKeyObjectLabels)
if lbkt != nil { if lbkt != nil {
lc := lbkt.Cursor() lc := lbkt.Cursor()
...@@ -490,6 +452,15 @@ func sendContentRefs(ns string, bkt *bolt.Bucket, fn func(gc.Node)) error { ...@@ -490,6 +452,15 @@ func sendContentRefs(ns string, bkt *bolt.Bucket, fn func(gc.Node)) error {
fn(gcnode(ResourceContent, ns, string(v))) fn(gcnode(ResourceContent, ns, string(v)))
} }
for k, v := lc.Seek(labelGCSnapRef); k != nil && strings.HasPrefix(string(k), string(labelGCSnapRef)); k, v = lc.Next() {
snapshotter := k[len(labelGCSnapRef):]
if i := bytes.IndexByte(snapshotter, '/'); i >= 0 {
snapshotter = snapshotter[:i]
}
fn(gcnode(ResourceSnapshot, ns, fmt.Sprintf("%s/%s", snapshotter, v)))
}
} }
return nil return nil
} }
...@@ -506,17 +477,6 @@ func isRootRef(bkt *bolt.Bucket) bool { ...@@ -506,17 +477,6 @@ func isRootRef(bkt *bolt.Bucket) bool {
return false return false
} }
func sendRootRef(ctx context.Context, nc chan<- gc.Node, n gc.Node, bkt *bolt.Bucket) error {
if isRootRef(bkt) {
select {
case nc <- n:
case <-ctx.Done():
return ctx.Err()
}
}
return nil
}
func gcnode(t gc.ResourceType, ns, key string) gc.Node { func gcnode(t gc.ResourceType, ns, key string) gc.Node {
return gc.Node{ return gc.Node{
Type: t, Type: t,
......
...@@ -25,6 +25,8 @@ import ( ...@@ -25,6 +25,8 @@ import (
"os" "os"
"strconv" "strconv"
"strings" "strings"
"github.com/pkg/errors"
) )
// Self retrieves a list of mounts for the current running process. // Self retrieves a list of mounts for the current running process.
...@@ -41,13 +43,15 @@ func Self() ([]Info, error) { ...@@ -41,13 +43,15 @@ func Self() ([]Info, error) {
func parseInfoFile(r io.Reader) ([]Info, error) { func parseInfoFile(r io.Reader) ([]Info, error) {
s := bufio.NewScanner(r) s := bufio.NewScanner(r)
out := []Info{} out := []Info{}
var err error
for s.Scan() { for s.Scan() {
if err := s.Err(); err != nil { if err = s.Err(); err != nil {
return nil, err return nil, err
} }
/* /*
See http://man7.org/linux/man-pages/man5/proc.5.html
36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue 36 35 98:0 /mnt1 /mnt2 rw,noatime master:1 - ext3 /dev/root rw,errors=continue
(1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11) (1)(2)(3) (4) (5) (6) (7) (8) (9) (10) (11)
(1) mount ID: unique identifier of the mount (may be reused after umount) (1) mount ID: unique identifier of the mount (may be reused after umount)
...@@ -68,7 +72,7 @@ func parseInfoFile(r io.Reader) ([]Info, error) { ...@@ -68,7 +72,7 @@ func parseInfoFile(r io.Reader) ([]Info, error) {
numFields := len(fields) numFields := len(fields)
if numFields < 10 { if numFields < 10 {
// should be at least 10 fields // should be at least 10 fields
return nil, fmt.Errorf("parsing '%s' failed: not enough fields (%d)", text, numFields) return nil, errors.Errorf("parsing '%s' failed: not enough fields (%d)", text, numFields)
} }
p := Info{} p := Info{}
// ignore any numbers parsing errors, as there should not be any // ignore any numbers parsing errors, as there should not be any
...@@ -76,13 +80,19 @@ func parseInfoFile(r io.Reader) ([]Info, error) { ...@@ -76,13 +80,19 @@ func parseInfoFile(r io.Reader) ([]Info, error) {
p.Parent, _ = strconv.Atoi(fields[1]) p.Parent, _ = strconv.Atoi(fields[1])
mm := strings.Split(fields[2], ":") mm := strings.Split(fields[2], ":")
if len(mm) != 2 { if len(mm) != 2 {
return nil, fmt.Errorf("parsing '%s' failed: unexpected minor:major pair %s", text, mm) return nil, errors.Errorf("parsing '%s' failed: unexpected minor:major pair %s", text, mm)
} }
p.Major, _ = strconv.Atoi(mm[0]) p.Major, _ = strconv.Atoi(mm[0])
p.Minor, _ = strconv.Atoi(mm[1]) p.Minor, _ = strconv.Atoi(mm[1])
p.Root = fields[3] p.Root, err = strconv.Unquote(`"` + fields[3] + `"`)
p.Mountpoint = fields[4] if err != nil {
return nil, errors.Wrapf(err, "parsing '%s' failed: unable to unquote root field", fields[3])
}
p.Mountpoint, err = strconv.Unquote(`"` + fields[4] + `"`)
if err != nil {
return nil, errors.Wrapf(err, "parsing '%s' failed: unable to unquote mount point field", fields[4])
}
p.Options = fields[5] p.Options = fields[5]
// one or more optional fields, when a separator (-) // one or more optional fields, when a separator (-)
...@@ -101,11 +111,11 @@ func parseInfoFile(r io.Reader) ([]Info, error) { ...@@ -101,11 +111,11 @@ func parseInfoFile(r io.Reader) ([]Info, error) {
} }
} }
if i == numFields { if i == numFields {
return nil, fmt.Errorf("parsing '%s' failed: missing separator ('-')", text) return nil, errors.Errorf("parsing '%s' failed: missing separator ('-')", text)
} }
// There should be 3 fields after the separator... // There should be 3 fields after the separator...
if i+4 > numFields { if i+4 > numFields {
return nil, fmt.Errorf("parsing '%s' failed: not enough fields after a separator", text) return nil, errors.Errorf("parsing '%s' failed: not enough fields after a separator", text)
} }
// ... but in Linux <= 3.9 mounting a cifs with spaces in a share name // ... but in Linux <= 3.9 mounting a cifs with spaces in a share name
// (like "//serv/My Documents") _may_ end up having a space in the last field // (like "//serv/My Documents") _may_ end up having a space in the last field
......
...@@ -733,7 +733,9 @@ func WithCapabilities(caps []string) SpecOpts { ...@@ -733,7 +733,9 @@ func WithCapabilities(caps []string) SpecOpts {
} }
// WithAllCapabilities sets all linux capabilities for the process // WithAllCapabilities sets all linux capabilities for the process
var WithAllCapabilities = WithCapabilities(getAllCapabilities()) var WithAllCapabilities = func(ctx context.Context, client Client, c *containers.Container, s *Spec) error {
return WithCapabilities(getAllCapabilities())(ctx, client, c, s)
}
func getAllCapabilities() []string { func getAllCapabilities() []string {
last := capability.CAP_LAST_CAP last := capability.CAP_LAST_CAP
......
...@@ -24,8 +24,10 @@ import ( ...@@ -24,8 +24,10 @@ import (
"io" "io"
"os" "os"
"sync" "sync"
"sync/atomic"
"syscall" "syscall"
"github.com/containerd/containerd/log"
"github.com/containerd/fifo" "github.com/containerd/fifo"
runc "github.com/containerd/go-runc" runc "github.com/containerd/go-runc"
) )
...@@ -38,7 +40,7 @@ var bufPool = sync.Pool{ ...@@ -38,7 +40,7 @@ var bufPool = sync.Pool{
} }
func copyPipes(ctx context.Context, rio runc.IO, stdin, stdout, stderr string, wg, cwg *sync.WaitGroup) error { func copyPipes(ctx context.Context, rio runc.IO, stdin, stdout, stderr string, wg, cwg *sync.WaitGroup) error {
var sameFile io.WriteCloser var sameFile *countingWriteCloser
for _, i := range []struct { for _, i := range []struct {
name string name string
dest func(wc io.WriteCloser, rc io.Closer) dest func(wc io.WriteCloser, rc io.Closer)
...@@ -52,7 +54,9 @@ func copyPipes(ctx context.Context, rio runc.IO, stdin, stdout, stderr string, w ...@@ -52,7 +54,9 @@ func copyPipes(ctx context.Context, rio runc.IO, stdin, stdout, stderr string, w
cwg.Done() cwg.Done()
p := bufPool.Get().(*[]byte) p := bufPool.Get().(*[]byte)
defer bufPool.Put(p) defer bufPool.Put(p)
io.CopyBuffer(wc, rio.Stdout(), *p) if _, err := io.CopyBuffer(wc, rio.Stdout(), *p); err != nil {
log.G(ctx).Warn("error copying stdout")
}
wg.Done() wg.Done()
wc.Close() wc.Close()
if rc != nil { if rc != nil {
...@@ -69,7 +73,9 @@ func copyPipes(ctx context.Context, rio runc.IO, stdin, stdout, stderr string, w ...@@ -69,7 +73,9 @@ func copyPipes(ctx context.Context, rio runc.IO, stdin, stdout, stderr string, w
cwg.Done() cwg.Done()
p := bufPool.Get().(*[]byte) p := bufPool.Get().(*[]byte)
defer bufPool.Put(p) defer bufPool.Put(p)
io.CopyBuffer(wc, rio.Stderr(), *p) if _, err := io.CopyBuffer(wc, rio.Stderr(), *p); err != nil {
log.G(ctx).Warn("error copying stderr")
}
wg.Done() wg.Done()
wc.Close() wc.Close()
if rc != nil { if rc != nil {
...@@ -96,6 +102,7 @@ func copyPipes(ctx context.Context, rio runc.IO, stdin, stdout, stderr string, w ...@@ -96,6 +102,7 @@ func copyPipes(ctx context.Context, rio runc.IO, stdin, stdout, stderr string, w
} }
} else { } else {
if sameFile != nil { if sameFile != nil {
sameFile.count++
i.dest(sameFile, nil) i.dest(sameFile, nil)
continue continue
} }
...@@ -103,7 +110,10 @@ func copyPipes(ctx context.Context, rio runc.IO, stdin, stdout, stderr string, w ...@@ -103,7 +110,10 @@ func copyPipes(ctx context.Context, rio runc.IO, stdin, stdout, stderr string, w
return fmt.Errorf("containerd-shim: opening %s failed: %s", i.name, err) return fmt.Errorf("containerd-shim: opening %s failed: %s", i.name, err)
} }
if stdout == stderr { if stdout == stderr {
sameFile = fw sameFile = &countingWriteCloser{
WriteCloser: fw,
count: 1,
}
} }
} }
i.dest(fw, fr) i.dest(fw, fr)
...@@ -128,6 +138,19 @@ func copyPipes(ctx context.Context, rio runc.IO, stdin, stdout, stderr string, w ...@@ -128,6 +138,19 @@ func copyPipes(ctx context.Context, rio runc.IO, stdin, stdout, stderr string, w
return nil return nil
} }
// countingWriteCloser masks io.Closer() until close has been invoked a certain number of times.
type countingWriteCloser struct {
io.WriteCloser
count int64
}
func (c *countingWriteCloser) Close() error {
if atomic.AddInt64(&c.count, -1) > 0 {
return nil
}
return c.WriteCloser.Close()
}
// isFifo checks if a file is a fifo // isFifo checks if a file is a fifo
// if the file does not exist then it returns false // if the file does not exist then it returns false
func isFifo(path string) (bool, error) { func isFifo(path string) (bool, error) {
......
github.com/containerd/go-runc 5a6d9f37cfa36b15efba46dc7ea349fa9b7143c3 github.com/containerd/go-runc 5a6d9f37cfa36b15efba46dc7ea349fa9b7143c3
github.com/containerd/console c12b1e7919c14469339a5d38f2f8ed9b64a9de23 github.com/containerd/console c12b1e7919c14469339a5d38f2f8ed9b64a9de23
github.com/containerd/cgroups 5e610833b72089b37d0e615de9a92dfc043757c2 github.com/containerd/cgroups 4994991857f9b0ae8dc439551e8bebdbb4bf66c1
github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40 github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40
github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c
github.com/containerd/btrfs 2e1aa0ddf94f91fa282b6ed87c23bf0d64911244 github.com/containerd/btrfs 2e1aa0ddf94f91fa282b6ed87c23bf0d64911244
...@@ -20,7 +20,7 @@ github.com/gogo/protobuf v1.0.0 ...@@ -20,7 +20,7 @@ github.com/gogo/protobuf v1.0.0
github.com/gogo/googleapis 08a7655d27152912db7aaf4f983275eaf8d128ef github.com/gogo/googleapis 08a7655d27152912db7aaf4f983275eaf8d128ef
github.com/golang/protobuf v1.1.0 github.com/golang/protobuf v1.1.0
github.com/opencontainers/runtime-spec eba862dc2470385a233c7507392675cbeadf7353 # v1.0.1-45-geba862d github.com/opencontainers/runtime-spec eba862dc2470385a233c7507392675cbeadf7353 # v1.0.1-45-geba862d
github.com/opencontainers/runc 6635b4f0c6af3810594d2770f662f34ddc15b40d github.com/opencontainers/runc 029124da7af7360afa781a0234d1b083550f797c # v1.0.0-rc7-6-g029124da
github.com/sirupsen/logrus v1.0.0 github.com/sirupsen/logrus v1.0.0
github.com/urfave/cli 7bc6a0acffa589f415f88aca16cc1de5ffd66f9c github.com/urfave/cli 7bc6a0acffa589f415f88aca16cc1de5ffd66f9c
golang.org/x/net b3756b4b77d7b13260a0a2ec658753cf48922eac golang.org/x/net b3756b4b77d7b13260a0a2ec658753cf48922eac
...@@ -43,11 +43,11 @@ github.com/google/go-cmp v0.1.0 ...@@ -43,11 +43,11 @@ github.com/google/go-cmp v0.1.0
go.etcd.io/bbolt v1.3.1-etcd.8 go.etcd.io/bbolt v1.3.1-etcd.8
# cri dependencies # cri dependencies
github.com/containerd/cri da0c016c830b2ea97fd1d737c49a568a816bf964 # release/1.2 branch github.com/containerd/cri eb926cd79d3bac188dcc4ed7694fc9298f8831be # release/1.2 branch
github.com/containerd/go-cni 40bcf8ec8acd7372be1d77031d585d5d8e561c90 github.com/containerd/go-cni 40bcf8ec8acd7372be1d77031d585d5d8e561c90
github.com/blang/semver v3.1.0 github.com/blang/semver v3.1.0
github.com/containernetworking/cni v0.6.0 github.com/containernetworking/cni v0.6.0
github.com/containernetworking/plugins v0.7.0 github.com/containernetworking/plugins v0.7.5
github.com/davecgh/go-spew v1.1.0 github.com/davecgh/go-spew v1.1.0
github.com/docker/distribution 0d3efadf0154c2b8a4e7b6621fff9809655cc580 github.com/docker/distribution 0d3efadf0154c2b8a4e7b6621fff9809655cc580
github.com/docker/docker 86f080cff0914e9694068ed78d503701667c4c00 github.com/docker/docker 86f080cff0914e9694068ed78d503701667c4c00
...@@ -62,7 +62,7 @@ github.com/json-iterator/go 1.1.5 ...@@ -62,7 +62,7 @@ github.com/json-iterator/go 1.1.5
github.com/modern-go/reflect2 1.0.1 github.com/modern-go/reflect2 1.0.1
github.com/modern-go/concurrent 1.0.3 github.com/modern-go/concurrent 1.0.3
github.com/opencontainers/runtime-tools v0.6.0 github.com/opencontainers/runtime-tools v0.6.0
github.com/opencontainers/selinux b6fa367ed7f534f9ba25391cc2d467085dbb445a github.com/opencontainers/selinux v1.2.1
github.com/seccomp/libseccomp-golang 32f571b70023028bd57d9288c20efbcb237f3ce0 github.com/seccomp/libseccomp-golang 32f571b70023028bd57d9288c20efbcb237f3ce0
github.com/tchap/go-patricia v2.2.6 github.com/tchap/go-patricia v2.2.6
github.com/xeipuuv/gojsonpointer 4e3ac2762d5f479393488629ee9370b50873b3a6 github.com/xeipuuv/gojsonpointer 4e3ac2762d5f479393488629ee9370b50873b3a6
......
...@@ -21,7 +21,7 @@ var ( ...@@ -21,7 +21,7 @@ var (
Package = "github.com/containerd/containerd" Package = "github.com/containerd/containerd"
// Version holds the complete version number. Filled in at linking time. // Version holds the complete version number. Filled in at linking time.
Version = "1.2.4+unknown" Version = "1.2.5+unknown"
// Revision is filled with the VCS (e.g. git) revision being used to build // Revision is filled with the VCS (e.g. git) revision being used to build
// the program at linking time. // the program at linking time.
......
...@@ -32,6 +32,15 @@ const ( ...@@ -32,6 +32,15 @@ const (
// SandboxID is the sandbox ID annotation // SandboxID is the sandbox ID annotation
SandboxID = "io.kubernetes.cri.sandbox-id" SandboxID = "io.kubernetes.cri.sandbox-id"
// SandboxLogDir is the pod log directory annotation.
// If the sandbox needs to generate any log, it will put it into this directory.
// Kubelet will be responsible for:
// 1) Monitoring the disk usage of the log, and including it as part of the pod
// ephemeral storage usage.
// 2) Cleaning up the logs when the pod is deleted.
// NOTE: Kubelet is not responsible for rotating the logs.
SandboxLogDir = "io.kubernetes.cri.sandbox-log-directory"
// UntrustedWorkload is the sandbox annotation for untrusted workload. Untrusted // UntrustedWorkload is the sandbox annotation for untrusted workload. Untrusted
// workload can only run on dedicated runtime for untrusted workload. // workload can only run on dedicated runtime for untrusted workload.
UntrustedWorkload = "io.kubernetes.cri.untrusted-workload" UntrustedWorkload = "io.kubernetes.cri.untrusted-workload"
......
...@@ -35,7 +35,6 @@ import ( ...@@ -35,7 +35,6 @@ import (
imagespec "github.com/opencontainers/image-spec/specs-go/v1" imagespec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/opencontainers/runc/libcontainer/devices" "github.com/opencontainers/runc/libcontainer/devices"
runtimespec "github.com/opencontainers/runtime-spec/specs-go" runtimespec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate"
"github.com/opencontainers/runtime-tools/validate" "github.com/opencontainers/runtime-tools/validate"
"github.com/opencontainers/selinux/go-selinux/label" "github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors" "github.com/pkg/errors"
...@@ -76,6 +75,7 @@ func init() { ...@@ -76,6 +75,7 @@ func init() {
// CreateContainer creates a new container in the given PodSandbox. // CreateContainer creates a new container in the given PodSandbox.
func (c *criService) CreateContainer(ctx context.Context, r *runtime.CreateContainerRequest) (_ *runtime.CreateContainerResponse, retErr error) { func (c *criService) CreateContainer(ctx context.Context, r *runtime.CreateContainerRequest) (_ *runtime.CreateContainerResponse, retErr error) {
config := r.GetConfig() config := r.GetConfig()
logrus.Debugf("Container config %+v", config)
sandboxConfig := r.GetSandboxConfig() sandboxConfig := r.GetSandboxConfig()
sandbox, err := c.sandboxStore.Get(r.GetPodSandboxId()) sandbox, err := c.sandboxStore.Get(r.GetPodSandboxId())
if err != nil { if err != nil {
...@@ -182,7 +182,7 @@ func (c *criService) CreateContainer(ctx context.Context, r *runtime.CreateConta ...@@ -182,7 +182,7 @@ func (c *criService) CreateContainer(ctx context.Context, r *runtime.CreateConta
if len(volumeMounts) > 0 { if len(volumeMounts) > 0 {
mountMap := make(map[string]string) mountMap := make(map[string]string)
for _, v := range volumeMounts { for _, v := range volumeMounts {
mountMap[v.HostPath] = v.ContainerPath mountMap[filepath.Clean(v.HostPath)] = v.ContainerPath
} }
opts = append(opts, customopts.WithVolumes(mountMap)) opts = append(opts, customopts.WithVolumes(mountMap))
} }
...@@ -191,7 +191,7 @@ func (c *criService) CreateContainer(ctx context.Context, r *runtime.CreateConta ...@@ -191,7 +191,7 @@ func (c *criService) CreateContainer(ctx context.Context, r *runtime.CreateConta
// Get container log path. // Get container log path.
if config.GetLogPath() != "" { if config.GetLogPath() != "" {
meta.LogPath = filepath.Join(sandbox.Config.GetLogDirectory(), config.GetLogPath()) meta.LogPath = filepath.Join(sandboxConfig.GetLogDirectory(), config.GetLogPath())
} }
containerIO, err := cio.NewContainerIO(id, containerIO, err := cio.NewContainerIO(id,
...@@ -335,8 +335,7 @@ func (c *criService) generateContainerSpec(id string, sandboxID string, sandboxP ...@@ -335,8 +335,7 @@ func (c *criService) generateContainerSpec(id string, sandboxID string, sandboxP
// Add HOSTNAME env. // Add HOSTNAME env.
hostname := sandboxConfig.GetHostname() hostname := sandboxConfig.GetHostname()
if sandboxConfig.GetLinux().GetSecurityContext().GetNamespaceOptions().GetNetwork() == runtime.NamespaceMode_NODE && if sandboxConfig.GetHostname() == "" {
hostname == "" {
hostname, err = c.os.Hostname() hostname, err = c.os.Hostname()
if err != nil { if err != nil {
return nil, err return nil, err
...@@ -473,6 +472,22 @@ func (c *criService) generateVolumeMounts(containerRootDir string, criMounts []* ...@@ -473,6 +472,22 @@ func (c *criService) generateVolumeMounts(containerRootDir string, criMounts []*
func (c *criService) generateContainerMounts(sandboxID string, config *runtime.ContainerConfig) []*runtime.Mount { func (c *criService) generateContainerMounts(sandboxID string, config *runtime.ContainerConfig) []*runtime.Mount {
var mounts []*runtime.Mount var mounts []*runtime.Mount
securityContext := config.GetLinux().GetSecurityContext() securityContext := config.GetLinux().GetSecurityContext()
if !isInCRIMounts(etcHostname, config.GetMounts()) {
// /etc/hostname is added since 1.1.6, 1.2.4 and 1.3.
// For in-place upgrade, the old sandbox doesn't have the hostname file,
// do not mount this in that case.
// TODO(random-liu): Remove the check and always mount this when
// containerd 1.1 and 1.2 are deprecated.
hostpath := c.getSandboxHostname(sandboxID)
if _, err := c.os.Stat(hostpath); err == nil {
mounts = append(mounts, &runtime.Mount{
ContainerPath: etcHostname,
HostPath: hostpath,
Readonly: securityContext.GetReadonlyRootfs(),
})
}
}
if !isInCRIMounts(etcHosts, config.GetMounts()) { if !isInCRIMounts(etcHosts, config.GetMounts()) {
mounts = append(mounts, &runtime.Mount{ mounts = append(mounts, &runtime.Mount{
ContainerPath: etcHosts, ContainerPath: etcHosts,
...@@ -507,7 +522,7 @@ func (c *criService) generateContainerMounts(sandboxID string, config *runtime.C ...@@ -507,7 +522,7 @@ func (c *criService) generateContainerMounts(sandboxID string, config *runtime.C
// setOCIProcessArgs sets process args. It returns error if the final arg list // setOCIProcessArgs sets process args. It returns error if the final arg list
// is empty. // is empty.
func setOCIProcessArgs(g *generate.Generator, config *runtime.ContainerConfig, imageConfig *imagespec.ImageConfig) error { func setOCIProcessArgs(g *generator, config *runtime.ContainerConfig, imageConfig *imagespec.ImageConfig) error {
command, args := config.GetCommand(), config.GetArgs() command, args := config.GetCommand(), config.GetArgs()
// The following logic is migrated from https://github.com/moby/moby/blob/master/daemon/commit.go // The following logic is migrated from https://github.com/moby/moby/blob/master/daemon/commit.go
// TODO(random-liu): Clearly define the commands overwrite behavior. // TODO(random-liu): Clearly define the commands overwrite behavior.
...@@ -529,7 +544,7 @@ func setOCIProcessArgs(g *generate.Generator, config *runtime.ContainerConfig, i ...@@ -529,7 +544,7 @@ func setOCIProcessArgs(g *generate.Generator, config *runtime.ContainerConfig, i
// addImageEnvs adds environment variables from image config. It returns error if // addImageEnvs adds environment variables from image config. It returns error if
// an invalid environment variable is encountered. // an invalid environment variable is encountered.
func addImageEnvs(g *generate.Generator, imageEnvs []string) error { func addImageEnvs(g *generator, imageEnvs []string) error {
for _, e := range imageEnvs { for _, e := range imageEnvs {
kv := strings.SplitN(e, "=", 2) kv := strings.SplitN(e, "=", 2)
if len(kv) != 2 { if len(kv) != 2 {
...@@ -540,7 +555,7 @@ func addImageEnvs(g *generate.Generator, imageEnvs []string) error { ...@@ -540,7 +555,7 @@ func addImageEnvs(g *generate.Generator, imageEnvs []string) error {
return nil return nil
} }
func setOCIPrivileged(g *generate.Generator, config *runtime.ContainerConfig) error { func setOCIPrivileged(g *generator, config *runtime.ContainerConfig) error {
// Add all capabilities in privileged mode. // Add all capabilities in privileged mode.
g.SetupPrivileged(true) g.SetupPrivileged(true)
setOCIBindMountsPrivileged(g) setOCIBindMountsPrivileged(g)
...@@ -561,7 +576,7 @@ func clearReadOnly(m *runtimespec.Mount) { ...@@ -561,7 +576,7 @@ func clearReadOnly(m *runtimespec.Mount) {
} }
// addDevices set device mapping without privilege. // addDevices set device mapping without privilege.
func (c *criService) addOCIDevices(g *generate.Generator, devs []*runtime.Device) error { func (c *criService) addOCIDevices(g *generator, devs []*runtime.Device) error {
spec := g.Config spec := g.Config
for _, device := range devs { for _, device := range devs {
path, err := c.os.ResolveSymbolicLink(device.HostPath) path, err := c.os.ResolveSymbolicLink(device.HostPath)
...@@ -593,7 +608,7 @@ func (c *criService) addOCIDevices(g *generate.Generator, devs []*runtime.Device ...@@ -593,7 +608,7 @@ func (c *criService) addOCIDevices(g *generate.Generator, devs []*runtime.Device
} }
// addDevices set device mapping with privilege. // addDevices set device mapping with privilege.
func setOCIDevicesPrivileged(g *generate.Generator) error { func setOCIDevicesPrivileged(g *generator) error {
spec := g.Config spec := g.Config
hostDevices, err := devices.HostDevices() hostDevices, err := devices.HostDevices()
if err != nil { if err != nil {
...@@ -624,7 +639,7 @@ func setOCIDevicesPrivileged(g *generate.Generator) error { ...@@ -624,7 +639,7 @@ func setOCIDevicesPrivileged(g *generate.Generator) error {
} }
// addOCIBindMounts adds bind mounts. // addOCIBindMounts adds bind mounts.
func (c *criService) addOCIBindMounts(g *generate.Generator, mounts []*runtime.Mount, mountLabel string) error { func (c *criService) addOCIBindMounts(g *generator, mounts []*runtime.Mount, mountLabel string) error {
// Sort mounts in number of parts. This ensures that high level mounts don't // Sort mounts in number of parts. This ensures that high level mounts don't
// shadow other mounts. // shadow other mounts.
sort.Sort(orderedMounts(mounts)) sort.Sort(orderedMounts(mounts))
...@@ -729,11 +744,11 @@ func (c *criService) addOCIBindMounts(g *generate.Generator, mounts []*runtime.M ...@@ -729,11 +744,11 @@ func (c *criService) addOCIBindMounts(g *generate.Generator, mounts []*runtime.M
return nil return nil
} }
func setOCIBindMountsPrivileged(g *generate.Generator) { func setOCIBindMountsPrivileged(g *generator) {
spec := g.Config spec := g.Config
// clear readonly for /sys and cgroup // clear readonly for /sys and cgroup
for i, m := range spec.Mounts { for i, m := range spec.Mounts {
if spec.Mounts[i].Destination == "/sys" { if filepath.Clean(spec.Mounts[i].Destination) == "/sys" {
clearReadOnly(&spec.Mounts[i]) clearReadOnly(&spec.Mounts[i])
} }
if m.Type == "cgroup" { if m.Type == "cgroup" {
...@@ -745,7 +760,7 @@ func setOCIBindMountsPrivileged(g *generate.Generator) { ...@@ -745,7 +760,7 @@ func setOCIBindMountsPrivileged(g *generate.Generator) {
} }
// setOCILinuxResource set container resource limit. // setOCILinuxResource set container resource limit.
func setOCILinuxResource(g *generate.Generator, resources *runtime.LinuxContainerResources) { func setOCILinuxResource(g *generator, resources *runtime.LinuxContainerResources) {
if resources == nil { if resources == nil {
return return
} }
...@@ -771,7 +786,7 @@ func getOCICapabilitiesList() []string { ...@@ -771,7 +786,7 @@ func getOCICapabilitiesList() []string {
} }
// Adds capabilities to all sets relevant to root (bounding, permitted, effective, inheritable) // Adds capabilities to all sets relevant to root (bounding, permitted, effective, inheritable)
func addProcessRootCapability(g *generate.Generator, c string) error { func addProcessRootCapability(g *generator, c string) error {
if err := g.AddProcessCapabilityBounding(c); err != nil { if err := g.AddProcessCapabilityBounding(c); err != nil {
return err return err
} }
...@@ -788,7 +803,7 @@ func addProcessRootCapability(g *generate.Generator, c string) error { ...@@ -788,7 +803,7 @@ func addProcessRootCapability(g *generate.Generator, c string) error {
} }
// Drops capabilities to all sets relevant to root (bounding, permitted, effective, inheritable) // Drops capabilities to all sets relevant to root (bounding, permitted, effective, inheritable)
func dropProcessRootCapability(g *generate.Generator, c string) error { func dropProcessRootCapability(g *generator, c string) error {
if err := g.DropProcessCapabilityBounding(c); err != nil { if err := g.DropProcessCapabilityBounding(c); err != nil {
return err return err
} }
...@@ -805,7 +820,7 @@ func dropProcessRootCapability(g *generate.Generator, c string) error { ...@@ -805,7 +820,7 @@ func dropProcessRootCapability(g *generate.Generator, c string) error {
} }
// setOCICapabilities adds/drops process capabilities. // setOCICapabilities adds/drops process capabilities.
func setOCICapabilities(g *generate.Generator, capabilities *runtime.Capability) error { func setOCICapabilities(g *generator, capabilities *runtime.Capability) error {
if capabilities == nil { if capabilities == nil {
return nil return nil
} }
...@@ -851,7 +866,7 @@ func setOCICapabilities(g *generate.Generator, capabilities *runtime.Capability) ...@@ -851,7 +866,7 @@ func setOCICapabilities(g *generate.Generator, capabilities *runtime.Capability)
} }
// setOCINamespaces sets namespaces. // setOCINamespaces sets namespaces.
func setOCINamespaces(g *generate.Generator, namespaces *runtime.NamespaceOption, sandboxPid uint32) { func setOCINamespaces(g *generator, namespaces *runtime.NamespaceOption, sandboxPid uint32) {
g.AddOrReplaceLinuxNamespace(string(runtimespec.NetworkNamespace), getNetworkNamespace(sandboxPid)) // nolint: errcheck g.AddOrReplaceLinuxNamespace(string(runtimespec.NetworkNamespace), getNetworkNamespace(sandboxPid)) // nolint: errcheck
g.AddOrReplaceLinuxNamespace(string(runtimespec.IPCNamespace), getIPCNamespace(sandboxPid)) // nolint: errcheck g.AddOrReplaceLinuxNamespace(string(runtimespec.IPCNamespace), getIPCNamespace(sandboxPid)) // nolint: errcheck
g.AddOrReplaceLinuxNamespace(string(runtimespec.UTSNamespace), getUTSNamespace(sandboxPid)) // nolint: errcheck g.AddOrReplaceLinuxNamespace(string(runtimespec.UTSNamespace), getUTSNamespace(sandboxPid)) // nolint: errcheck
...@@ -874,7 +889,7 @@ func defaultRuntimeSpec(id string) (*runtimespec.Spec, error) { ...@@ -874,7 +889,7 @@ func defaultRuntimeSpec(id string) (*runtimespec.Spec, error) {
// TODO(random-liu): Mount tmpfs for /run and handle copy-up. // TODO(random-liu): Mount tmpfs for /run and handle copy-up.
var mounts []runtimespec.Mount var mounts []runtimespec.Mount
for _, mount := range spec.Mounts { for _, mount := range spec.Mounts {
if mount.Destination == "/run" { if filepath.Clean(mount.Destination) == "/run" {
continue continue
} }
mounts = append(mounts, mount) mounts = append(mounts, mount)
......
...@@ -143,8 +143,9 @@ func (c *criService) stopContainer(ctx context.Context, container containerstore ...@@ -143,8 +143,9 @@ func (c *criService) stopContainer(ctx context.Context, container containerstore
return errors.Wrapf(err, "failed to stop container %q", id) return errors.Wrapf(err, "failed to stop container %q", id)
} }
if err = c.waitContainerStop(ctx, container, timeout); err == nil { if err = c.waitContainerStop(ctx, container, timeout); err == nil || errors.Cause(err) == ctx.Err() {
return nil // Do not SIGKILL container if the context is cancelled.
return err
} }
logrus.WithError(err).Errorf("An error occurs during waiting for container %q to be stopped", id) logrus.WithError(err).Errorf("An error occurs during waiting for container %q to be stopped", id)
} }
...@@ -167,7 +168,7 @@ func (c *criService) waitContainerStop(ctx context.Context, container containers ...@@ -167,7 +168,7 @@ func (c *criService) waitContainerStop(ctx context.Context, container containers
defer timeoutTimer.Stop() defer timeoutTimer.Stop()
select { select {
case <-ctx.Done(): case <-ctx.Done():
return errors.Errorf("wait container %q is cancelled", container.ID) return errors.Wrapf(ctx.Err(), "wait container %q is cancelled", container.ID)
case <-timeoutTimer.C: case <-timeoutTimer.C:
return errors.Errorf("wait container %q stop timeout", container.ID) return errors.Errorf("wait container %q stop timeout", container.ID)
case <-container.Stopped(): case <-container.Stopped():
......
...@@ -37,7 +37,6 @@ import ( ...@@ -37,7 +37,6 @@ import (
imagedigest "github.com/opencontainers/go-digest" imagedigest "github.com/opencontainers/go-digest"
runtimespec "github.com/opencontainers/runtime-spec/specs-go" runtimespec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate" "github.com/opencontainers/runtime-tools/generate"
"github.com/opencontainers/selinux/go-selinux"
"github.com/opencontainers/selinux/go-selinux/label" "github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors" "github.com/pkg/errors"
"golang.org/x/net/context" "golang.org/x/net/context"
...@@ -97,6 +96,8 @@ const ( ...@@ -97,6 +96,8 @@ const (
devShm = "/dev/shm" devShm = "/dev/shm"
// etcHosts is the default path of /etc/hosts file. // etcHosts is the default path of /etc/hosts file.
etcHosts = "/etc/hosts" etcHosts = "/etc/hosts"
// etcHostname is the default path of /etc/hostname file.
etcHostname = "/etc/hostname"
// resolvConfPath is the abs path of resolv.conf on host or container. // resolvConfPath is the abs path of resolv.conf on host or container.
resolvConfPath = "/etc/resolv.conf" resolvConfPath = "/etc/resolv.conf"
// hostnameEnv is the key for HOSTNAME env. // hostnameEnv is the key for HOSTNAME env.
...@@ -197,6 +198,11 @@ func (c *criService) getVolatileContainerRootDir(id string) string { ...@@ -197,6 +198,11 @@ func (c *criService) getVolatileContainerRootDir(id string) string {
return filepath.Join(c.config.StateDir, containersDir, id) return filepath.Join(c.config.StateDir, containersDir, id)
} }
// getSandboxHostname returns the hostname file path inside the sandbox root directory.
func (c *criService) getSandboxHostname(id string) string {
return filepath.Join(c.getSandboxRootDir(id), "hostname")
}
// getSandboxHosts returns the hosts file path inside the sandbox root directory. // getSandboxHosts returns the hosts file path inside the sandbox root directory.
func (c *criService) getSandboxHosts(id string) string { func (c *criService) getSandboxHosts(id string) string {
return filepath.Join(c.getSandboxRootDir(id), "hosts") return filepath.Join(c.getSandboxRootDir(id), "hosts")
...@@ -348,7 +354,12 @@ func initSelinuxOpts(selinuxOpt *runtime.SELinuxOption) (string, string, error) ...@@ -348,7 +354,12 @@ func initSelinuxOpts(selinuxOpt *runtime.SELinuxOption) (string, string, error)
selinuxOpt.GetRole(), selinuxOpt.GetRole(),
selinuxOpt.GetType(), selinuxOpt.GetType(),
selinuxOpt.GetLevel()) selinuxOpt.GetLevel())
return label.InitLabels(selinux.DupSecOpt(labelOpts))
options, err := label.DupSecOpt(labelOpts)
if err != nil {
return "", "", err
}
return label.InitLabels(options)
} }
func checkSelinuxLevel(level string) (bool, error) { func checkSelinuxLevel(level string) (bool, error) {
...@@ -366,7 +377,7 @@ func checkSelinuxLevel(level string) (bool, error) { ...@@ -366,7 +377,7 @@ func checkSelinuxLevel(level string) (bool, error) {
// isInCRIMounts checks whether a destination is in CRI mount list. // isInCRIMounts checks whether a destination is in CRI mount list.
func isInCRIMounts(dst string, mounts []*runtime.Mount) bool { func isInCRIMounts(dst string, mounts []*runtime.Mount) bool {
for _, m := range mounts { for _, m := range mounts {
if m.ContainerPath == dst { if filepath.Clean(m.ContainerPath) == filepath.Clean(dst) {
return true return true
} }
} }
...@@ -390,10 +401,51 @@ func buildLabels(configLabels map[string]string, containerType string) map[strin ...@@ -390,10 +401,51 @@ func buildLabels(configLabels map[string]string, containerType string) map[strin
} }
// newSpecGenerator creates a new spec generator for the runtime spec. // newSpecGenerator creates a new spec generator for the runtime spec.
func newSpecGenerator(spec *runtimespec.Spec) generate.Generator { func newSpecGenerator(spec *runtimespec.Spec) generator {
g := generate.NewFromSpec(spec) g := generate.NewFromSpec(spec)
g.HostSpecific = true g.HostSpecific = true
return g return newCustomGenerator(g)
}
// generator is a custom generator with some functions overridden
// used by the cri plugin.
// TODO(random-liu): Upstream this fix.
type generator struct {
generate.Generator
envCache map[string]int
}
func newCustomGenerator(g generate.Generator) generator {
cg := generator{
Generator: g,
envCache: make(map[string]int),
}
if g.Config != nil && g.Config.Process != nil {
for i, env := range g.Config.Process.Env {
kv := strings.SplitN(env, "=", 2)
cg.envCache[kv[0]] = i
}
}
return cg
}
// AddProcessEnv overrides the original AddProcessEnv. It uses
// a map to cache and override envs.
func (g *generator) AddProcessEnv(key, value string) {
if len(g.envCache) == 0 {
// Call AddProccessEnv once to initialize the spec.
g.Generator.AddProcessEnv(key, value)
g.envCache[key] = 0
return
}
spec := g.Config
env := fmt.Sprintf("%s=%s", key, value)
if idx, ok := g.envCache[key]; !ok {
spec.Process.Env = append(spec.Process.Env, env)
g.envCache[key] = len(spec.Process.Env) - 1
} else {
spec.Process.Env[idx] = env
}
} }
func getPodCNILabels(id string, config *runtime.PodSandboxConfig) map[string]string { func getPodCNILabels(id string, config *runtime.PodSandboxConfig) map[string]string {
......
...@@ -52,7 +52,7 @@ func (in *instrumentedService) RunPodSandbox(ctx context.Context, r *runtime.Run ...@@ -52,7 +52,7 @@ func (in *instrumentedService) RunPodSandbox(ctx context.Context, r *runtime.Run
if err := in.checkInitialized(); err != nil { if err := in.checkInitialized(); err != nil {
return nil, err return nil, err
} }
logrus.Infof("RunPodSandbox with config %+v", r.GetConfig()) logrus.Infof("RunPodsandbox for %+v", r.GetConfig().GetMetadata())
defer func() { defer func() {
if err != nil { if err != nil {
logrus.WithError(err).Errorf("RunPodSandbox for %+v failed, error", r.GetConfig().GetMetadata()) logrus.WithError(err).Errorf("RunPodSandbox for %+v failed, error", r.GetConfig().GetMetadata())
...@@ -142,8 +142,8 @@ func (in *instrumentedService) CreateContainer(ctx context.Context, r *runtime.C ...@@ -142,8 +142,8 @@ func (in *instrumentedService) CreateContainer(ctx context.Context, r *runtime.C
if err := in.checkInitialized(); err != nil { if err := in.checkInitialized(); err != nil {
return nil, err return nil, err
} }
logrus.Infof("CreateContainer within sandbox %q with container config %+v and sandbox config %+v", logrus.Infof("CreateContainer within sandbox %q for container %+v",
r.GetPodSandboxId(), r.GetConfig(), r.GetSandboxConfig()) r.GetPodSandboxId(), r.GetConfig().GetMetadata())
defer func() { defer func() {
if err != nil { if err != nil {
logrus.WithError(err).Errorf("CreateContainer within sandbox %q for %+v failed", logrus.WithError(err).Errorf("CreateContainer within sandbox %q for %+v failed",
......
...@@ -27,6 +27,7 @@ import ( ...@@ -27,6 +27,7 @@ import (
"github.com/containerd/containerd/oci" "github.com/containerd/containerd/oci"
cni "github.com/containerd/go-cni" cni "github.com/containerd/go-cni"
"github.com/containerd/typeurl" "github.com/containerd/typeurl"
"github.com/davecgh/go-spew/spew"
imagespec "github.com/opencontainers/image-spec/specs-go/v1" imagespec "github.com/opencontainers/image-spec/specs-go/v1"
runtimespec "github.com/opencontainers/runtime-spec/specs-go" runtimespec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors" "github.com/pkg/errors"
...@@ -54,6 +55,7 @@ func init() { ...@@ -54,6 +55,7 @@ func init() {
// the sandbox is in ready state. // the sandbox is in ready state.
func (c *criService) RunPodSandbox(ctx context.Context, r *runtime.RunPodSandboxRequest) (_ *runtime.RunPodSandboxResponse, retErr error) { func (c *criService) RunPodSandbox(ctx context.Context, r *runtime.RunPodSandboxRequest) (_ *runtime.RunPodSandboxResponse, retErr error) {
config := r.GetConfig() config := r.GetConfig()
logrus.Debugf("Sandbox config %+v", config)
// Generate unique id and name for the sandbox and reserve the name. // Generate unique id and name for the sandbox and reserve the name.
id := util.GenerateID() id := util.GenerateID()
...@@ -143,7 +145,7 @@ func (c *criService) RunPodSandbox(ctx context.Context, r *runtime.RunPodSandbox ...@@ -143,7 +145,7 @@ func (c *criService) RunPodSandbox(ctx context.Context, r *runtime.RunPodSandbox
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to generate sandbox container spec") return nil, errors.Wrap(err, "failed to generate sandbox container spec")
} }
logrus.Debugf("Sandbox container spec: %+v", spec) logrus.Debugf("Sandbox container %q spec: %#+v", id, spew.NewFormatter(spec))
var specOpts []oci.SpecOpts var specOpts []oci.SpecOpts
userstr, err := generateUserString( userstr, err := generateUserString(
...@@ -227,7 +229,7 @@ func (c *criService) RunPodSandbox(ctx context.Context, r *runtime.RunPodSandbox ...@@ -227,7 +229,7 @@ func (c *criService) RunPodSandbox(ctx context.Context, r *runtime.RunPodSandbox
} }
}() }()
// Setup sandbox /dev/shm, /etc/hosts and /etc/resolv.conf. // Setup sandbox /dev/shm, /etc/hosts, /etc/resolv.conf and /etc/hostname.
if err = c.setupSandboxFiles(id, config); err != nil { if err = c.setupSandboxFiles(id, config); err != nil {
return nil, errors.Wrapf(err, "failed to setup sandbox files") return nil, errors.Wrapf(err, "failed to setup sandbox files")
} }
...@@ -383,6 +385,7 @@ func (c *criService) generateSandboxContainerSpec(id string, config *runtime.Pod ...@@ -383,6 +385,7 @@ func (c *criService) generateSandboxContainerSpec(id string, config *runtime.Pod
nsOptions := securityContext.GetNamespaceOptions() nsOptions := securityContext.GetNamespaceOptions()
if nsOptions.GetNetwork() == runtime.NamespaceMode_NODE { if nsOptions.GetNetwork() == runtime.NamespaceMode_NODE {
g.RemoveLinuxNamespace(string(runtimespec.NetworkNamespace)) // nolint: errcheck g.RemoveLinuxNamespace(string(runtimespec.NetworkNamespace)) // nolint: errcheck
g.RemoveLinuxNamespace(string(runtimespec.UTSNamespace)) // nolint: errcheck
} else { } else {
//TODO(Abhi): May be move this to containerd spec opts (WithLinuxSpaceOption) //TODO(Abhi): May be move this to containerd spec opts (WithLinuxSpaceOption)
g.AddOrReplaceLinuxNamespace(string(runtimespec.NetworkNamespace), nsPath) // nolint: errcheck g.AddOrReplaceLinuxNamespace(string(runtimespec.NetworkNamespace), nsPath) // nolint: errcheck
...@@ -433,13 +436,27 @@ func (c *criService) generateSandboxContainerSpec(id string, config *runtime.Pod ...@@ -433,13 +436,27 @@ func (c *criService) generateSandboxContainerSpec(id string, config *runtime.Pod
g.AddAnnotation(annotations.ContainerType, annotations.ContainerTypeSandbox) g.AddAnnotation(annotations.ContainerType, annotations.ContainerTypeSandbox)
g.AddAnnotation(annotations.SandboxID, id) g.AddAnnotation(annotations.SandboxID, id)
g.AddAnnotation(annotations.SandboxLogDir, config.GetLogDirectory())
return g.Config, nil return g.Config, nil
} }
// setupSandboxFiles sets up necessary sandbox files including /dev/shm, /etc/hosts // setupSandboxFiles sets up necessary sandbox files including /dev/shm, /etc/hosts,
// and /etc/resolv.conf. // /etc/resolv.conf and /etc/hostname.
func (c *criService) setupSandboxFiles(id string, config *runtime.PodSandboxConfig) error { func (c *criService) setupSandboxFiles(id string, config *runtime.PodSandboxConfig) error {
sandboxEtcHostname := c.getSandboxHostname(id)
hostname := config.GetHostname()
if hostname == "" {
var err error
hostname, err = c.os.Hostname()
if err != nil {
return errors.Wrap(err, "failed to get hostname")
}
}
if err := c.os.WriteFile(sandboxEtcHostname, []byte(hostname+"\n"), 0644); err != nil {
return errors.Wrapf(err, "failed to write hostname to %q", sandboxEtcHostname)
}
// TODO(random-liu): Consider whether we should maintain /etc/hosts and /etc/resolv.conf in kubelet. // TODO(random-liu): Consider whether we should maintain /etc/hosts and /etc/resolv.conf in kubelet.
sandboxEtcHosts := c.getSandboxHosts(id) sandboxEtcHosts := c.getSandboxHosts(id)
if err := c.os.CopyFile(etcHosts, sandboxEtcHosts, 0644); err != nil { if err := c.os.CopyFile(etcHosts, sandboxEtcHosts, 0644); err != nil {
......
...@@ -144,7 +144,7 @@ func (c *criService) waitSandboxStop(ctx context.Context, sandbox sandboxstore.S ...@@ -144,7 +144,7 @@ func (c *criService) waitSandboxStop(ctx context.Context, sandbox sandboxstore.S
defer timeoutTimer.Stop() defer timeoutTimer.Stop()
select { select {
case <-ctx.Done(): case <-ctx.Done():
return errors.Errorf("wait sandbox container %q is cancelled", sandbox.ID) return errors.Wrapf(ctx.Err(), "wait sandbox container %q is cancelled", sandbox.ID)
case <-timeoutTimer.C: case <-timeoutTimer.C:
return errors.Errorf("wait sandbox container %q stop timeout", sandbox.ID) return errors.Errorf("wait sandbox container %q stop timeout", sandbox.ID)
case <-sandbox.Stopped(): case <-sandbox.Stopped():
......
github.com/beorn7/perks 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9 github.com/beorn7/perks 4c0e84591b9aa9e6dcfdf3e020114cd81f89d5f9
github.com/blang/semver v3.1.0 github.com/blang/semver v3.1.0
github.com/BurntSushi/toml a368813c5e648fee92e5f6c30e3944ff9d5e8895 github.com/BurntSushi/toml a368813c5e648fee92e5f6c30e3944ff9d5e8895
github.com/containerd/cgroups 5e610833b72089b37d0e615de9a92dfc043757c2 github.com/containerd/cgroups dbea6f2bd41658b84b00417ceefa416b979cbf10
github.com/containerd/console c12b1e7919c14469339a5d38f2f8ed9b64a9de23 github.com/containerd/console c12b1e7919c14469339a5d38f2f8ed9b64a9de23
github.com/containerd/containerd 0137339c8c1d55de5545ffdd723199dfba27cb24 github.com/containerd/containerd v1.2.5
github.com/containerd/continuity bd77b46c8352f74eb12c85bdc01f4b90f69d66b4 github.com/containerd/continuity bd77b46c8352f74eb12c85bdc01f4b90f69d66b4
github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c github.com/containerd/fifo 3d5202aec260678c48179c56f40e6f38a095738c
github.com/containerd/go-cni 40bcf8ec8acd7372be1d77031d585d5d8e561c90 github.com/containerd/go-cni 40bcf8ec8acd7372be1d77031d585d5d8e561c90
...@@ -11,10 +11,10 @@ github.com/containerd/go-runc 5a6d9f37cfa36b15efba46dc7ea349fa9b7143c3 ...@@ -11,10 +11,10 @@ github.com/containerd/go-runc 5a6d9f37cfa36b15efba46dc7ea349fa9b7143c3
github.com/containerd/ttrpc 2a805f71863501300ae1976d29f0454ae003e85a github.com/containerd/ttrpc 2a805f71863501300ae1976d29f0454ae003e85a
github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40 github.com/containerd/typeurl a93fcdb778cd272c6e9b3028b2f42d813e785d40
github.com/containernetworking/cni v0.6.0 github.com/containernetworking/cni v0.6.0
github.com/containernetworking/plugins v0.7.0 github.com/containernetworking/plugins v0.7.5
github.com/coreos/go-systemd v14 github.com/coreos/go-systemd v14
github.com/davecgh/go-spew v1.1.0 github.com/davecgh/go-spew v1.1.0
github.com/docker/distribution b38e5838b7b2f2ad48e06ec4b500011976080621 github.com/docker/distribution 0d3efadf0154c2b8a4e7b6621fff9809655cc580
github.com/docker/docker 86f080cff0914e9694068ed78d503701667c4c00 github.com/docker/docker 86f080cff0914e9694068ed78d503701667c4c00
github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9 github.com/docker/go-events 9461782956ad83b30282bf90e31fa6a70c255ba9
github.com/docker/go-metrics 4ea375f7759c82740c893fc030bc37088d2ec098 github.com/docker/go-metrics 4ea375f7759c82740c893fc030bc37088d2ec098
...@@ -39,10 +39,10 @@ github.com/modern-go/concurrent 1.0.3 ...@@ -39,10 +39,10 @@ github.com/modern-go/concurrent 1.0.3
github.com/modern-go/reflect2 1.0.1 github.com/modern-go/reflect2 1.0.1
github.com/opencontainers/go-digest c9281466c8b2f606084ac71339773efd177436e7 github.com/opencontainers/go-digest c9281466c8b2f606084ac71339773efd177436e7
github.com/opencontainers/image-spec v1.0.1 github.com/opencontainers/image-spec v1.0.1
github.com/opencontainers/runc 12f6a991201fdb8f82579582d5e00e28fba06d0a github.com/opencontainers/runc 2b18fe1d885ee5083ef9f0838fee39b62d653e30
github.com/opencontainers/runtime-spec eba862dc2470385a233c7507392675cbeadf7353 github.com/opencontainers/runtime-spec eba862dc2470385a233c7507392675cbeadf7353
github.com/opencontainers/runtime-tools v0.6.0 github.com/opencontainers/runtime-tools v0.6.0
github.com/opencontainers/selinux b6fa367ed7f534f9ba25391cc2d467085dbb445a github.com/opencontainers/selinux v1.2.1
github.com/pkg/errors v0.8.0 github.com/pkg/errors v0.8.0
github.com/pmezard/go-difflib v1.0.0 github.com/pmezard/go-difflib v1.0.0
github.com/prometheus/client_golang f4fb1b73fb099f396a7f0036bf86aa8def4ed823 github.com/prometheus/client_golang f4fb1b73fb099f396a7f0036bf86aa8def4ed823
...@@ -63,7 +63,7 @@ golang.org/x/crypto 49796115aa4b964c318aad4f3084fdb41e9aa067 ...@@ -63,7 +63,7 @@ golang.org/x/crypto 49796115aa4b964c318aad4f3084fdb41e9aa067
golang.org/x/net b3756b4b77d7b13260a0a2ec658753cf48922eac golang.org/x/net b3756b4b77d7b13260a0a2ec658753cf48922eac
golang.org/x/oauth2 a6bd8cefa1811bd24b86f8902872e4e8225f74c4 golang.org/x/oauth2 a6bd8cefa1811bd24b86f8902872e4e8225f74c4
golang.org/x/sync 450f422ab23cf9881c94e2db30cac0eb1b7cf80c golang.org/x/sync 450f422ab23cf9881c94e2db30cac0eb1b7cf80c
golang.org/x/sys 1b2967e3c290b7c545b3db0deeda16e9be4f98a2 https://github.com/golang/sys golang.org/x/sys 41f3e6584952bb034a481797859f6ab34b6803bd https://github.com/golang/sys
golang.org/x/text 19e51611da83d6be54ddafce4a4af510cb3e9ea4 golang.org/x/text 19e51611da83d6be54ddafce4a4af510cb3e9ea4
golang.org/x/time f51c12702a4d776e4c1fa9b0fabab841babae631 golang.org/x/time f51c12702a4d776e4c1fa9b0fabab841babae631
google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944 google.golang.org/genproto d80a6e20e776b0b17a324d0ba1ab50a39c8e8944
......
# Changelog # Changelog
### 0.33.0 (2019-02-26)
- Add --raw_cgroup_prefix_whitelist flag to allow configuring which raw cgroup trees cAdvisor monitors
- Replace `du` and `find` with a golang implementation
- Periodically update MachineInfo to support hot-add/remove
- Add explicit timestamps to prometheus metrics to fix rate calculations
- Add --url_base_prefix flag to provide better support for reverse proxies
- Add --white_listed_container_labels flag to allow specifying the container labels added as prometheus labels
### 0.32.0 (2018-11-12) ### 0.32.0 (2018-11-12)
- Add container process and file descriptor metrics (disabled by default) - Add container process and file descriptor metrics (disabled by default)
- Rename `type` label to `failure_type` for prometheus `memory_failures_total` metric - Rename `type` label to `failure_type` for prometheus `memory_failures_total` metric
......
...@@ -51,7 +51,6 @@ type realFsHandler struct { ...@@ -51,7 +51,6 @@ type realFsHandler struct {
} }
const ( const (
timeout = 2 * time.Minute
maxBackoffFactor = 20 maxBackoffFactor = 20
) )
...@@ -74,17 +73,16 @@ func NewFsHandler(period time.Duration, rootfs, extraDir string, fsInfo fs.FsInf ...@@ -74,17 +73,16 @@ func NewFsHandler(period time.Duration, rootfs, extraDir string, fsInfo fs.FsInf
func (fh *realFsHandler) update() error { func (fh *realFsHandler) update() error {
var ( var (
baseUsage, extraDirUsage, inodeUsage uint64 rootUsage, extraUsage fs.UsageInfo
rootDiskErr, rootInodeErr, extraDiskErr error rootErr, extraErr error
) )
// TODO(vishh): Add support for external mounts. // TODO(vishh): Add support for external mounts.
if fh.rootfs != "" { if fh.rootfs != "" {
baseUsage, rootDiskErr = fh.fsInfo.GetDirDiskUsage(fh.rootfs, timeout) rootUsage, rootErr = fh.fsInfo.GetDirUsage(fh.rootfs)
inodeUsage, rootInodeErr = fh.fsInfo.GetDirInodeUsage(fh.rootfs, timeout)
} }
if fh.extraDir != "" { if fh.extraDir != "" {
extraDirUsage, extraDiskErr = fh.fsInfo.GetDirDiskUsage(fh.extraDir, timeout) extraUsage, extraErr = fh.fsInfo.GetDirUsage(fh.extraDir)
} }
// Wait to handle errors until after all operartions are run. // Wait to handle errors until after all operartions are run.
...@@ -92,18 +90,17 @@ func (fh *realFsHandler) update() error { ...@@ -92,18 +90,17 @@ func (fh *realFsHandler) update() error {
fh.Lock() fh.Lock()
defer fh.Unlock() defer fh.Unlock()
fh.lastUpdate = time.Now() fh.lastUpdate = time.Now()
if rootInodeErr == nil && fh.rootfs != "" { if fh.rootfs != "" && rootErr == nil {
fh.usage.InodeUsage = inodeUsage fh.usage.InodeUsage = rootUsage.Inodes
fh.usage.TotalUsageBytes = rootUsage.Bytes + extraUsage.Bytes
} }
if rootDiskErr == nil && fh.rootfs != "" { if fh.extraDir != "" && extraErr == nil {
fh.usage.TotalUsageBytes = baseUsage + extraDirUsage fh.usage.BaseUsageBytes = rootUsage.Bytes
}
if extraDiskErr == nil && fh.extraDir != "" {
fh.usage.BaseUsageBytes = baseUsage
} }
// Combine errors into a single error to return // Combine errors into a single error to return
if rootDiskErr != nil || rootInodeErr != nil || extraDiskErr != nil { if rootErr != nil || extraErr != nil {
return fmt.Errorf("rootDiskErr: %v, rootInodeErr: %v, extraDiskErr: %v", rootDiskErr, rootInodeErr, extraDiskErr) return fmt.Errorf("rootDiskErr: %v, extraDiskErr: %v", rootErr, extraErr)
} }
return nil return nil
} }
...@@ -132,7 +129,7 @@ func (fh *realFsHandler) trackUsage() { ...@@ -132,7 +129,7 @@ func (fh *realFsHandler) trackUsage() {
// if the long duration is persistent either because of slow // if the long duration is persistent either because of slow
// disk or lots of containers. // disk or lots of containers.
longOp = longOp + time.Second longOp = longOp + time.Second
klog.V(2).Infof("du and find on following dirs took %v: %v; will not log again for this container unless duration exceeds %v", duration, []string{fh.rootfs, fh.extraDir}, longOp) klog.V(2).Infof("fs: disk usage and inodes count on following dirs took %v: %v; will not log again for this container unless duration exceeds %v", duration, []string{fh.rootfs, fh.extraDir}, longOp)
} }
} }
} }
......
...@@ -134,7 +134,7 @@ func readString(dirpath string, file string) string { ...@@ -134,7 +134,7 @@ func readString(dirpath string, file string) string {
if err != nil { if err != nil {
// Ignore non-existent files // Ignore non-existent files
if !os.IsNotExist(err) { if !os.IsNotExist(err) {
klog.Errorf("readString: Failed to read %q: %s", cgroupFile, err) klog.Warningf("readString: Failed to read %q: %s", cgroupFile, err)
} }
return "" return ""
} }
......
...@@ -128,7 +128,7 @@ func Register(factory info.MachineInfoFactory, fsInfo fs.FsInfo, includedMetrics ...@@ -128,7 +128,7 @@ func Register(factory info.MachineInfoFactory, fsInfo fs.FsInfo, includedMetrics
return fmt.Errorf("failed to fetch containerd client version: %v", err) return fmt.Errorf("failed to fetch containerd client version: %v", err)
} }
cgroupSubsystems, err := libcontainer.GetCgroupSubsystems() cgroupSubsystems, err := libcontainer.GetCgroupSubsystems(includedMetrics)
if err != nil { if err != nil {
return fmt.Errorf("failed to get cgroup subsystems: %v", err) return fmt.Errorf("failed to get cgroup subsystems: %v", err)
} }
......
...@@ -18,7 +18,6 @@ package containerd ...@@ -18,7 +18,6 @@ package containerd
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"path"
"strings" "strings"
"time" "time"
...@@ -67,10 +66,7 @@ func newContainerdContainerHandler( ...@@ -67,10 +66,7 @@ func newContainerdContainerHandler(
includedMetrics container.MetricSet, includedMetrics container.MetricSet,
) (container.ContainerHandler, error) { ) (container.ContainerHandler, error) {
// Create the cgroup paths. // Create the cgroup paths.
cgroupPaths := make(map[string]string, len(cgroupSubsystems.MountPoints)) cgroupPaths := common.MakeCgroupPaths(cgroupSubsystems.MountPoints, name)
for key, val := range cgroupSubsystems.MountPoints {
cgroupPaths[key] = path.Join(val, name)
}
// Generate the equivalent cgroup manager for this container. // Generate the equivalent cgroup manager for this container.
cgroupManager := &cgroupfs.Manager{ cgroupManager := &cgroupfs.Manager{
......
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package crio
import (
"encoding/json"
"fmt"
"net"
"net/http"
"syscall"
"time"
)
const (
CrioSocket = "/var/run/crio/crio.sock"
maxUnixSocketPathSize = len(syscall.RawSockaddrUnix{}.Path)
)
// Info represents CRI-O information as sent by the CRI-O server
type Info struct {
StorageDriver string `json:"storage_driver"`
StorageRoot string `json:"storage_root"`
}
// ContainerInfo represents a given container information
type ContainerInfo struct {
Name string `json:"name"`
Pid int `json:"pid"`
Image string `json:"image"`
CreatedTime int64 `json:"created_time"`
Labels map[string]string `json:"labels"`
Annotations map[string]string `json:"annotations"`
LogPath string `json:"log_path"`
Root string `json:"root"`
IP string `json:"ip_address"`
}
type crioClient interface {
Info() (Info, error)
ContainerInfo(string) (*ContainerInfo, error)
}
type crioClientImpl struct {
client *http.Client
}
func configureUnixTransport(tr *http.Transport, proto, addr string) error {
if len(addr) > maxUnixSocketPathSize {
return fmt.Errorf("Unix socket path %q is too long", addr)
}
// No need for compression in local communications.
tr.DisableCompression = true
tr.Dial = func(_, _ string) (net.Conn, error) {
return net.DialTimeout(proto, addr, 32*time.Second)
}
return nil
}
// Client returns a new configured CRI-O client
func Client() (crioClient, error) {
tr := new(http.Transport)
configureUnixTransport(tr, "unix", CrioSocket)
c := &http.Client{
Transport: tr,
}
return &crioClientImpl{
client: c,
}, nil
}
func getRequest(path string) (*http.Request, error) {
req, err := http.NewRequest("GET", path, nil)
if err != nil {
return nil, err
}
// For local communications over a unix socket, it doesn't matter what
// the host is. We just need a valid and meaningful host name.
req.Host = "crio"
req.URL.Host = CrioSocket
req.URL.Scheme = "http"
return req, nil
}
// Info returns generic info from the CRI-O server
func (c *crioClientImpl) Info() (Info, error) {
info := Info{}
req, err := getRequest("/info")
if err != nil {
return info, err
}
resp, err := c.client.Do(req)
if err != nil {
return info, err
}
defer resp.Body.Close()
if err := json.NewDecoder(resp.Body).Decode(&info); err != nil {
return info, err
}
return info, nil
}
// ContainerInfo returns information about a given container
func (c *crioClientImpl) ContainerInfo(id string) (*ContainerInfo, error) {
req, err := getRequest("/containers/" + id)
if err != nil {
return nil, err
}
resp, err := c.client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
cInfo := ContainerInfo{}
if err := json.NewDecoder(resp.Body).Decode(&cInfo); err != nil {
return nil, err
}
return &cInfo, nil
}
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package crio
import (
"fmt"
"path"
"regexp"
"strings"
"github.com/google/cadvisor/container"
"github.com/google/cadvisor/container/libcontainer"
"github.com/google/cadvisor/fs"
info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/manager/watcher"
"k8s.io/klog"
)
// The namespace under which crio aliases are unique.
const CrioNamespace = "crio"
// Regexp that identifies CRI-O cgroups
var crioCgroupRegexp = regexp.MustCompile(`([a-z0-9]{64})`)
type storageDriver string
const (
// TODO add full set of supported drivers in future..
overlayStorageDriver storageDriver = "overlay"
overlay2StorageDriver storageDriver = "overlay2"
)
type crioFactory struct {
machineInfoFactory info.MachineInfoFactory
storageDriver storageDriver
storageDir string
// Information about the mounted cgroup subsystems.
cgroupSubsystems libcontainer.CgroupSubsystems
// Information about mounted filesystems.
fsInfo fs.FsInfo
includedMetrics container.MetricSet
client crioClient
}
func (self *crioFactory) String() string {
return CrioNamespace
}
func (self *crioFactory) NewContainerHandler(name string, inHostNamespace bool) (handler container.ContainerHandler, err error) {
client, err := Client()
if err != nil {
return
}
// TODO are there any env vars we need to white list, if so, do it here...
metadataEnvs := []string{}
handler, err = newCrioContainerHandler(
client,
name,
self.machineInfoFactory,
self.fsInfo,
self.storageDriver,
self.storageDir,
&self.cgroupSubsystems,
inHostNamespace,
metadataEnvs,
self.includedMetrics,
)
return
}
// Returns the CRIO ID from the full container name.
func ContainerNameToCrioId(name string) string {
id := path.Base(name)
if matches := crioCgroupRegexp.FindStringSubmatch(id); matches != nil {
return matches[1]
}
return id
}
// isContainerName returns true if the cgroup with associated name
// corresponds to a crio container.
func isContainerName(name string) bool {
// always ignore .mount cgroup even if associated with crio and delegate to systemd
if strings.HasSuffix(name, ".mount") {
return false
}
return crioCgroupRegexp.MatchString(path.Base(name))
}
// crio handles all containers under /crio
func (self *crioFactory) CanHandleAndAccept(name string) (bool, bool, error) {
if strings.HasPrefix(path.Base(name), "crio-conmon") {
// TODO(runcom): should we include crio-conmon cgroups?
return false, false, nil
}
if !strings.HasPrefix(path.Base(name), CrioNamespace) {
return false, false, nil
}
// if the container is not associated with CRI-O, we can't handle it or accept it.
if !isContainerName(name) {
return false, false, nil
}
return true, true, nil
}
func (self *crioFactory) DebugInfo() map[string][]string {
return map[string][]string{}
}
var (
// TODO(runcom): handle versioning in CRI-O
version_regexp_string = `(\d+)\.(\d+)\.(\d+)`
version_re = regexp.MustCompile(version_regexp_string)
apiversion_regexp_string = `(\d+)\.(\d+)`
apiversion_re = regexp.MustCompile(apiversion_regexp_string)
)
// Register root container before running this function!
func Register(factory info.MachineInfoFactory, fsInfo fs.FsInfo, includedMetrics container.MetricSet) error {
client, err := Client()
if err != nil {
return err
}
info, err := client.Info()
if err != nil {
return err
}
// TODO determine crio version so we can work differently w/ future versions if needed
cgroupSubsystems, err := libcontainer.GetCgroupSubsystems(includedMetrics)
if err != nil {
return fmt.Errorf("failed to get cgroup subsystems: %v", err)
}
klog.V(1).Infof("Registering CRI-O factory")
f := &crioFactory{
client: client,
cgroupSubsystems: cgroupSubsystems,
fsInfo: fsInfo,
machineInfoFactory: factory,
storageDriver: storageDriver(info.StorageDriver),
storageDir: info.StorageRoot,
includedMetrics: includedMetrics,
}
container.RegisterContainerHandlerFactory(f, []watcher.ContainerWatchSource{watcher.Raw})
return nil
}
// Copyright 2017 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Handler for CRI-O containers.
package crio
import (
"fmt"
"path"
"path/filepath"
"strconv"
"strings"
"github.com/google/cadvisor/container"
"github.com/google/cadvisor/container/common"
containerlibcontainer "github.com/google/cadvisor/container/libcontainer"
"github.com/google/cadvisor/fs"
info "github.com/google/cadvisor/info/v1"
cgroupfs "github.com/opencontainers/runc/libcontainer/cgroups/fs"
libcontainerconfigs "github.com/opencontainers/runc/libcontainer/configs"
)
type crioContainerHandler struct {
machineInfoFactory info.MachineInfoFactory
// Absolute path to the cgroup hierarchies of this container.
// (e.g.: "cpu" -> "/sys/fs/cgroup/cpu/test")
cgroupPaths map[string]string
// the CRI-O storage driver
storageDriver storageDriver
fsInfo fs.FsInfo
rootfsStorageDir string
// Metadata associated with the container.
envs map[string]string
labels map[string]string
// TODO
// crio version handling...
// Image name used for this container.
image string
// The network mode of the container
// TODO
// Filesystem handler.
fsHandler common.FsHandler
// The IP address of the container
ipAddress string
includedMetrics container.MetricSet
reference info.ContainerReference
libcontainerHandler *containerlibcontainer.Handler
}
var _ container.ContainerHandler = &crioContainerHandler{}
// newCrioContainerHandler returns a new container.ContainerHandler
func newCrioContainerHandler(
client crioClient,
name string,
machineInfoFactory info.MachineInfoFactory,
fsInfo fs.FsInfo,
storageDriver storageDriver,
storageDir string,
cgroupSubsystems *containerlibcontainer.CgroupSubsystems,
inHostNamespace bool,
metadataEnvs []string,
includedMetrics container.MetricSet,
) (container.ContainerHandler, error) {
// Create the cgroup paths.
cgroupPaths := common.MakeCgroupPaths(cgroupSubsystems.MountPoints, name)
// Generate the equivalent cgroup manager for this container.
cgroupManager := &cgroupfs.Manager{
Cgroups: &libcontainerconfigs.Cgroup{
Name: name,
},
Paths: cgroupPaths,
}
rootFs := "/"
if !inHostNamespace {
rootFs = "/rootfs"
storageDir = path.Join(rootFs, storageDir)
}
id := ContainerNameToCrioId(name)
cInfo, err := client.ContainerInfo(id)
if err != nil {
return nil, err
}
// passed to fs handler below ...
// XXX: this is using the full container logpath, as constructed by the CRI
// /var/log/pods/<pod_uuid>/container_instance.log
// It's not actually a log dir, as the CRI doesn't have per-container dirs
// under /var/log/pods/<pod_uuid>/
// We can't use /var/log/pods/<pod_uuid>/ to count per-container log usage.
// We use the container log file directly.
storageLogDir := cInfo.LogPath
// Determine the rootfs storage dir
rootfsStorageDir := cInfo.Root
// TODO(runcom): CRI-O doesn't strip /merged but we need to in order to
// get device ID from root, otherwise, it's going to error out as overlay
// mounts doesn't have fixed dev ids.
rootfsStorageDir = strings.TrimSuffix(rootfsStorageDir, "/merged")
switch storageDriver {
case overlayStorageDriver, overlay2StorageDriver:
// overlay and overlay2 driver are the same "overlay2" driver so treat
// them the same.
rootfsStorageDir = filepath.Join(rootfsStorageDir, "diff")
}
containerReference := info.ContainerReference{
Id: id,
Name: name,
Aliases: []string{cInfo.Name, id},
Namespace: CrioNamespace,
}
libcontainerHandler := containerlibcontainer.NewHandler(cgroupManager, rootFs, cInfo.Pid, includedMetrics)
// TODO: extract object mother method
handler := &crioContainerHandler{
machineInfoFactory: machineInfoFactory,
cgroupPaths: cgroupPaths,
storageDriver: storageDriver,
fsInfo: fsInfo,
rootfsStorageDir: rootfsStorageDir,
envs: make(map[string]string),
labels: cInfo.Labels,
includedMetrics: includedMetrics,
reference: containerReference,
libcontainerHandler: libcontainerHandler,
}
handler.image = cInfo.Image
// TODO: we wantd to know graph driver DeviceId (dont think this is needed now)
// ignore err and get zero as default, this happens with sandboxes, not sure why...
// kube isn't sending restart count in labels for sandboxes.
restartCount, _ := strconv.Atoi(cInfo.Annotations["io.kubernetes.container.restartCount"])
// Only adds restartcount label if it's greater than 0
if restartCount > 0 {
handler.labels["restartcount"] = strconv.Itoa(restartCount)
}
handler.ipAddress = cInfo.IP
// we optionally collect disk usage metrics
if includedMetrics.Has(container.DiskUsageMetrics) {
handler.fsHandler = common.NewFsHandler(common.DefaultPeriod, rootfsStorageDir, storageLogDir, fsInfo)
}
// TODO for env vars we wanted to show from container.Config.Env from whitelist
//for _, exposedEnv := range metadataEnvs {
//klog.V(4).Infof("TODO env whitelist: %v", exposedEnv)
//}
return handler, nil
}
func (self *crioContainerHandler) Start() {
if self.fsHandler != nil {
self.fsHandler.Start()
}
}
func (self *crioContainerHandler) Cleanup() {
if self.fsHandler != nil {
self.fsHandler.Stop()
}
}
func (self *crioContainerHandler) ContainerReference() (info.ContainerReference, error) {
return self.reference, nil
}
func (self *crioContainerHandler) needNet() bool {
if self.includedMetrics.Has(container.NetworkUsageMetrics) {
return self.labels["io.kubernetes.container.name"] == "POD"
}
return false
}
func (self *crioContainerHandler) GetSpec() (info.ContainerSpec, error) {
hasFilesystem := self.includedMetrics.Has(container.DiskUsageMetrics)
spec, err := common.GetSpec(self.cgroupPaths, self.machineInfoFactory, self.needNet(), hasFilesystem)
spec.Labels = self.labels
spec.Envs = self.envs
spec.Image = self.image
return spec, err
}
func (self *crioContainerHandler) getFsStats(stats *info.ContainerStats) error {
mi, err := self.machineInfoFactory.GetMachineInfo()
if err != nil {
return err
}
if self.includedMetrics.Has(container.DiskIOMetrics) {
common.AssignDeviceNamesToDiskStats((*common.MachineInfoNamer)(mi), &stats.DiskIo)
}
if !self.includedMetrics.Has(container.DiskUsageMetrics) {
return nil
}
var device string
switch self.storageDriver {
case overlay2StorageDriver, overlayStorageDriver:
deviceInfo, err := self.fsInfo.GetDirFsDevice(self.rootfsStorageDir)
if err != nil {
return fmt.Errorf("unable to determine device info for dir: %v: %v", self.rootfsStorageDir, err)
}
device = deviceInfo.Device
default:
return nil
}
var (
limit uint64
fsType string
)
// crio does not impose any filesystem limits for containers. So use capacity as limit.
for _, fs := range mi.Filesystems {
if fs.Device == device {
limit = fs.Capacity
fsType = fs.Type
break
}
}
fsStat := info.FsStats{Device: device, Type: fsType, Limit: limit}
usage := self.fsHandler.Usage()
fsStat.BaseUsage = usage.BaseUsageBytes
fsStat.Usage = usage.TotalUsageBytes
fsStat.Inodes = usage.InodeUsage
stats.Filesystem = append(stats.Filesystem, fsStat)
return nil
}
func (self *crioContainerHandler) GetStats() (*info.ContainerStats, error) {
stats, err := self.libcontainerHandler.GetStats()
if err != nil {
return stats, err
}
// Clean up stats for containers that don't have their own network - this
// includes containers running in Kubernetes pods that use the network of the
// infrastructure container. This stops metrics being reported multiple times
// for each container in a pod.
if !self.needNet() {
stats.Network = info.NetworkStats{}
}
// Get filesystem stats.
err = self.getFsStats(stats)
if err != nil {
return stats, err
}
return stats, nil
}
func (self *crioContainerHandler) ListContainers(listType container.ListType) ([]info.ContainerReference, error) {
// No-op for Docker driver.
return []info.ContainerReference{}, nil
}
func (self *crioContainerHandler) GetCgroupPath(resource string) (string, error) {
path, ok := self.cgroupPaths[resource]
if !ok {
return "", fmt.Errorf("could not find path for resource %q for container %q\n", resource, self.reference.Name)
}
return path, nil
}
func (self *crioContainerHandler) GetContainerLabels() map[string]string {
return self.labels
}
func (self *crioContainerHandler) GetContainerIPAddress() string {
return self.ipAddress
}
func (self *crioContainerHandler) ListProcesses(listType container.ListType) ([]int, error) {
return self.libcontainerHandler.GetProcesses()
}
func (self *crioContainerHandler) Exists() bool {
return common.CgroupExists(self.cgroupPaths)
}
func (self *crioContainerHandler) Type() container.ContainerType {
return container.ContainerTypeCrio
}
...@@ -325,7 +325,7 @@ func Register(factory info.MachineInfoFactory, fsInfo fs.FsInfo, includedMetrics ...@@ -325,7 +325,7 @@ func Register(factory info.MachineInfoFactory, fsInfo fs.FsInfo, includedMetrics
dockerAPIVersion, _ := APIVersion() dockerAPIVersion, _ := APIVersion()
cgroupSubsystems, err := libcontainer.GetCgroupSubsystems() cgroupSubsystems, err := libcontainer.GetCgroupSubsystems(includedMetrics)
if err != nil { if err != nil {
return fmt.Errorf("failed to get cgroup subsystems: %v", err) return fmt.Errorf("failed to get cgroup subsystems: %v", err)
} }
......
...@@ -134,10 +134,7 @@ func newDockerContainerHandler( ...@@ -134,10 +134,7 @@ func newDockerContainerHandler(
zfsWatcher *zfs.ZfsWatcher, zfsWatcher *zfs.ZfsWatcher,
) (container.ContainerHandler, error) { ) (container.ContainerHandler, error) {
// Create the cgroup paths. // Create the cgroup paths.
cgroupPaths := make(map[string]string, len(cgroupSubsystems.MountPoints)) cgroupPaths := common.MakeCgroupPaths(cgroupSubsystems.MountPoints, name)
for key, val := range cgroupSubsystems.MountPoints {
cgroupPaths[key] = path.Join(val, name)
}
// Generate the equivalent cgroup manager for this container. // Generate the equivalent cgroup manager for this container.
cgroupManager := &cgroupfs.Manager{ cgroupManager := &cgroupfs.Manager{
......
...@@ -66,8 +66,7 @@ func (h *Handler) GetStats() (*info.ContainerStats, error) { ...@@ -66,8 +66,7 @@ func (h *Handler) GetStats() (*info.ContainerStats, error) {
libcontainerStats := &libcontainer.Stats{ libcontainerStats := &libcontainer.Stats{
CgroupStats: cgroupStats, CgroupStats: cgroupStats,
} }
withPerCPU := h.includedMetrics.Has(container.PerCpuUsageMetrics) stats := newContainerStats(libcontainerStats, h.includedMetrics)
stats := newContainerStats(libcontainerStats, withPerCPU)
if h.includedMetrics.Has(container.ProcessSchedulerMetrics) { if h.includedMetrics.Has(container.ProcessSchedulerMetrics) {
pids, err := h.cgroupManager.GetAllPids() pids, err := h.cgroupManager.GetAllPids()
...@@ -599,14 +598,16 @@ func setNetworkStats(libcontainerStats *libcontainer.Stats, ret *info.ContainerS ...@@ -599,14 +598,16 @@ func setNetworkStats(libcontainerStats *libcontainer.Stats, ret *info.ContainerS
} }
} }
func newContainerStats(libcontainerStats *libcontainer.Stats, withPerCPU bool) *info.ContainerStats { func newContainerStats(libcontainerStats *libcontainer.Stats, includedMetrics container.MetricSet) *info.ContainerStats {
ret := &info.ContainerStats{ ret := &info.ContainerStats{
Timestamp: time.Now(), Timestamp: time.Now(),
} }
if s := libcontainerStats.CgroupStats; s != nil { if s := libcontainerStats.CgroupStats; s != nil {
setCpuStats(s, ret, withPerCPU) setCpuStats(s, ret, includedMetrics.Has(container.PerCpuUsageMetrics))
setDiskIoStats(s, ret) if includedMetrics.Has(container.DiskIOMetrics) {
setDiskIoStats(s, ret)
}
setMemoryStats(s, ret) setMemoryStats(s, ret)
} }
if len(libcontainerStats.Interfaces) > 0 { if len(libcontainerStats.Interfaces) > 0 {
......
...@@ -19,6 +19,7 @@ import ( ...@@ -19,6 +19,7 @@ import (
info "github.com/google/cadvisor/info/v1" info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/container"
"github.com/opencontainers/runc/libcontainer/cgroups" "github.com/opencontainers/runc/libcontainer/cgroups"
"k8s.io/klog" "k8s.io/klog"
) )
...@@ -33,18 +34,36 @@ type CgroupSubsystems struct { ...@@ -33,18 +34,36 @@ type CgroupSubsystems struct {
MountPoints map[string]string MountPoints map[string]string
} }
// Get information about the cgroup subsystems. // Get information about the cgroup subsystems those we want
func GetCgroupSubsystems() (CgroupSubsystems, error) { func GetCgroupSubsystems(includedMetrics container.MetricSet) (CgroupSubsystems, error) {
// Get all cgroup mounts. // Get all cgroup mounts.
allCgroups, err := cgroups.GetCgroupMounts(true) allCgroups, err := cgroups.GetCgroupMounts(true)
if err != nil { if err != nil {
return CgroupSubsystems{}, err return CgroupSubsystems{}, err
} }
return getCgroupSubsystemsHelper(allCgroups) disableCgroups := map[string]struct{}{}
//currently we only support disable blkio subsystem
if !includedMetrics.Has(container.DiskIOMetrics) {
disableCgroups["blkio"] = struct{}{}
}
return getCgroupSubsystemsHelper(allCgroups, disableCgroups)
}
// Get information about all the cgroup subsystems.
func GetAllCgroupSubsystems() (CgroupSubsystems, error) {
// Get all cgroup mounts.
allCgroups, err := cgroups.GetCgroupMounts(true)
if err != nil {
return CgroupSubsystems{}, err
}
emptyDisableCgroups := map[string]struct{}{}
return getCgroupSubsystemsHelper(allCgroups, emptyDisableCgroups)
} }
func getCgroupSubsystemsHelper(allCgroups []cgroups.Mount) (CgroupSubsystems, error) { func getCgroupSubsystemsHelper(allCgroups []cgroups.Mount, disableCgroups map[string]struct{}) (CgroupSubsystems, error) {
if len(allCgroups) == 0 { if len(allCgroups) == 0 {
return CgroupSubsystems{}, fmt.Errorf("failed to find cgroup mounts") return CgroupSubsystems{}, fmt.Errorf("failed to find cgroup mounts")
} }
...@@ -55,6 +74,9 @@ func getCgroupSubsystemsHelper(allCgroups []cgroups.Mount) (CgroupSubsystems, er ...@@ -55,6 +74,9 @@ func getCgroupSubsystemsHelper(allCgroups []cgroups.Mount) (CgroupSubsystems, er
mountPoints := make(map[string]string, len(allCgroups)) mountPoints := make(map[string]string, len(allCgroups))
for _, mount := range allCgroups { for _, mount := range allCgroups {
for _, subsystem := range mount.Subsystems { for _, subsystem := range mount.Subsystems {
if _, exists := disableCgroups[subsystem]; exists {
continue
}
if _, ok := supportedSubsystems[subsystem]; !ok { if _, ok := supportedSubsystems[subsystem]; !ok {
// Unsupported subsystem // Unsupported subsystem
continue continue
......
...@@ -63,17 +63,20 @@ func (self *rawFactory) NewContainerHandler(name string, inHostNamespace bool) ( ...@@ -63,17 +63,20 @@ func (self *rawFactory) NewContainerHandler(name string, inHostNamespace bool) (
return newRawContainerHandler(name, self.cgroupSubsystems, self.machineInfoFactory, self.fsInfo, self.watcher, rootFs, self.includedMetrics) return newRawContainerHandler(name, self.cgroupSubsystems, self.machineInfoFactory, self.fsInfo, self.watcher, rootFs, self.includedMetrics)
} }
// The raw factory can handle any container. If --docker_only is set to false, non-docker containers are ignored. // The raw factory can handle any container. If --docker_only is set to true, non-docker containers are ignored except for "/" and those whitelisted by raw_cgroup_prefix_whitelist flag.
func (self *rawFactory) CanHandleAndAccept(name string) (bool, bool, error) { func (self *rawFactory) CanHandleAndAccept(name string) (bool, bool, error) {
accept := name == "/" || !*dockerOnly if name == "/" {
return true, true, nil
}
if *dockerOnly && self.rawPrefixWhiteList[0] == "" {
return true, false, nil
}
for _, prefix := range self.rawPrefixWhiteList { for _, prefix := range self.rawPrefixWhiteList {
if strings.HasPrefix(name, prefix) { if strings.HasPrefix(name, prefix) {
accept = true return true, true, nil
break
} }
} }
return true, accept, nil return true, false, nil
} }
func (self *rawFactory) DebugInfo() map[string][]string { func (self *rawFactory) DebugInfo() map[string][]string {
...@@ -81,7 +84,7 @@ func (self *rawFactory) DebugInfo() map[string][]string { ...@@ -81,7 +84,7 @@ func (self *rawFactory) DebugInfo() map[string][]string {
} }
func Register(machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo, includedMetrics map[container.MetricKind]struct{}, rawPrefixWhiteList []string) error { func Register(machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo, includedMetrics map[container.MetricKind]struct{}, rawPrefixWhiteList []string) error {
cgroupSubsystems, err := libcontainer.GetCgroupSubsystems() cgroupSubsystems, err := libcontainer.GetCgroupSubsystems(includedMetrics)
if err != nil { if err != nil {
return fmt.Errorf("failed to get cgroup subsystems: %v", err) return fmt.Errorf("failed to get cgroup subsystems: %v", err)
} }
......
...@@ -39,8 +39,9 @@ type rawContainerHandler struct { ...@@ -39,8 +39,9 @@ type rawContainerHandler struct {
// (e.g.: "cpu" -> "/sys/fs/cgroup/cpu/test") // (e.g.: "cpu" -> "/sys/fs/cgroup/cpu/test")
cgroupPaths map[string]string cgroupPaths map[string]string
fsInfo fs.FsInfo fsInfo fs.FsInfo
externalMounts []common.Mount externalMounts []common.Mount
includedMetrics container.MetricSet
libcontainerHandler *libcontainer.Handler libcontainerHandler *libcontainer.Handler
} }
...@@ -86,6 +87,7 @@ func newRawContainerHandler(name string, cgroupSubsystems *libcontainer.CgroupSu ...@@ -86,6 +87,7 @@ func newRawContainerHandler(name string, cgroupSubsystems *libcontainer.CgroupSu
cgroupPaths: cgroupPaths, cgroupPaths: cgroupPaths,
fsInfo: fsInfo, fsInfo: fsInfo,
externalMounts: externalMounts, externalMounts: externalMounts,
includedMetrics: includedMetrics,
libcontainerHandler: handler, libcontainerHandler: handler,
}, nil }, nil
} }
...@@ -185,36 +187,39 @@ func fsToFsStats(fs *fs.Fs) info.FsStats { ...@@ -185,36 +187,39 @@ func fsToFsStats(fs *fs.Fs) info.FsStats {
} }
func (self *rawContainerHandler) getFsStats(stats *info.ContainerStats) error { func (self *rawContainerHandler) getFsStats(stats *info.ContainerStats) error {
var allFs []fs.Fs var filesystems []fs.Fs
var err error
// Get Filesystem information only for the root cgroup. // Get Filesystem information only for the root cgroup.
if isRootCgroup(self.name) { if isRootCgroup(self.name) {
filesystems, err := self.fsInfo.GetGlobalFsInfo() filesystems, err = self.fsInfo.GetGlobalFsInfo()
if err != nil { if err != nil {
return err return err
} }
for i := range filesystems { } else if self.includedMetrics.Has(container.DiskUsageMetrics) || self.includedMetrics.Has(container.DiskIOMetrics) {
fs := filesystems[i] if len(self.externalMounts) > 0 {
stats.Filesystem = append(stats.Filesystem, fsToFsStats(&fs)) var mountSet map[string]struct{}
} mountSet = make(map[string]struct{})
allFs = filesystems for _, mount := range self.externalMounts {
} else if len(self.externalMounts) > 0 { mountSet[mount.HostDir] = struct{}{}
var mountSet map[string]struct{} }
mountSet = make(map[string]struct{}) filesystems, err = self.fsInfo.GetFsInfoForPath(mountSet)
for _, mount := range self.externalMounts { if err != nil {
mountSet[mount.HostDir] = struct{}{} return err
} }
filesystems, err := self.fsInfo.GetFsInfoForPath(mountSet)
if err != nil {
return err
} }
}
if isRootCgroup(self.name) || self.includedMetrics.Has(container.DiskUsageMetrics) {
for i := range filesystems { for i := range filesystems {
fs := filesystems[i] fs := filesystems[i]
stats.Filesystem = append(stats.Filesystem, fsToFsStats(&fs)) stats.Filesystem = append(stats.Filesystem, fsToFsStats(&fs))
} }
allFs = filesystems
} }
common.AssignDeviceNamesToDiskStats(&fsNamer{fs: allFs, factory: self.machineInfoFactory}, &stats.DiskIo) if isRootCgroup(self.name) || self.includedMetrics.Has(container.DiskIOMetrics) {
common.AssignDeviceNamesToDiskStats(&fsNamer{fs: filesystems, factory: self.machineInfoFactory}, &stats.DiskIo)
}
return nil return nil
} }
......
// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package systemd
import (
"fmt"
"strings"
"github.com/google/cadvisor/container"
"github.com/google/cadvisor/fs"
info "github.com/google/cadvisor/info/v1"
"github.com/google/cadvisor/manager/watcher"
"k8s.io/klog"
)
type systemdFactory struct{}
func (f *systemdFactory) String() string {
return "systemd"
}
func (f *systemdFactory) NewContainerHandler(name string, inHostNamespace bool) (container.ContainerHandler, error) {
return nil, fmt.Errorf("Not yet supported")
}
func (f *systemdFactory) CanHandleAndAccept(name string) (bool, bool, error) {
// on systemd using devicemapper each mount into the container has an associated cgroup that we ignore.
// for details on .mount units: http://man7.org/linux/man-pages/man5/systemd.mount.5.html
if strings.HasSuffix(name, ".mount") {
return true, false, nil
}
return false, false, fmt.Errorf("%s not handled by systemd handler", name)
}
func (f *systemdFactory) DebugInfo() map[string][]string {
return map[string][]string{}
}
// Register registers the systemd container factory.
func Register(machineInfoFactory info.MachineInfoFactory, fsInfo fs.FsInfo, includedMetrics container.MetricSet) error {
klog.V(1).Infof("Registering systemd factory")
factory := &systemdFactory{}
container.RegisterContainerHandlerFactory(factory, []watcher.ContainerWatchSource{watcher.Raw})
return nil
}
...@@ -19,7 +19,6 @@ package fs ...@@ -19,7 +19,6 @@ package fs
import ( import (
"bufio" "bufio"
"bytes"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os" "os"
...@@ -30,7 +29,6 @@ import ( ...@@ -30,7 +29,6 @@ import (
"strconv" "strconv"
"strings" "strings"
"syscall" "syscall"
"time"
"github.com/docker/docker/pkg/mount" "github.com/docker/docker/pkg/mount"
"github.com/google/cadvisor/devicemapper" "github.com/google/cadvisor/devicemapper"
...@@ -47,8 +45,12 @@ const ( ...@@ -47,8 +45,12 @@ const (
LabelCrioImages = "crio-images" LabelCrioImages = "crio-images"
) )
// The maximum number of `du` and `find` tasks that can be running at once. const (
const maxConcurrentOps = 20 // The block size in bytes.
statBlockSize uint64 = 512
// The maximum number of `disk usage` tasks that can be running at once.
maxConcurrentOps = 20
)
// A pool for restricting the number of consecutive `du` and `find` tasks running. // A pool for restricting the number of consecutive `du` and `find` tasks running.
var pool = make(chan struct{}, maxConcurrentOps) var pool = make(chan struct{}, maxConcurrentOps)
...@@ -559,78 +561,73 @@ func (self *RealFsInfo) GetDirFsDevice(dir string) (*DeviceInfo, error) { ...@@ -559,78 +561,73 @@ func (self *RealFsInfo) GetDirFsDevice(dir string) (*DeviceInfo, error) {
return nil, fmt.Errorf("could not find device with major: %d, minor: %d in cached partitions map", major, minor) return nil, fmt.Errorf("could not find device with major: %d, minor: %d in cached partitions map", major, minor)
} }
func (self *RealFsInfo) GetDirDiskUsage(dir string, timeout time.Duration) (uint64, error) { func GetDirUsage(dir string) (UsageInfo, error) {
claimToken() var usage UsageInfo
defer releaseToken()
return GetDirDiskUsage(dir, timeout)
}
func GetDirDiskUsage(dir string, timeout time.Duration) (uint64, error) {
if dir == "" { if dir == "" {
return 0, fmt.Errorf("invalid directory") return usage, fmt.Errorf("invalid directory")
} }
cmd := exec.Command("ionice", "-c3", "nice", "-n", "19", "du", "-s", dir)
stdoutp, err := cmd.StdoutPipe() rootInfo, err := os.Stat(dir)
if err != nil {
return 0, fmt.Errorf("failed to setup stdout for cmd %v - %v", cmd.Args, err)
}
stderrp, err := cmd.StderrPipe()
if err != nil { if err != nil {
return 0, fmt.Errorf("failed to setup stderr for cmd %v - %v", cmd.Args, err) return usage, fmt.Errorf("could not stat %q to get inode usage: %v", dir, err)
} }
if err := cmd.Start(); err != nil { rootStat, ok := rootInfo.Sys().(*syscall.Stat_t)
return 0, fmt.Errorf("failed to exec du - %v", err) if !ok {
return usage, fmt.Errorf("unsuported fileinfo for getting inode usage of %q", dir)
} }
timer := time.AfterFunc(timeout, func() {
klog.Warningf("Killing cmd %v due to timeout(%s)", cmd.Args, timeout.String()) rootDevId := rootStat.Dev
cmd.Process.Kill()
// dedupedInode stores inodes that could be duplicates (nlink > 1)
dedupedInodes := make(map[uint64]struct{})
err = filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
if os.IsNotExist(err) {
// expected if files appear/vanish
return nil
}
if err != nil {
return fmt.Errorf("unable to count inodes for part of dir %s: %s", dir, err)
}
// according to the docs, Sys can be nil
if info.Sys() == nil {
return fmt.Errorf("fileinfo Sys is nil")
}
s, ok := info.Sys().(*syscall.Stat_t)
if !ok {
return fmt.Errorf("unsupported fileinfo; could not convert to stat_t")
}
if s.Dev != rootDevId {
// don't descend into directories on other devices
return filepath.SkipDir
}
if s.Nlink > 1 {
if _, ok := dedupedInodes[s.Ino]; !ok {
// Dedupe things that could be hardlinks
dedupedInodes[s.Ino] = struct{}{}
usage.Bytes += uint64(s.Blocks) * statBlockSize
usage.Inodes++
}
} else {
usage.Bytes += uint64(s.Blocks) * statBlockSize
usage.Inodes++
}
return nil
}) })
stdoutb, souterr := ioutil.ReadAll(stdoutp)
if souterr != nil { return usage, nil
klog.Errorf("Failed to read from stdout for cmd %v - %v", cmd.Args, souterr)
}
stderrb, _ := ioutil.ReadAll(stderrp)
err = cmd.Wait()
timer.Stop()
if err != nil {
return 0, fmt.Errorf("du command failed on %s with output stdout: %s, stderr: %s - %v", dir, string(stdoutb), string(stderrb), err)
}
stdout := string(stdoutb)
usageInKb, err := strconv.ParseUint(strings.Fields(stdout)[0], 10, 64)
if err != nil {
return 0, fmt.Errorf("cannot parse 'du' output %s - %s", stdout, err)
}
return usageInKb * 1024, nil
} }
func (self *RealFsInfo) GetDirInodeUsage(dir string, timeout time.Duration) (uint64, error) { func (self *RealFsInfo) GetDirUsage(dir string) (UsageInfo, error) {
claimToken() claimToken()
defer releaseToken() defer releaseToken()
return GetDirInodeUsage(dir, timeout) return GetDirUsage(dir)
}
func GetDirInodeUsage(dir string, timeout time.Duration) (uint64, error) {
if dir == "" {
return 0, fmt.Errorf("invalid directory")
}
var counter byteCounter
var stderr bytes.Buffer
findCmd := exec.Command("ionice", "-c3", "nice", "-n", "19", "find", dir, "-xdev", "-printf", ".")
findCmd.Stdout, findCmd.Stderr = &counter, &stderr
if err := findCmd.Start(); err != nil {
return 0, fmt.Errorf("failed to exec cmd %v - %v; stderr: %v", findCmd.Args, err, stderr.String())
}
timer := time.AfterFunc(timeout, func() {
klog.Warningf("Killing cmd %v due to timeout(%s)", findCmd.Args, timeout.String())
findCmd.Process.Kill()
})
err := findCmd.Wait()
timer.Stop()
if err != nil {
return 0, fmt.Errorf("cmd %v failed. stderr: %s; err: %v", findCmd.Args, stderr.String(), err)
}
return counter.bytesWritten, nil
} }
func getVfsStats(path string) (total uint64, free uint64, avail uint64, inodes uint64, inodesFree uint64, err error) { func getVfsStats(path string) (total uint64, free uint64, avail uint64, inodes uint64, inodesFree uint64, err error) {
......
...@@ -16,7 +16,6 @@ package fs ...@@ -16,7 +16,6 @@ package fs
import ( import (
"errors" "errors"
"time"
) )
type DeviceInfo struct { type DeviceInfo struct {
...@@ -62,6 +61,11 @@ type DiskStats struct { ...@@ -62,6 +61,11 @@ type DiskStats struct {
WeightedIoTime uint64 WeightedIoTime uint64
} }
type UsageInfo struct {
Bytes uint64
Inodes uint64
}
// ErrNoSuchDevice is the error indicating the requested device does not exist. // ErrNoSuchDevice is the error indicating the requested device does not exist.
var ErrNoSuchDevice = errors.New("cadvisor: no such device") var ErrNoSuchDevice = errors.New("cadvisor: no such device")
...@@ -72,11 +76,8 @@ type FsInfo interface { ...@@ -72,11 +76,8 @@ type FsInfo interface {
// Returns capacity and free space, in bytes, of the set of mounts passed. // Returns capacity and free space, in bytes, of the set of mounts passed.
GetFsInfoForPath(mountSet map[string]struct{}) ([]Fs, error) GetFsInfoForPath(mountSet map[string]struct{}) ([]Fs, error)
// Returns number of bytes occupied by 'dir'. // GetDirUsage returns a usage information for 'dir'.
GetDirDiskUsage(dir string, timeout time.Duration) (uint64, error) GetDirUsage(dir string) (UsageInfo, error)
// Returns number of inodes used by 'dir'.
GetDirInodeUsage(dir string, timeout time.Duration) (uint64, error)
// GetDeviceInfoByFsUUID returns the information of the device with the // GetDeviceInfoByFsUUID returns the information of the device with the
// specified filesystem uuid. If no such device exists, this function will // specified filesystem uuid. If no such device exists, this function will
......
...@@ -16,12 +16,13 @@ ...@@ -16,12 +16,13 @@
package machine package machine
import ( import (
"bytes"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"path/filepath"
"regexp" "regexp"
"strconv" "strconv"
"strings" "strings"
// s390/s390x changes // s390/s390x changes
"runtime" "runtime"
...@@ -36,9 +37,10 @@ import ( ...@@ -36,9 +37,10 @@ import (
) )
var ( var (
cpuRegExp = regexp.MustCompile(`^processor\s*:\s*([0-9]+)$`) cpuRegExp = regexp.MustCompile(`^processor\s*:\s*([0-9]+)$`)
coreRegExp = regexp.MustCompile(`^core id\s*:\s*([0-9]+)$`) coreRegExp = regexp.MustCompile(`^core id\s*:\s*([0-9]+)$`)
nodeRegExp = regexp.MustCompile(`^physical id\s*:\s*([0-9]+)$`) nodeRegExp = regexp.MustCompile(`^physical id\s*:\s*([0-9]+)$`)
nodeBusRegExp = regexp.MustCompile(`^node([0-9]+)$`)
// Power systems have a different format so cater for both // Power systems have a different format so cater for both
cpuClockSpeedMHz = regexp.MustCompile(`(?:cpu MHz|clock)\s*:\s*([0-9]+\.[0-9]+)(?:MHz)?`) cpuClockSpeedMHz = regexp.MustCompile(`(?:cpu MHz|clock)\s*:\s*([0-9]+\.[0-9]+)(?:MHz)?`)
memoryCapacityRegexp = regexp.MustCompile(`MemTotal:\s*([0-9]+) kB`) memoryCapacityRegexp = regexp.MustCompile(`MemTotal:\s*([0-9]+) kB`)
...@@ -46,6 +48,7 @@ var ( ...@@ -46,6 +48,7 @@ var (
) )
const maxFreqFile = "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq" const maxFreqFile = "/sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq"
const cpuBusPath = "/sys/bus/cpu/devices/"
// GetClockSpeed returns the CPU clock speed, given a []byte formatted as the /proc/cpuinfo file. // GetClockSpeed returns the CPU clock speed, given a []byte formatted as the /proc/cpuinfo file.
func GetClockSpeed(procInfo []byte) (uint64, error) { func GetClockSpeed(procInfo []byte) (uint64, error) {
...@@ -127,6 +130,67 @@ func parseCapacity(b []byte, r *regexp.Regexp) (uint64, error) { ...@@ -127,6 +130,67 @@ func parseCapacity(b []byte, r *regexp.Regexp) (uint64, error) {
return m * 1024, err return m * 1024, err
} }
/* Look for sysfs cpu path containing core_id */
/* Such as: sys/bus/cpu/devices/cpu0/topology/core_id */
func getCoreIdFromCpuBus(cpuBusPath string, threadId int) (int, error) {
path := filepath.Join(cpuBusPath, fmt.Sprintf("cpu%d/topology", threadId))
file := filepath.Join(path, "core_id")
num, err := ioutil.ReadFile(file)
if err != nil {
return threadId, err
}
coreId, err := strconv.ParseInt(string(bytes.TrimSpace(num)), 10, 32)
if err != nil {
return threadId, err
}
if coreId < 0 {
// report threadId if found coreId < 0
coreId = int64(threadId)
}
return int(coreId), nil
}
/* Look for sysfs cpu path containing node id */
/* Such as: /sys/bus/cpu/devices/cpu0/node%d */
func getNodeIdFromCpuBus(cpuBusPath string, threadId int) (int, error) {
path := filepath.Join(cpuBusPath, fmt.Sprintf("cpu%d", threadId))
files, err := ioutil.ReadDir(path)
if err != nil {
return 0, err
}
nodeId := 0
for _, file := range files {
filename := file.Name()
isNode, error := regexp.MatchString("^node([0-9]+)$", filename)
if error != nil {
continue
}
if !isNode {
continue
}
ok, val, _ := extractValue(filename, nodeBusRegExp)
if err != nil {
continue
}
if ok {
if val < 0 {
continue
}
nodeId = val
}
}
return nodeId, nil
}
func GetTopology(sysFs sysfs.SysFs, cpuinfo string) ([]info.Node, int, error) { func GetTopology(sysFs sysfs.SysFs, cpuinfo string) ([]info.Node, int, error) {
nodes := []info.Node{} nodes := []info.Node{}
...@@ -161,8 +225,36 @@ func GetTopology(sysFs sysfs.SysFs, cpuinfo string) ([]info.Node, int, error) { ...@@ -161,8 +225,36 @@ func GetTopology(sysFs sysfs.SysFs, cpuinfo string) ([]info.Node, int, error) {
lastNode = -1 lastNode = -1
} }
lastThread = thread lastThread = thread
/* On Arm platform, no 'core id' and 'physical id' in '/proc/cpuinfo'. */
/* So we search sysfs cpu path directly. */
/* This method can also be used on other platforms, such as x86, ppc64le... */
/* /sys/bus/cpu/devices/cpu%d contains the information of 'core_id' & 'node_id'. */
/* Such as: /sys/bus/cpu/devices/cpu0/topology/core_id */
/* Such as: /sys/bus/cpu/devices/cpu0/node0 */
if isAArch64() {
val, err = getCoreIdFromCpuBus(cpuBusPath, lastThread)
if err != nil {
// Report thread id if no NUMA
val = lastThread
}
lastCore = val
val, err = getNodeIdFromCpuBus(cpuBusPath, lastThread)
if err != nil {
// Report node 0 if no NUMA
val = 0
}
lastNode = val
}
continue continue
} }
if isAArch64() {
/* On Arm platform, no 'core id' and 'physical id' in '/proc/cpuinfo'. */
continue
}
ok, val, err = extractValue(line, coreRegExp) ok, val, err = extractValue(line, coreRegExp)
if err != nil { if err != nil {
return nil, -1, fmt.Errorf("could not parse core info from %q: %v", line, err) return nil, -1, fmt.Errorf("could not parse core info from %q: %v", line, err)
...@@ -171,6 +263,7 @@ func GetTopology(sysFs sysfs.SysFs, cpuinfo string) ([]info.Node, int, error) { ...@@ -171,6 +263,7 @@ func GetTopology(sysFs sysfs.SysFs, cpuinfo string) ([]info.Node, int, error) {
lastCore = val lastCore = val
continue continue
} }
ok, val, err = extractValue(line, nodeRegExp) ok, val, err = extractValue(line, nodeRegExp)
if err != nil { if err != nil {
return nil, -1, fmt.Errorf("could not parse node info from %q: %v", line, err) return nil, -1, fmt.Errorf("could not parse node info from %q: %v", line, err)
...@@ -180,6 +273,7 @@ func GetTopology(sysFs sysfs.SysFs, cpuinfo string) ([]info.Node, int, error) { ...@@ -180,6 +273,7 @@ func GetTopology(sysFs sysfs.SysFs, cpuinfo string) ([]info.Node, int, error) {
continue continue
} }
} }
nodeIdx, err := addNode(&nodes, lastNode) nodeIdx, err := addNode(&nodes, lastNode)
if err != nil { if err != nil {
return nil, -1, fmt.Errorf("failed to add node %d: %v", lastNode, err) return nil, -1, fmt.Errorf("failed to add node %d: %v", lastNode, err)
......
...@@ -31,8 +31,10 @@ import ( ...@@ -31,8 +31,10 @@ import (
"github.com/google/cadvisor/collector" "github.com/google/cadvisor/collector"
"github.com/google/cadvisor/container" "github.com/google/cadvisor/container"
"github.com/google/cadvisor/container/containerd" "github.com/google/cadvisor/container/containerd"
"github.com/google/cadvisor/container/crio"
"github.com/google/cadvisor/container/docker" "github.com/google/cadvisor/container/docker"
"github.com/google/cadvisor/container/raw" "github.com/google/cadvisor/container/raw"
"github.com/google/cadvisor/container/systemd"
"github.com/google/cadvisor/events" "github.com/google/cadvisor/events"
"github.com/google/cadvisor/fs" "github.com/google/cadvisor/fs"
info "github.com/google/cadvisor/info/v1" info "github.com/google/cadvisor/info/v1"
...@@ -51,6 +53,7 @@ import ( ...@@ -51,6 +53,7 @@ import (
) )
var globalHousekeepingInterval = flag.Duration("global_housekeeping_interval", 1*time.Minute, "Interval between global housekeepings") var globalHousekeepingInterval = flag.Duration("global_housekeeping_interval", 1*time.Minute, "Interval between global housekeepings")
var updateMachineInfoInterval = flag.Duration("update_machine_info_interval", 5*time.Minute, "Interval between machine info updates.")
var logCadvisorUsage = flag.Bool("log_cadvisor_usage", false, "Whether to log the usage of the cAdvisor container") var logCadvisorUsage = flag.Bool("log_cadvisor_usage", false, "Whether to log the usage of the cAdvisor container")
var eventStorageAgeLimit = flag.String("event_storage_age_limit", "default=24h", "Max length of time for which to store events (per type). Value is a comma separated list of key values, where the keys are event types (e.g.: creation, oom) or \"default\" and the value is a duration. Default is applied to all non-specified event types") var eventStorageAgeLimit = flag.String("event_storage_age_limit", "default=24h", "Max length of time for which to store events (per type). Value is a comma separated list of key values, where the keys are event types (e.g.: creation, oom) or \"default\" and the value is a duration. Default is applied to all non-specified event types")
var eventStorageEventLimit = flag.String("event_storage_event_limit", "default=100000", "Max number of events to store (per type). Value is a comma separated list of key values, where the keys are event types (e.g.: creation, oom) or \"default\" and the value is an integer. Default is applied to all non-specified event types") var eventStorageEventLimit = flag.String("event_storage_event_limit", "default=100000", "Max number of events to store (per type). Value is a comma separated list of key values, where the keys are event types (e.g.: creation, oom) or \"default\" and the value is an integer. Default is applied to all non-specified event types")
...@@ -156,12 +159,24 @@ func New(memoryCache *memory.InMemoryCache, sysfs sysfs.SysFs, maxHousekeepingIn ...@@ -156,12 +159,24 @@ func New(memoryCache *memory.InMemoryCache, sysfs sysfs.SysFs, maxHousekeepingIn
// Try to connect to docker indefinitely on startup. // Try to connect to docker indefinitely on startup.
dockerStatus = retryDockerStatus() dockerStatus = retryDockerStatus()
crioClient, err := crio.Client()
if err != nil {
return nil, err
}
crioInfo, err := crioClient.Info()
if err != nil {
klog.V(5).Infof("CRI-O not connected: %v", err)
}
context := fs.Context{ context := fs.Context{
Docker: fs.DockerContext{ Docker: fs.DockerContext{
Root: docker.RootDir(), Root: docker.RootDir(),
Driver: dockerStatus.Driver, Driver: dockerStatus.Driver,
DriverStatus: dockerStatus.DriverStatus, DriverStatus: dockerStatus.DriverStatus,
}, },
Crio: fs.CrioContext{
Root: crioInfo.StorageRoot,
},
} }
fsInfo, err := fs.NewFsInfo(context) fsInfo, err := fs.NewFsInfo(context)
if err != nil { if err != nil {
...@@ -183,6 +198,7 @@ func New(memoryCache *memory.InMemoryCache, sysfs sysfs.SysFs, maxHousekeepingIn ...@@ -183,6 +198,7 @@ func New(memoryCache *memory.InMemoryCache, sysfs sysfs.SysFs, maxHousekeepingIn
quitChannels: make([]chan error, 0, 2), quitChannels: make([]chan error, 0, 2),
memoryCache: memoryCache, memoryCache: memoryCache,
fsInfo: fsInfo, fsInfo: fsInfo,
sysFs: sysfs,
cadvisorContainer: selfContainer, cadvisorContainer: selfContainer,
inHostNamespace: inHostNamespace, inHostNamespace: inHostNamespace,
startupTime: time.Now(), startupTime: time.Now(),
...@@ -252,6 +268,8 @@ type manager struct { ...@@ -252,6 +268,8 @@ type manager struct {
containersLock sync.RWMutex containersLock sync.RWMutex
memoryCache *memory.InMemoryCache memoryCache *memory.InMemoryCache
fsInfo fs.FsInfo fsInfo fs.FsInfo
sysFs sysfs.SysFs
machineMu sync.RWMutex // protects machineInfo
machineInfo info.MachineInfo machineInfo info.MachineInfo
quitChannels []chan error quitChannels []chan error
cadvisorContainer string cadvisorContainer string
...@@ -281,6 +299,16 @@ func (self *manager) Start() error { ...@@ -281,6 +299,16 @@ func (self *manager) Start() error {
klog.V(5).Infof("Registration of the containerd container factory failed: %v", err) klog.V(5).Infof("Registration of the containerd container factory failed: %v", err)
} }
err = crio.Register(self, self.fsInfo, self.includedMetrics)
if err != nil {
klog.V(5).Infof("Registration of the crio container factory failed: %v", err)
}
err = systemd.Register(self, self.fsInfo, self.includedMetrics)
if err != nil {
klog.V(5).Infof("Registration of the systemd container factory failed: %v", err)
}
err = raw.Register(self, self.fsInfo, self.includedMetrics, self.rawContainerCgroupPathPrefixWhiteList) err = raw.Register(self, self.fsInfo, self.includedMetrics, self.rawContainerCgroupPathPrefixWhiteList)
if err != nil { if err != nil {
klog.Errorf("Registration of the raw container factory failed: %v", err) klog.Errorf("Registration of the raw container factory failed: %v", err)
...@@ -331,6 +359,10 @@ func (self *manager) Start() error { ...@@ -331,6 +359,10 @@ func (self *manager) Start() error {
self.quitChannels = append(self.quitChannels, quitGlobalHousekeeping) self.quitChannels = append(self.quitChannels, quitGlobalHousekeeping)
go self.globalHousekeeping(quitGlobalHousekeeping) go self.globalHousekeeping(quitGlobalHousekeeping)
quitUpdateMachineInfo := make(chan error)
self.quitChannels = append(self.quitChannels, quitUpdateMachineInfo)
go self.updateMachineInfo(quitUpdateMachineInfo)
return nil return nil
} }
...@@ -351,6 +383,28 @@ func (self *manager) Stop() error { ...@@ -351,6 +383,28 @@ func (self *manager) Stop() error {
return nil return nil
} }
func (self *manager) updateMachineInfo(quit chan error) {
ticker := time.NewTicker(*updateMachineInfoInterval)
for {
select {
case <-ticker.C:
info, err := machine.Info(self.sysFs, self.fsInfo, self.inHostNamespace)
if err != nil {
klog.Errorf("Could not get machine info: %v", err)
break
}
self.machineMu.Lock()
self.machineInfo = *info
self.machineMu.Unlock()
klog.V(5).Infof("Update machine info: %+v", *info)
case <-quit:
ticker.Stop()
quit <- nil
return
}
}
}
func (self *manager) globalHousekeeping(quit chan error) { func (self *manager) globalHousekeeping(quit chan error) {
// Long housekeeping is either 100ms or half of the housekeeping interval. // Long housekeeping is either 100ms or half of the housekeeping interval.
longHousekeeping := 100 * time.Millisecond longHousekeeping := 100 * time.Millisecond
...@@ -450,7 +504,9 @@ func (self *manager) getAdjustedSpec(cinfo *containerInfo) info.ContainerSpec { ...@@ -450,7 +504,9 @@ func (self *manager) getAdjustedSpec(cinfo *containerInfo) info.ContainerSpec {
if spec.HasMemory { if spec.HasMemory {
// Memory.Limit is 0 means there's no limit // Memory.Limit is 0 means there's no limit
if spec.Memory.Limit == 0 { if spec.Memory.Limit == 0 {
self.machineMu.RLock()
spec.Memory.Limit = uint64(self.machineInfo.MemoryCapacity) spec.Memory.Limit = uint64(self.machineInfo.MemoryCapacity)
self.machineMu.RUnlock()
} }
} }
return spec return spec
...@@ -783,6 +839,8 @@ func (self *manager) GetFsInfo(label string) ([]v2.FsInfo, error) { ...@@ -783,6 +839,8 @@ func (self *manager) GetFsInfo(label string) ([]v2.FsInfo, error) {
} }
func (m *manager) GetMachineInfo() (*info.MachineInfo, error) { func (m *manager) GetMachineInfo() (*info.MachineInfo, error) {
m.machineMu.RLock()
defer m.machineMu.RUnlock()
// Copy and return the MachineInfo. // Copy and return the MachineInfo.
return &m.machineInfo, nil return &m.machineInfo, nil
} }
......
...@@ -45,7 +45,7 @@ type rawContainerWatcher struct { ...@@ -45,7 +45,7 @@ type rawContainerWatcher struct {
} }
func NewRawContainerWatcher() (watcher.ContainerWatcher, error) { func NewRawContainerWatcher() (watcher.ContainerWatcher, error) {
cgroupSubsystems, err := libcontainer.GetCgroupSubsystems() cgroupSubsystems, err := libcontainer.GetAllCgroupSubsystems()
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to get cgroup subsystems: %v", err) return nil, fmt.Errorf("failed to get cgroup subsystems: %v", err)
} }
......
...@@ -101,8 +101,9 @@ func (self *OomParser) StreamOoms(outStream chan<- *OomInstance) { ...@@ -101,8 +101,9 @@ func (self *OomParser) StreamOoms(outStream chan<- *OomInstance) {
in_oom_kernel_log := checkIfStartOfOomMessages(msg.Message) in_oom_kernel_log := checkIfStartOfOomMessages(msg.Message)
if in_oom_kernel_log { if in_oom_kernel_log {
oomCurrentInstance := &OomInstance{ oomCurrentInstance := &OomInstance{
ContainerName: "/", ContainerName: "/",
TimeOfDeath: msg.Timestamp, VictimContainerName: "/",
TimeOfDeath: msg.Timestamp,
} }
for msg := range kmsgEntries { for msg := range kmsgEntries {
err := getContainerName(msg.Message, oomCurrentInstance) err := getContainerName(msg.Message, oomCurrentInstance)
......
...@@ -30,9 +30,9 @@ type TwoQueueCache struct { ...@@ -30,9 +30,9 @@ type TwoQueueCache struct {
size int size int
recentSize int recentSize int
recent *simplelru.LRU recent simplelru.LRUCache
frequent *simplelru.LRU frequent simplelru.LRUCache
recentEvict *simplelru.LRU recentEvict simplelru.LRUCache
lock sync.RWMutex lock sync.RWMutex
} }
...@@ -84,7 +84,8 @@ func New2QParams(size int, recentRatio float64, ghostRatio float64) (*TwoQueueCa ...@@ -84,7 +84,8 @@ func New2QParams(size int, recentRatio float64, ghostRatio float64) (*TwoQueueCa
return c, nil return c, nil
} }
func (c *TwoQueueCache) Get(key interface{}) (interface{}, bool) { // Get looks up a key's value from the cache.
func (c *TwoQueueCache) Get(key interface{}) (value interface{}, ok bool) {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()
...@@ -105,6 +106,7 @@ func (c *TwoQueueCache) Get(key interface{}) (interface{}, bool) { ...@@ -105,6 +106,7 @@ func (c *TwoQueueCache) Get(key interface{}) (interface{}, bool) {
return nil, false return nil, false
} }
// Add adds a value to the cache.
func (c *TwoQueueCache) Add(key, value interface{}) { func (c *TwoQueueCache) Add(key, value interface{}) {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()
...@@ -160,12 +162,15 @@ func (c *TwoQueueCache) ensureSpace(recentEvict bool) { ...@@ -160,12 +162,15 @@ func (c *TwoQueueCache) ensureSpace(recentEvict bool) {
c.frequent.RemoveOldest() c.frequent.RemoveOldest()
} }
// Len returns the number of items in the cache.
func (c *TwoQueueCache) Len() int { func (c *TwoQueueCache) Len() int {
c.lock.RLock() c.lock.RLock()
defer c.lock.RUnlock() defer c.lock.RUnlock()
return c.recent.Len() + c.frequent.Len() return c.recent.Len() + c.frequent.Len()
} }
// Keys returns a slice of the keys in the cache.
// The frequently used keys are first in the returned slice.
func (c *TwoQueueCache) Keys() []interface{} { func (c *TwoQueueCache) Keys() []interface{} {
c.lock.RLock() c.lock.RLock()
defer c.lock.RUnlock() defer c.lock.RUnlock()
...@@ -174,6 +179,7 @@ func (c *TwoQueueCache) Keys() []interface{} { ...@@ -174,6 +179,7 @@ func (c *TwoQueueCache) Keys() []interface{} {
return append(k1, k2...) return append(k1, k2...)
} }
// Remove removes the provided key from the cache.
func (c *TwoQueueCache) Remove(key interface{}) { func (c *TwoQueueCache) Remove(key interface{}) {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()
...@@ -188,6 +194,7 @@ func (c *TwoQueueCache) Remove(key interface{}) { ...@@ -188,6 +194,7 @@ func (c *TwoQueueCache) Remove(key interface{}) {
} }
} }
// Purge is used to completely clear the cache.
func (c *TwoQueueCache) Purge() { func (c *TwoQueueCache) Purge() {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()
...@@ -196,13 +203,17 @@ func (c *TwoQueueCache) Purge() { ...@@ -196,13 +203,17 @@ func (c *TwoQueueCache) Purge() {
c.recentEvict.Purge() c.recentEvict.Purge()
} }
// Contains is used to check if the cache contains a key
// without updating recency or frequency.
func (c *TwoQueueCache) Contains(key interface{}) bool { func (c *TwoQueueCache) Contains(key interface{}) bool {
c.lock.RLock() c.lock.RLock()
defer c.lock.RUnlock() defer c.lock.RUnlock()
return c.frequent.Contains(key) || c.recent.Contains(key) return c.frequent.Contains(key) || c.recent.Contains(key)
} }
func (c *TwoQueueCache) Peek(key interface{}) (interface{}, bool) { // Peek is used to inspect the cache value of a key
// without updating recency or frequency.
func (c *TwoQueueCache) Peek(key interface{}) (value interface{}, ok bool) {
c.lock.RLock() c.lock.RLock()
defer c.lock.RUnlock() defer c.lock.RUnlock()
if val, ok := c.frequent.Peek(key); ok { if val, ok := c.frequent.Peek(key); ok {
......
...@@ -18,11 +18,11 @@ type ARCCache struct { ...@@ -18,11 +18,11 @@ type ARCCache struct {
size int // Size is the total capacity of the cache size int // Size is the total capacity of the cache
p int // P is the dynamic preference towards T1 or T2 p int // P is the dynamic preference towards T1 or T2
t1 *simplelru.LRU // T1 is the LRU for recently accessed items t1 simplelru.LRUCache // T1 is the LRU for recently accessed items
b1 *simplelru.LRU // B1 is the LRU for evictions from t1 b1 simplelru.LRUCache // B1 is the LRU for evictions from t1
t2 *simplelru.LRU // T2 is the LRU for frequently accessed items t2 simplelru.LRUCache // T2 is the LRU for frequently accessed items
b2 *simplelru.LRU // B2 is the LRU for evictions from t2 b2 simplelru.LRUCache // B2 is the LRU for evictions from t2
lock sync.RWMutex lock sync.RWMutex
} }
...@@ -60,11 +60,11 @@ func NewARC(size int) (*ARCCache, error) { ...@@ -60,11 +60,11 @@ func NewARC(size int) (*ARCCache, error) {
} }
// Get looks up a key's value from the cache. // Get looks up a key's value from the cache.
func (c *ARCCache) Get(key interface{}) (interface{}, bool) { func (c *ARCCache) Get(key interface{}) (value interface{}, ok bool) {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()
// Ff the value is contained in T1 (recent), then // If the value is contained in T1 (recent), then
// promote it to T2 (frequent) // promote it to T2 (frequent)
if val, ok := c.t1.Peek(key); ok { if val, ok := c.t1.Peek(key); ok {
c.t1.Remove(key) c.t1.Remove(key)
...@@ -153,7 +153,7 @@ func (c *ARCCache) Add(key, value interface{}) { ...@@ -153,7 +153,7 @@ func (c *ARCCache) Add(key, value interface{}) {
// Remove from B2 // Remove from B2
c.b2.Remove(key) c.b2.Remove(key)
// Add the key to the frequntly used list // Add the key to the frequently used list
c.t2.Add(key, value) c.t2.Add(key, value)
return return
} }
...@@ -247,7 +247,7 @@ func (c *ARCCache) Contains(key interface{}) bool { ...@@ -247,7 +247,7 @@ func (c *ARCCache) Contains(key interface{}) bool {
// Peek is used to inspect the cache value of a key // Peek is used to inspect the cache value of a key
// without updating recency or frequency. // without updating recency or frequency.
func (c *ARCCache) Peek(key interface{}) (interface{}, bool) { func (c *ARCCache) Peek(key interface{}) (value interface{}, ok bool) {
c.lock.RLock() c.lock.RLock()
defer c.lock.RUnlock() defer c.lock.RUnlock()
if val, ok := c.t1.Peek(key); ok { if val, ok := c.t1.Peek(key); ok {
......
// Package lru provides three different LRU caches of varying sophistication.
//
// Cache is a simple LRU cache. It is based on the
// LRU implementation in groupcache:
// https://github.com/golang/groupcache/tree/master/lru
//
// TwoQueueCache tracks frequently used and recently used entries separately.
// This avoids a burst of accesses from taking out frequently used entries,
// at the cost of about 2x computational overhead and some extra bookkeeping.
//
// ARCCache is an adaptive replacement cache. It tracks recent evictions as
// well as recent usage in both the frequent and recent caches. Its
// computational overhead is comparable to TwoQueueCache, but the memory
// overhead is linear with the size of the cache.
//
// ARC has been patented by IBM, so do not use it if that is problematic for
// your program.
//
// All caches in this package take locks while operating, and are therefore
// thread-safe for consumers.
package lru
module github.com/hashicorp/golang-lru
// This package provides a simple LRU cache. It is based on the
// LRU implementation in groupcache:
// https://github.com/golang/groupcache/tree/master/lru
package lru package lru
import ( import (
...@@ -11,11 +8,11 @@ import ( ...@@ -11,11 +8,11 @@ import (
// Cache is a thread-safe fixed size LRU cache. // Cache is a thread-safe fixed size LRU cache.
type Cache struct { type Cache struct {
lru *simplelru.LRU lru simplelru.LRUCache
lock sync.RWMutex lock sync.RWMutex
} }
// New creates an LRU of the given size // New creates an LRU of the given size.
func New(size int) (*Cache, error) { func New(size int) (*Cache, error) {
return NewWithEvict(size, nil) return NewWithEvict(size, nil)
} }
...@@ -33,7 +30,7 @@ func NewWithEvict(size int, onEvicted func(key interface{}, value interface{})) ...@@ -33,7 +30,7 @@ func NewWithEvict(size int, onEvicted func(key interface{}, value interface{}))
return c, nil return c, nil
} }
// Purge is used to completely clear the cache // Purge is used to completely clear the cache.
func (c *Cache) Purge() { func (c *Cache) Purge() {
c.lock.Lock() c.lock.Lock()
c.lru.Purge() c.lru.Purge()
...@@ -41,30 +38,30 @@ func (c *Cache) Purge() { ...@@ -41,30 +38,30 @@ func (c *Cache) Purge() {
} }
// Add adds a value to the cache. Returns true if an eviction occurred. // Add adds a value to the cache. Returns true if an eviction occurred.
func (c *Cache) Add(key, value interface{}) bool { func (c *Cache) Add(key, value interface{}) (evicted bool) {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()
return c.lru.Add(key, value) return c.lru.Add(key, value)
} }
// Get looks up a key's value from the cache. // Get looks up a key's value from the cache.
func (c *Cache) Get(key interface{}) (interface{}, bool) { func (c *Cache) Get(key interface{}) (value interface{}, ok bool) {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()
return c.lru.Get(key) return c.lru.Get(key)
} }
// Check if a key is in the cache, without updating the recent-ness // Contains checks if a key is in the cache, without updating the
// or deleting it for being stale. // recent-ness or deleting it for being stale.
func (c *Cache) Contains(key interface{}) bool { func (c *Cache) Contains(key interface{}) bool {
c.lock.RLock() c.lock.RLock()
defer c.lock.RUnlock() defer c.lock.RUnlock()
return c.lru.Contains(key) return c.lru.Contains(key)
} }
// Returns the key value (or undefined if not found) without updating // Peek returns the key value (or undefined if not found) without updating
// the "recently used"-ness of the key. // the "recently used"-ness of the key.
func (c *Cache) Peek(key interface{}) (interface{}, bool) { func (c *Cache) Peek(key interface{}) (value interface{}, ok bool) {
c.lock.RLock() c.lock.RLock()
defer c.lock.RUnlock() defer c.lock.RUnlock()
return c.lru.Peek(key) return c.lru.Peek(key)
...@@ -73,16 +70,15 @@ func (c *Cache) Peek(key interface{}) (interface{}, bool) { ...@@ -73,16 +70,15 @@ func (c *Cache) Peek(key interface{}) (interface{}, bool) {
// ContainsOrAdd checks if a key is in the cache without updating the // ContainsOrAdd checks if a key is in the cache without updating the
// recent-ness or deleting it for being stale, and if not, adds the value. // recent-ness or deleting it for being stale, and if not, adds the value.
// Returns whether found and whether an eviction occurred. // Returns whether found and whether an eviction occurred.
func (c *Cache) ContainsOrAdd(key, value interface{}) (ok, evict bool) { func (c *Cache) ContainsOrAdd(key, value interface{}) (ok, evicted bool) {
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()
if c.lru.Contains(key) { if c.lru.Contains(key) {
return true, false return true, false
} else {
evict := c.lru.Add(key, value)
return false, evict
} }
evicted = c.lru.Add(key, value)
return false, evicted
} }
// Remove removes the provided key from the cache. // Remove removes the provided key from the cache.
......
...@@ -36,7 +36,7 @@ func NewLRU(size int, onEvict EvictCallback) (*LRU, error) { ...@@ -36,7 +36,7 @@ func NewLRU(size int, onEvict EvictCallback) (*LRU, error) {
return c, nil return c, nil
} }
// Purge is used to completely clear the cache // Purge is used to completely clear the cache.
func (c *LRU) Purge() { func (c *LRU) Purge() {
for k, v := range c.items { for k, v := range c.items {
if c.onEvict != nil { if c.onEvict != nil {
...@@ -47,8 +47,8 @@ func (c *LRU) Purge() { ...@@ -47,8 +47,8 @@ func (c *LRU) Purge() {
c.evictList.Init() c.evictList.Init()
} }
// Add adds a value to the cache. Returns true if an eviction occured. // Add adds a value to the cache. Returns true if an eviction occurred.
func (c *LRU) Add(key, value interface{}) bool { func (c *LRU) Add(key, value interface{}) (evicted bool) {
// Check for existing item // Check for existing item
if ent, ok := c.items[key]; ok { if ent, ok := c.items[key]; ok {
c.evictList.MoveToFront(ent) c.evictList.MoveToFront(ent)
...@@ -78,17 +78,18 @@ func (c *LRU) Get(key interface{}) (value interface{}, ok bool) { ...@@ -78,17 +78,18 @@ func (c *LRU) Get(key interface{}) (value interface{}, ok bool) {
return return
} }
// Check if a key is in the cache, without updating the recent-ness // Contains checks if a key is in the cache, without updating the recent-ness
// or deleting it for being stale. // or deleting it for being stale.
func (c *LRU) Contains(key interface{}) (ok bool) { func (c *LRU) Contains(key interface{}) (ok bool) {
_, ok = c.items[key] _, ok = c.items[key]
return ok return ok
} }
// Returns the key value (or undefined if not found) without updating // Peek returns the key value (or undefined if not found) without updating
// the "recently used"-ness of the key. // the "recently used"-ness of the key.
func (c *LRU) Peek(key interface{}) (value interface{}, ok bool) { func (c *LRU) Peek(key interface{}) (value interface{}, ok bool) {
if ent, ok := c.items[key]; ok { var ent *list.Element
if ent, ok = c.items[key]; ok {
return ent.Value.(*entry).value, true return ent.Value.(*entry).value, true
} }
return nil, ok return nil, ok
...@@ -96,7 +97,7 @@ func (c *LRU) Peek(key interface{}) (value interface{}, ok bool) { ...@@ -96,7 +97,7 @@ func (c *LRU) Peek(key interface{}) (value interface{}, ok bool) {
// Remove removes the provided key from the cache, returning if the // Remove removes the provided key from the cache, returning if the
// key was contained. // key was contained.
func (c *LRU) Remove(key interface{}) bool { func (c *LRU) Remove(key interface{}) (present bool) {
if ent, ok := c.items[key]; ok { if ent, ok := c.items[key]; ok {
c.removeElement(ent) c.removeElement(ent)
return true return true
...@@ -105,7 +106,7 @@ func (c *LRU) Remove(key interface{}) bool { ...@@ -105,7 +106,7 @@ func (c *LRU) Remove(key interface{}) bool {
} }
// RemoveOldest removes the oldest item from the cache. // RemoveOldest removes the oldest item from the cache.
func (c *LRU) RemoveOldest() (interface{}, interface{}, bool) { func (c *LRU) RemoveOldest() (key interface{}, value interface{}, ok bool) {
ent := c.evictList.Back() ent := c.evictList.Back()
if ent != nil { if ent != nil {
c.removeElement(ent) c.removeElement(ent)
...@@ -116,7 +117,7 @@ func (c *LRU) RemoveOldest() (interface{}, interface{}, bool) { ...@@ -116,7 +117,7 @@ func (c *LRU) RemoveOldest() (interface{}, interface{}, bool) {
} }
// GetOldest returns the oldest entry // GetOldest returns the oldest entry
func (c *LRU) GetOldest() (interface{}, interface{}, bool) { func (c *LRU) GetOldest() (key interface{}, value interface{}, ok bool) {
ent := c.evictList.Back() ent := c.evictList.Back()
if ent != nil { if ent != nil {
kv := ent.Value.(*entry) kv := ent.Value.(*entry)
......
package simplelru
// LRUCache is the interface for simple LRU cache.
type LRUCache interface {
// Adds a value to the cache, returns true if an eviction occurred and
// updates the "recently used"-ness of the key.
Add(key, value interface{}) bool
// Returns key's value from the cache and
// updates the "recently used"-ness of the key. #value, isFound
Get(key interface{}) (value interface{}, ok bool)
// Check if a key exsists in cache without updating the recent-ness.
Contains(key interface{}) (ok bool)
// Returns key's value without updating the "recently used"-ness of the key.
Peek(key interface{}) (value interface{}, ok bool)
// Removes a key from the cache.
Remove(key interface{}) bool
// Removes the oldest entry from cache.
RemoveOldest() (interface{}, interface{}, bool)
// Returns the oldest entry from the cache. #key, value, isFound
GetOldest() (interface{}, interface{}, bool)
// Returns a slice of the keys in the cache, from oldest to newest.
Keys() []interface{}
// Returns the number of items in the cache.
Len() int
// Clear all cache entries
Purge()
}
...@@ -11,7 +11,7 @@ import ( ...@@ -11,7 +11,7 @@ import (
"github.com/ibuildthecloud/kvsql/pkg/broadcast" "github.com/ibuildthecloud/kvsql/pkg/broadcast"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
utiltrace "k8s.io/apiserver/pkg/util/trace" utiltrace "k8s.io/utils/trace"
) )
type Generic struct { type Generic struct {
......
...@@ -21,7 +21,7 @@ import ( ...@@ -21,7 +21,7 @@ import (
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"golang.org/x/net/context" "golang.org/x/net/context"
utiltrace "k8s.io/apiserver/pkg/util/trace" utiltrace "k8s.io/utils/trace"
) )
type Txn interface { type Txn interface {
......
...@@ -66,7 +66,7 @@ func NewKVSQLHealthCheck(c storagebackend.Config) (func() error, error) { ...@@ -66,7 +66,7 @@ func NewKVSQLHealthCheck(c storagebackend.Config) (func() error, error) {
func newETCD3Client(c storagebackend.Config) (*clientv3.Client, error) { func newETCD3Client(c storagebackend.Config) (*clientv3.Client, error) {
cfg := clientv3.Config{ cfg := clientv3.Config{
Endpoints: c.ServerList, Endpoints: c.Transport.ServerList,
} }
if len(cfg.Endpoints) == 0 { if len(cfg.Endpoints) == 0 {
......
...@@ -39,7 +39,7 @@ import ( ...@@ -39,7 +39,7 @@ import (
"k8s.io/apiserver/pkg/storage" "k8s.io/apiserver/pkg/storage"
"k8s.io/apiserver/pkg/storage/etcd" "k8s.io/apiserver/pkg/storage/etcd"
"k8s.io/apiserver/pkg/storage/value" "k8s.io/apiserver/pkg/storage/value"
utiltrace "k8s.io/apiserver/pkg/util/trace" utiltrace "k8s.io/utils/trace"
) )
// authenticatedDataString satisfies the value.Context interface. It uses the key to // authenticatedDataString satisfies the value.Context interface. It uses the key to
......
language: go
go:
- "1.8"
- "1.9"
- "1.10"
- "1.11"
- "1.12"
- master
script: go test -v ./...
Copyright (c) 2009 The Go Authors. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
This repo is a drop-in replacement for the golang [text/tabwriter](https://golang.org/pkg/text/tabwriter/) package.
It is based on that package at [cf2c2ea8](https://github.com/golang/go/tree/cf2c2ea89d09d486bb018b1817c5874388038c3a/src/text/tabwriter) and inherits its license.
The following additional features are supported:
* `RememberWidths` flag allows remembering maximum widths seen per column even after Flush() is called.
* `RememberedWidths() []int` and `SetRememberedWidths([]int) *Writer` allows obtaining and transferring remembered column width between writers.
language: go
go:
- "1.6"
- "1.7"
- "1.8"
- "1.9"
- "1.10"
- "1.11"
sudo: false
The MIT License (MIT) The MIT License (MIT)
Copyright (c) 2015 Peter Renström Copyright (c) 2018 Peter Lithammer
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal
......
# Dedent # Dedent
[![Build Status](https://travis-ci.org/renstrom/dedent.svg?branch=master)](https://travis-ci.org/renstrom/dedent) [![Build Status](https://travis-ci.org/lithammer/dedent.svg?branch=master)](https://travis-ci.org/lithammer/dedent)
[![Godoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/renstrom/dedent) [![Godoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/lithammer/dedent)
Removes common leading whitespace from multiline strings. Inspired by [`textwrap.dedent`](https://docs.python.org/3/library/textwrap.html#textwrap.dedent) in Python. Removes common leading whitespace from multiline strings. Inspired by [`textwrap.dedent`](https://docs.python.org/3/library/textwrap.html#textwrap.dedent) in Python.
...@@ -13,19 +13,20 @@ Imagine the following snippet that prints a multiline string. You want the inden ...@@ -13,19 +13,20 @@ Imagine the following snippet that prints a multiline string. You want the inden
package main package main
import ( import (
"fmt" "fmt"
"github.com/renstrom/dedent" "github.com/lithammer/dedent"
) )
func main() { func main() {
s := `Lorem ipsum dolor sit amet, s := `
consectetur adipiscing elit. Lorem ipsum dolor sit amet,
Curabitur justo tellus, facilisis nec efficitur dictum, consectetur adipiscing elit.
fermentum vitae ligula. Sed eu convallis sapien.` Curabitur justo tellus, facilisis nec efficitur dictum,
fmt.Println(dedent.Dedent(s)) fermentum vitae ligula. Sed eu convallis sapien.`
fmt.Println("-------------") fmt.Println(Dedent(s))
fmt.Println(s) fmt.Println("-------------")
fmt.Println(s)
} }
``` ```
...@@ -39,10 +40,11 @@ consectetur adipiscing elit. ...@@ -39,10 +40,11 @@ consectetur adipiscing elit.
Curabitur justo tellus, facilisis nec efficitur dictum, Curabitur justo tellus, facilisis nec efficitur dictum,
fermentum vitae ligula. Sed eu convallis sapien. fermentum vitae ligula. Sed eu convallis sapien.
------------- -------------
Lorem ipsum dolor sit amet,
consectetur adipiscing elit. Lorem ipsum dolor sit amet,
Curabitur justo tellus, facilisis nec efficitur dictum, consectetur adipiscing elit.
fermentum vitae ligula. Sed eu convallis sapien. Curabitur justo tellus, facilisis nec efficitur dictum,
fermentum vitae ligula. Sed eu convallis sapien.
``` ```
## License ## License
......
module github.com/lithammer/dedent
dist: xenial
language: go language: go
go: go:
- 1.9.x - 1.9.x
...@@ -6,6 +7,13 @@ go: ...@@ -6,6 +7,13 @@ go:
- tip - tip
matrix: matrix:
include:
- go: 1.11.x
env:
- RUNC_USE_SYSTEMD=1
script:
- make BUILDTAGS="${BUILDTAGS}" all
- sudo PATH="$PATH" make localintegration RUNC_USE_SYSTEMD=1
allow_failures: allow_failures:
- go: tip - go: tip
...@@ -21,9 +29,8 @@ env: ...@@ -21,9 +29,8 @@ env:
- BUILDTAGS="seccomp apparmor selinux ambient" - BUILDTAGS="seccomp apparmor selinux ambient"
before_install: before_install:
- echo "deb http://archive.ubuntu.com/ubuntu trusty-backports main restricted universe multiverse" | sudo tee -a /etc/apt/sources.list
- sudo apt-get -qq update - sudo apt-get -qq update
- sudo apt-get install -y libseccomp-dev/trusty-backports - sudo apt-get install -y libseccomp-dev
- go get -u golang.org/x/lint/golint - go get -u golang.org/x/lint/golint
- go get -u github.com/vbatts/git-validation - go get -u github.com/vbatts/git-validation
- env | grep TRAVIS_ - env | grep TRAVIS_
......
...@@ -16,10 +16,9 @@ This means that `runc` 1.0.0 should implement the 1.0 version of the specificati ...@@ -16,10 +16,9 @@ This means that `runc` 1.0.0 should implement the 1.0 version of the specificati
You can find official releases of `runc` on the [release](https://github.com/opencontainers/runc/releases) page. You can find official releases of `runc` on the [release](https://github.com/opencontainers/runc/releases) page.
### Security ## Security
If you wish to report a security issue, please disclose the issue responsibly Reporting process and disclosure communications are outlined in [/org/security](https://github.com/opencontainers/org/blob/master/security/)
to security@opencontainers.org.
## Building ## Building
......
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
File mode changed from 100755 to 100644
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment